小住真央のデモノートです。

このノートブックでは、受け入れ側にとって最も望ましい定員数が何通り考えられるかを調べている。

手順は以下の通りである。

  1. 定員を設けなかった場合のマッチングを調べる。その結果から受け入れ側にとって利得を生まない学生を除外する。

  2. (1)の結果から、受け入れ側にとって利得を生まない学生を除外し、そのときの定員数を導出する。

  3. (2)から導出した定員数から、各学科が1人定員を増やした場合の利得の増分を求め、比較する。

  4. 最も利得の増分が多い学科の定員を1人増やす。

  5. (3)(4)の操作を繰り返し、最適な定員数を導出する。


In [1]:
using MyMatching

今回は学生の人数を前回の300人から1500人に増やして試行を行う。


In [2]:
using ShingakuMatching

departments = get_departments()

num_students = 1500
students = get_students(num_students)

prop_prefs, resp_prefs, caps = get_random_prefs(students, departments)


WARNING: Compat.UTF8String is deprecated, use String instead
Out[2]:
(Array{Int64,1}[[126, 129, 140, 127, 84, 9, 60, 8, 146, 137  …  81, 106, 58, 130, 120, 109, 6, 23, 5, 21], [19, 76, 37, 138, 60, 126, 100, 72, 41, 56  …  84, 1, 33, 94, 74, 47, 13, 31, 35, 141], [15, 143, 97, 25, 120, 49, 76, 54, 126, 124  …  74, 41, 141, 9, 23, 51, 122, 123, 58, 45], [29, 39, 116, 135, 146, 54, 41, 13, 56, 131  …  5, 106, 83, 97, 8, 134, 147, 109, 137, 121], [25, 51, 126, 84, 130, 2, 56, 31, 120, 53  …  74, 124, 69, 13, 27, 86, 33, 121, 49, 17], [49, 3, 74, 36, 64, 56, 123, 32, 143, 100  …  141, 47, 21, 86, 37, 48, 16, 66, 35, 43], [72, 100, 7, 33, 56, 31, 94, 47, 35, 37  …  97, 19, 13, 39, 41, 76, 51, 130, 25, 121], [29, 54, 130, 13, 120, 60, 94, 100, 11, 37  …  62, 45, 39, 86, 56, 121, 33, 141, 25, 35], [9, 21, 112, 102, 17, 29, 23, 37, 62, 124  …  126, 15, 74, 58, 13, 43, 51, 138, 122, 100], [49, 15, 86, 39, 130, 121, 54, 23, 37, 97  …  60, 94, 29, 35, 143, 45, 47, 138, 17, 25]  …  [15, 9, 39, 133, 109, 49, 77, 75, 140, 58  …  35, 126, 129, 53, 132, 45, 33, 139, 127, 27], [100, 40, 58, 42, 55, 70, 64, 13, 20, 44  …  65, 24, 41, 53, 46, 35, 141, 16, 14, 123], [11, 15, 56, 86, 97, 60, 62, 68, 27, 9  …  102, 47, 21, 76, 33, 94, 122, 143, 25, 29], [54, 126, 58, 122, 141, 130, 9, 76, 49, 121  …  29, 15, 23, 11, 94, 138, 97, 37, 45, 13], [27, 41, 37, 54, 13, 56, 36, 112, 3, 34  …  42, 120, 61, 29, 19, 141, 97, 24, 52, 60], [1, 53, 51, 74, 130, 120, 121, 29, 49, 47  …  41, 72, 76, 35, 9, 19, 33, 17, 45, 25], [31, 68, 72, 138, 120, 94, 123, 33, 9, 43  …  97, 102, 112, 17, 130, 122, 53, 58, 15, 74], [123, 39, 37, 69, 54, 11, 138, 27, 86, 7  …  112, 43, 31, 33, 19, 45, 51, 62, 35, 94], [11, 21, 31, 68, 17, 130, 122, 123, 56, 19  …  62, 27, 74, 94, 97, 60, 51, 76, 45, 43], [100, 37, 39, 86, 112, 97, 126, 9, 94, 54  …  25, 13, 68, 41, 1, 122, 121, 58, 35, 123]], Array{Int64,1}[[830, 354, 277, 1395, 792, 121, 279, 49, 1062, 669  …  808, 634, 1080, 556, 374, 864, 1360, 1184, 1161, 811], [272, 582, 982, 358, 526, 1150, 1486, 1403, 136, 3  …  201, 233, 305, 769, 82, 561, 586, 868, 101, 820], [867, 898, 1374, 51, 130, 339, 1124, 163, 22, 422  …  1195, 1371, 998, 1186, 997, 1140, 859, 256, 938, 1006], [1074, 330, 1227, 476, 1327, 182, 970, 1262, 522, 1399  …  1256, 310, 406, 857, 1275, 1058, 1248, 323, 197, 1278], [466, 608, 1373, 1358, 547, 529, 301, 424, 60, 226  …  800, 31, 772, 1343, 485, 42, 1071, 562, 624, 1303], [145, 406, 86, 281, 827, 560, 840, 529, 1005, 221  …  391, 639, 77, 1341, 617, 1275, 452, 148, 1321, 275], [569, 1002, 582, 652, 1281, 1285, 1061, 690, 847, 1412  …  1117, 581, 174, 192, 346, 379, 1443, 1142, 658, 1116], [364, 1254, 196, 987, 284, 906, 639, 1230, 457, 237  …  1267, 407, 508, 1343, 972, 146, 546, 86, 1094, 1357], [594, 1469, 808, 1221, 625, 834, 412, 416, 1423, 875  …  1171, 576, 1468, 1083, 7, 333, 365, 552, 237, 747], [6, 743, 922, 629, 604, 642, 698, 12, 1459, 249  …  656, 1106, 620, 1186, 1381, 342, 936, 574, 712, 287]  …  [1476, 551, 351, 1047, 617, 1152, 474, 292, 204, 329  …  754, 514, 1058, 245, 1314, 973, 529, 747, 214, 1244], [1065, 862, 898, 790, 1062, 1478, 737, 412, 1405, 289  …  1412, 615, 98, 410, 1329, 758, 263, 1392, 203, 309], [1324, 389, 493, 1149, 861, 1482, 925, 1262, 54, 352  …  641, 514, 1004, 1354, 29, 1315, 1273, 1230, 1232, 236], [1491, 554, 806, 1313, 1324, 1265, 1152, 1121, 1177, 1294  …  1369, 54, 1455, 768, 218, 1234, 760, 383, 246, 1248], [492, 1469, 782, 537, 392, 1030, 589, 613, 880, 749  …  1005, 764, 1149, 364, 435, 31, 1286, 451, 854, 141], [1039, 351, 1109, 1303, 205, 1287, 140, 1020, 854, 984  …  1294, 125, 1267, 1198, 781, 106, 1043, 1373, 242, 684], [77, 84, 134, 1048, 838, 1241, 1347, 650, 1385, 987  …  678, 148, 90, 1174, 1108, 1429, 765, 238, 933, 716], [1491, 1369, 506, 660, 951, 941, 813, 764, 1170, 34  …  141, 372, 639, 319, 517, 991, 1004, 146, 903, 493], [739, 1369, 1215, 439, 1420, 596, 860, 253, 800, 1089  …  974, 1047, 218, 1219, 747, 429, 916, 568, 727, 190], [172, 849, 1240, 511, 487, 190, 976, 528, 1372, 662  …  1201, 970, 97, 364, 678, 1119, 1256, 719, 1221, 904]], [118, 1, 1, 1, 1, 2, 81, 3, 18, 3  …  7, 3, 6, 2, 6, 8, 24, 33, 8, 6])
.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
in #readtable#84 at C:\Users\mao21\.julia\v0.6\DataFrames\src\dataframe\io.jl
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
in #readtable#84 at C:\Users\mao21\.julia\v0.6\DataFrames\src\dataframe\io.jl
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
WARNING: Compat.UTF8String is deprecated, use String instead.
  likely near In[2]:3
in builddf at C:\Users\mao21\.julia\v0.6\DataFrames\src\dataframe\io.jl

以下では、学科にとって最適な定員数を考える。

学生の内定により発生する学科の利得は、次のように定める。Shingakumatchingでは、学生の志望する学科の選好リストにその学生が必ず含まれるため、選好リスト内には必ずしも学科の利得を上昇させない学生が含まれていると考えられる。

ここから、当該学生が学科の選好リストの下位50パーセントに属す場合、学科の利得は0とみなすこととする。

それ以外の場合は、1/2 ×(学生全体の人数)ー(学生に対する学科の選好順位)の大きさで決まるものとする。

まず、学科が定員を設けない場合のマッチングを考える。


In [3]:
limitless_caps = Vector{Int}(length(caps))
limitless_caps[1:end] = num_students


Out[3]:
1500

In [4]:
limitless_prop_matched, limitless_resp_matched, limitless_indptr = my_deferred_acceptance(prop_prefs, resp_prefs, limitless_caps)


Out[4]:
([126, 19, 15, 29, 25, 49, 72, 29, 9, 49  …  15, 100, 11, 54, 27, 1, 77, 123, 96, 100], [448, 471, 632, 711, 948, 976, 1150, 1496, 0, 0  …  0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 1501, 3001, 4501, 6001, 7501, 9001, 10501, 12001, 13501  …  213001, 214501, 216001, 217501, 219001, 220501, 222001, 223501, 225001, 226501])

まず、定員を設けなかった場合の学科側のマッチング結果(limitless_resp_matched)から、学科にとって利得が0の学生を排除し、各学科の利得が損なわれないように定員数を削減する。

以下の関数を用いる。


In [5]:
function decrease_caps(caps,resp_matched,resp_prefs,indptr)
    decreased_caps = copy(caps)
    function student_resp_rank(resp_number)
        w = resp_matched[indptr[resp_number]:indptr[resp_number+1]-1]
        st_ranking = Array{Int}(length(w),2)
        st_ranking[1:end] = 0
        y = 1
        while y <= caps[resp_number]
            if !(w[y] == 0) 
                resp_rank = 1
                pref = resp_prefs[resp_number]
                while !(pref[resp_rank] == w[y])
                    resp_rank += 1
                end
                st_ranking[y,1] = resp_rank
                st_ranking[y,2] = w[y]
            else
                break
            end
            y += 1
        end
        return st_ranking
    end
    
    resp_number = 1
    while resp_number <= length(caps)
        st_ranking = student_resp_rank(resp_number)
        i = 1
        while i <= length(st_ranking[:,2])
            if !(st_ranking[i,1] <= (1/2 * length(resp_prefs[resp_number]))) || (st_ranking[i,1] == 0)
                decreased_caps[resp_number] -= 1
            end
            i += 1
        end
        resp_number += 1
    end
    return decreased_caps
end


Out[5]:
decrease_caps (generic function with 1 method)

In [6]:
decreased_caps = decrease_caps(limitless_caps,limitless_resp_matched,resp_prefs,limitless_indptr)


Out[6]:
151-element Array{Int64,1}:
  4
  4
  3
  1
  0
  2
  3
  4
 10
  2
 14
  1
  8
  ⋮
  3
  5
  9
  5
  4
  3
  2
  5
  3
  4
  3
  0

実際にこの定員数を用いてマッチングを行う


In [7]:
decreased_prop_matched, decreased_resp_matched, decreased_indptr = my_deferred_acceptance(prop_prefs, resp_prefs, decreased_caps)


Out[7]:
([0, 0, 0, 0, 0, 0, 97, 37, 124, 97  …  145, 0, 0, 55, 0, 0, 134, 0, 127, 0], [636, 277, 44, 792, 420, 1006, 794, 1090, 51, 1374  …  84, 77, 225, 506, 1209, 872, 171, 739, 439, 1215], [1, 5, 9, 12, 13, 13, 15, 18, 22, 32  …  723, 728, 732, 735, 737, 742, 745, 749, 752, 752])

以下では、学科にとって、decreased_capsから定員数を増やす誘因があるかを調べる。

ある学科の定員数を増やした際に、その学科への移動を希望する学生と、その学生に対する学科の選好順位のリストを作成する関数を以下のように設定する。


In [8]:
function trans_ranking(resp_number,prop_matched)
    trans_list = Vector{Int}(num_students)
    trans_rank = Array{Int}(num_students,2)    
    trans_list[1:end] = 0
    trans_rank[1:end] = 0
    k = 1
    for x in 1:num_students
        if resp_number in prop_prefs[x]
            if prop_matched[x] == 0
                trans_list[k] = x
                k += 1
            else
                n = 1
                m = 1
                while !((prop_prefs[x][n]) == prop_matched[x])
                    n += 1
                end
                while !((prop_prefs[x][m]) == resp_number)
                    m += 1
                end
                if min(m,n) == m
                    trans_list[k] = x
                    k += 1
                else
                    continue
                end
            end
        end
    end
    y = 1
    if !(trans_list == []) && !(sum(trans_list) == 0)
        while y <= length(trans_list)
            if trans_list[y] == 0
                break
            else
                rank = 1
                while !((resp_prefs[resp_number][rank]) == trans_list[y])
                    rank += 1
                end
                trans_rank[y,2] = rank
                trans_rank[y,1]= trans_list[y]
            end
            y += 1
        end
        trans_order = view(trans_rank,1:y-1,:)
        sort(trans_order,1,rev=false)
    else
        trans_order = 0
    end
    return trans_order
end


Out[8]:
trans_ranking (generic function with 1 method)

trans_orderをもとに、定員を増やすことで学科が得られる利得を計算する関数を以下のように定める。


In [9]:
function increase_caps_benefit(resp_number,trans_order,prop_matched)
    z = 1
    delta_benefit = [0]
    if !(trans_order == 0)
        while ((trans_order[z,1]) <= (1/2 * length(resp_prefs[resp_number]))) && !(trans_order[z,1] == trans_order[end,1])
            push!(delta_benefit, (1/2 * num_students) - (trans_order[z,1]))
            z += 1
        end
        shift!(delta_benefit)
    else
        delta_benefit = []
    end
    return delta_benefit
end


Out[9]:
increase_caps_benefit (generic function with 1 method)

上の二つの関数を利用し、学科にとって最適な定員を求めるため、以下の関数を設定する。


In [10]:
function resp_optimal_caps(caps,prop_matched)
    resp_opt_caps = copy(caps)
    i = Vector{Int}(length(caps))
    i[1:end] = 1
    while sum(resp_opt_caps) <= num_students
        max_benefit = 0
        resp_num = 0
        resp_other_num = [0]
        resp_number = 1
        resp_box = 0
        stop = false
        while resp_number <= length(caps)
            trans_order = trans_ranking(resp_number,prop_matched)
            delta_benefit = increase_caps_benefit(resp_number,trans_order,prop_matched)
            if delta_benefit[i[resp_number]] <= max_benefit
                if delta_benefit[i[resp_number]] == max_benefit
                    push!(resp_other_num,resp_number)
                end
            else
                max_benefit = delta_benefit[i[resp_number]]
                resp_num = resp_number
                resp_other_num = [0]
            end
            resp_number += 1
        end
        if max_benefit == 0
            break
        end
        resp_opt_caps[resp_num] += 1
        i[resp_num] += 1
        if !(resp_other_num == [0])
            shift!(resp_other_num)
            m = 1
            while m <= length(resp_other_num)
                if !(num_students <= sum(resp_opt_caps))
                    resp_opt_caps[resp_other_num[m]] += 1
                    i[resp_other_num[m]] += 1
                else
                    for n in 1:m-1
                        resp_opt_caps[resp_other_num[n]] -= 1
                    end
                    resp_opt_caps[resp_num] -= 1
                    println("$resp_num and $resp_other_num can increase their caps.")
                    resp_box = push!(resp_other_num, resp_num)
                    stop = true
                    break
                end
                m += 1
            end
        end
        if stop
            break
        end
    end
    if resp_box == 0
        resp_box = resp_num
    end
    return resp_opt_caps,resp_box
end


Out[10]:
resp_optimal_caps (generic function with 1 method)

実際に、resp_opt_capsを求めてみると、以下のようになる。


In [11]:
resp_opt_caps,resp_box = resp_optimal_caps(decreased_caps,decreased_prop_matched)


3 and [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 70, 72, 74, 76, 84, 86, 94, 97, 100, 102, 112, 120, 121, 122, 123, 124, 126, 130, 138, 141, 143] can increase their caps.
Out[11]:
([9, 6, 4, 4, 0, 5, 5, 6, 21, 3  …  11, 13, 9, 6, 4, 6, 7, 4, 6, 2], [9, 10, 11, 12, 13, 14, 15, 16, 17, 18  …  121, 122, 123, 124, 126, 130, 138, 141, 143, 3])

In [12]:
sum(resp_opt_caps)


Out[12]:
1498

In [13]:
length(resp_box)


Out[13]:
80

以上の結果から、resp_opt_capsからさらに全体で2人定員を増やすことができることがわかる。

resp_boxに含まれる学科であればどの学科の定員を増やしても学科全体の利得の増え方は変わらない。

よって、resp_box内で2つ学科を選ぶような組み合わせを考えれば、最適な定員数が何通り考えられるかがわかる。


In [14]:
using Iterators

In [16]:
c = 0
for i in subsets(resp_box,num_students - sum(resp_opt_caps))
    c += 1
end
c


Out[16]:
3160

以上より、今回の試行では、学科全体にとって最適な定員数は3160通り考えられる。

以上で検証を終える。