実際の業務の内容を簡略化していますが、以下のようなケースについて、ロジックを考えています。
皆さんならどのようにロジックを考えますか。
ロジック(アルゴリズム)なので、言語は問いません。
4人の子供がおやつを選んでいます。
おやつは「ケーキ」「プリン」「いちご」「メロン」の4つで、それぞれ1つしかありません。
子どもたちは自分の欲しいおやつを第1希望~第3希望まで決められます。
ここでおやつと子どものマッチングロジックについて、以下の2つのロジックで考えます。
1.1位に選んだおやつを優先的に獲得できるロジック
2.おやつをなるべく希望に沿うように選択するロジック
※第1~3位のおやつ以外がなるべく行き渡らないようにするロジック
1、2のロジックの考え方について教えてください。
なお、1位で被った場合は、再び選択させるのではなく、ランダム(乱数)で良いです。
(子供たちは結果に対してケンカしないものとします)
■6/24 12:35 追記
こちらの質問が他のユーザから「やってほしいことだけを記載した丸投げの質問」という指摘を受けました 「質問を編集する」ボタンから編集を行い、調査したこと・試したことを記入していただくと、回答が得られやすくなります。
すみません、丸投げのつもりはなかったのですが、この手の質問の仕方はteratailではNGなのですね。
勉強になりました。
私の考えです。
1.1位に選んだおやつを優先的に獲得できるロジック
- 1つ目のおやつを選択する
- 1つ目のおやつを第1位に選んだ人を検索する
- 第1位に選んだ人が複数人いる場合は、乱数により決定する(1つ目のおやつと子どもをマッチングする)
- 第1位に選んだ人がいなかった場合はマッチングをスキップする
- 次のおやつに進む
- すべてのおやつに対して上記操作を行う
- おやつと子どものマッチングがすべて完了していたらマッチング終了
- マッチングが完了していなければ、検索対象を第2として1つ目のおやつから検索する
- マッチングが完了するまで、第3位、選択なしの順に検索対象をずらしていく
2.おやつをなるべく希望に沿うように選択するロジック
※第1~3位のおやつ以外がなるべく行き渡らないようにするロジック
- 1つ目から4つ目までのおやつに対して、おやつの選択の数を検索する
- おやつの選択の数が1つのおやつについてループする
- おやつを選択した子どもとおやつをマッチングする
- おやつの選択の数が1つのおやつについてのループが完了した後に、マッチング済みのおやつと子どもを除いて、再度おやつの選択の数を検索する
- おやつの選択の数が1つのおやつについてループして、おやつと子どもをマッチングする
- このループを繰り返し、選択の数が1つのおやつが無くなったら次に進む
- 1つ目のおやつを選択し、複数選択されているはずなので、順位の高い人とマッチングする。順位が同位で複数人いた場合は乱数による決定とする。
- 2つ目のおやつでも同じように順位の高い人とマッチングする
- この操作をすべてのマッチングが完了していないおやつと子どもに対して実施する
- 最後に、全く選択されていないおやつがあれば、おやつと最後に残った人をマッチングする
1も2もキレイなロジックではないと思いますが、いまいち良いロジックが思い浮かびません。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/06/24 04:13
回答6件
0
ベストアンサー
2部グラフを作成して、マッチング問題として解けばよいかと思います。
- 頂点集合
V1 = {'Aさん', 'Bさん', 'Cさん', 'Dさん'}
V2 = {'メロン', 'いちご', 'プリン', 'ケーキ'}
- 辺集合
Aさんがメロンを第1希望としていた場合、(Aさん、メロン) という辺を作成する。
辺の重みを 第1希望 > 第2希望 > 第3希望となるようにつける。
- アルゴリズム
2部グラフの最大重みマッチング
Maximum Matching in General Graphs
最大重みマッチング問題
できる限り各個人の希望を反映したマッチング ==> 最大重みマッチングです。
Python のサンプルコード
Python だと networkx というグラフライブラリの max_weight_matching() という関数で実装されています。
networkx.algorithms.matching.max_weight_matching — NetworkX 2.4rc1.dev20190623123601 documentation
python
1import networkx as nx 2 3# 子供の希望 4wishes = { 5 # "名前": [第1希望, 第2希望, 第3希望] 6 "Aさん": ["プリン", "ケーキ", "メロン"], 7 "Bさん": ["ケーキ", "いちご", "メロン"], 8 "Cさん": ["いちご", "プリン", "ケーキ"], 9 "Dさん": ["プリン", "いちご", "メロン"], 10} 11 12# グラフを作成する。 13G = nx.Graph() 14for person, wish in wishes.items(): 15 for snack, weight in zip(wish, range(3, 0, -1)): 16 # (人, 希望するお菓子) という辺を追加する。 17 # 重みは第1希望: 3, 第2希望: 2, 第3希望: 1 になる。 18 G.add_edge(person, snack, weight=weight) 19 20# 「重み最大となるマッチング」を探す。 21matches = nx.max_weight_matching(G) 22print(matches) 23# {('ケーキ', 'Bさん'), ('プリン', 'Dさん'), ('いちご', 'Cさん'), ('Aさん', 'メロン')} 24 25y = sum([G.edges[e1, e2]["weight"] for e1, e2 in matches]) 26print(f"y = {y}")
追記 整数計画問題として解く方法
次のように変数を定義することで整数計画問題としても定式化できます。
PuLP という線形ソルバーで説いたところ、networkx の最大重みマッチングの結果と一致することが確認できました。
python
1from pulp import * 2 3# お菓子の一覧 4snacks = ["いちご", "キャラメル", "ケーキ", "プリン", "メロン"] 5 6# 子供の希望 7wishes = { 8 # "名前": [第1希望, 第2希望, 第3希望] 9 "Aさん": ["プリン", "ケーキ", "メロン"], 10 "Bさん": ["ケーキ", "いちご", "メロン"], 11 "Cさん": ["いちご", "プリン", "キャラメル"], 12 "Dさん": ["プリン", "いちご", "メロン"], 13} 14 15# モデルを作成する。 16model = LpProblem(name="max weight matching", sense=LpMaximize) 17 18# # 変数を作成する。 19X = [ 20 [LpVariable(f"{person}_{snack}", cat=LpBinary) for snack in snacks] 21 for person in wishes 22] 23 24# 目的関数を設定する。 25weights = range(3, 0, -1) # 重み: 第1希望:3、第2希望:2、第3希望:1 26snack_to_idx = {name: i for i, name in enumerate(snacks)} # お菓子の名前がキー、インデックスが値の辞書 27 28y = 0 29for i, wish in enumerate(wishes.values()): 30 for snack, w in zip(wish, weights): 31 j = snack_to_idx[snack] 32 y += X[i][j] * w 33model += y 34 35# 制約を設定する。 36 37# (1) 一人1つしかお菓子を選べない。 38for i in range(len(wishes)): 39 model += lpSum(X[i]) == 1 40 41# (2) 二人以上が同じお菓子を選べない。 42for j in range(len(snacks)): 43 s = 0 44 for i in range(len(wishes)): 45 s += X[i][j] 46 model += s <= 1 47 48# モデルを表示する。 49print(model) 50 51# 解く。 52ret = model.solve() 53print("status", LpStatus[ret]) # status Optimal 54 55# 解けた場合は結果を表示する。 56if ret == 1: 57 print(f"y = {model.objective.value()}") 58 for i, person in enumerate(wishes): 59 select_idx = np.argmax([x.value() for x in X[i]]) # 選択したお菓子のインデックス 60 print(f"{person}: {snacks[select_idx]}")
output
1max weight matching: 2MAXIMIZE 32*Aさん_ケーキ + 3*Aさん_プリン + 1*Aさん_メロン + 2*Bさん_いちご + 3*Bさん_ケーキ + 1*Bさん_メロン + 3*Cさん_いちご + 1*Cさん_キャラメル + 2*Cさん_プリン + 2*Dさん_いちご + 3*Dさん_プリン + 1*Dさん_メロン + 0 4SUBJECT TO 5_C1: Aさん_いちご + Aさん_キャラメル + Aさん_ケーキ + Aさん_プリン + Aさん_メロン = 1 6 7_C2: Bさん_いちご + Bさん_キャラメル + Bさん_ケーキ + Bさん_プリン + Bさん_メロン = 1 8 9_C3: Cさん_いちご + Cさん_キャラメル + Cさん_ケーキ + Cさん_プリン + Cさん_メロン = 1 10 11_C4: Dさん_いちご + Dさん_キャラメル + Dさん_ケーキ + Dさん_プリン + Dさん_メロン = 1 12 13_C5: Aさん_いちご + Bさん_いちご + Cさん_いちご + Dさん_いちご <= 1 14 15_C6: Aさん_キャラメル + Bさん_キャラメル + Cさん_キャラメル + Dさん_キャラメル <= 1 16 17_C7: Aさん_ケーキ + Bさん_ケーキ + Cさん_ケーキ + Dさん_ケーキ <= 1 18 19_C8: Aさん_プリン + Bさん_プリン + Cさん_プリン + Dさん_プリン <= 1 20 21_C9: Aさん_メロン + Bさん_メロン + Cさん_メロン + Dさん_メロン <= 1 22 23VARIABLES 240 <= Aさん_いちご <= 1 Integer 250 <= Aさん_キャラメル <= 1 Integer 260 <= Aさん_ケーキ <= 1 Integer 270 <= Aさん_プリン <= 1 Integer 280 <= Aさん_メロン <= 1 Integer 290 <= Bさん_いちご <= 1 Integer 300 <= Bさん_キャラメル <= 1 Integer 310 <= Bさん_ケーキ <= 1 Integer 320 <= Bさん_プリン <= 1 Integer 330 <= Bさん_メロン <= 1 Integer 340 <= Cさん_いちご <= 1 Integer 350 <= Cさん_キャラメル <= 1 Integer 360 <= Cさん_ケーキ <= 1 Integer 370 <= Cさん_プリン <= 1 Integer 380 <= Cさん_メロン <= 1 Integer 390 <= Dさん_いちご <= 1 Integer 400 <= Dさん_キャラメル <= 1 Integer 410 <= Dさん_ケーキ <= 1 Integer 420 <= Dさん_プリン <= 1 Integer 430 <= Dさん_メロン <= 1 Integer 44 45status Optimal 46y = 10.0 47Aさん: メロン 48Bさん: ケーキ 49Cさん: いちご 50Dさん: プリン
投稿2019/06/24 05:39
編集2019/06/25 03:50総合スコア21956
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/06/24 21:12
2019/06/25 03:58
2019/06/25 17:49
2019/06/26 15:39
0
2のロジックですが組み合わせ数が少ないなら以下のようにできますね。(te2jiさんと同じ)
- 第一希望のおやつがきた => 3
- 第二希望のおやつがきた => 2
- 第三希望のおやつがきた => 1
- 希望していないものがきた => 0
例:全員第一希望のおやつが来た => 3 x 4 = 12
というふうに配点をして、4!(=24)通りの組み合わせについて点数を付けてみればいいです。
最高点で同点のものがあればランダムで一つ組み合わせを決定すれば良いと思います。
2019-06-25 追記
少年Aと少年Bがケーキを第1希望とし、少年C、少年Dがケーキを第2希望とした場合、3+3+2+2で10になると思いますが、
いや、問題設定に従うと全員違うおやつになるはずですよね?
もし少年Aが「ケーキ」だったときには3点としてカウントし、他の少年も同様に配られたおやつで採点するだけです。
もし ["プリン", "ケーキ", "いちご", "メロン"]
という配り方をした場合、この組み合わせの評価は何点か?を計算しているだけですよ。
以下、採点までを行うRubyのコードです。
ruby
1# ["ケーキ","プリン","いちご","メロン"]を並べ替えたすべての組み合わせ(24個) 2PERMIRATIONS = ["ケーキ","プリン","いちご","メロン"].permutation(4).to_a 3 4# 4人がそれぞれ欲しい順 5wants = Array.new(4) { PERMIRATIONS.sample } 6#[ 7# ["いちご", "メロン", "ケーキ", "プリン"], 8# ["いちご", "ケーキ", "メロン", "プリン"], 9# ["プリン", "ケーキ", "いちご", "メロン"], 10# ["いちご", "メロン", "ケーキ", "プリン"] 11#] 12 13# すべての組み合わせについて採点 14result = {} 15PERMIRATIONS.each { |a| 16 total = 0 17 a.each_with_index { |b, i| 18 total += 3 - wants[i].index(b) 19 } 20 result[a] = total 21} 22 23# 結果 24result 25#{["ケーキ", "プリン", "いちご", "メロン"]=>4, 26# ["ケーキ", "プリン", "メロン", "いちご"]=>4, 27# ["ケーキ", "いちご", "プリン", "メロン"]=>9, 28# ["ケーキ", "いちご", "メロン", "プリン"]=>4, 29# ["ケーキ", "メロン", "プリン", "いちご"]=>8, 30# ["ケーキ", "メロン", "いちご", "プリン"]=>3, 31# ["プリン", "ケーキ", "いちご", "メロン"]=>5, 32# ["プリン", "ケーキ", "メロン", "いちご"]=>5, 33# ["プリン", "いちご", "ケーキ", "メロン"]=>7, 34# ["プリン", "いちご", "メロン", "ケーキ"]=>4, 35# ["プリン", "メロン", "ケーキ", "いちご"]=>6, 36# ["プリン", "メロン", "いちご", "ケーキ"]=>3, 37# ["いちご", "ケーキ", "プリン", "メロン"]=>10, 38# ["いちご", "ケーキ", "メロン", "プリン"]=>5, 39# ["いちご", "プリン", "ケーキ", "メロン"]=>7, 40# ["いちご", "プリン", "メロン", "ケーキ"]=>4, 41# ["いちご", "メロン", "ケーキ", "プリン"]=>6, 42# ["いちご", "メロン", "プリン", "ケーキ"]=>8, 43# ["メロン", "ケーキ", "プリン", "いちご"]=>10, 44# ["メロン", "ケーキ", "いちご", "プリン"]=>5, 45# ["メロン", "プリン", "ケーキ", "いちご"]=>7, 46# ["メロン", "プリン", "いちご", "ケーキ"]=>4, 47# ["メロン", "いちご", "ケーキ", "プリン"]=>7, 48# ["メロン", "いちご", "プリン", "ケーキ"]=>9}
投稿2019/06/24 05:03
編集2019/06/25 02:52総合スコア6753
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/06/24 05:27
2019/06/24 05:34
2019/06/24 05:40 編集
2019/06/24 06:03
2019/06/24 06:51
2019/06/24 07:01
2019/06/24 21:08
2019/06/25 17:52
0
組み合わせは高々24通りなので、今見えてる3つのルールに対してスコアを付け、最大となる組み合わせを提示すればよいかと。
投稿2019/06/24 02:04
編集2019/06/24 02:11退会済みユーザー
総合スコア0
0
子供Aが「ケーキ」「プリン」「いちご」の順(ケーキが第一希望)で希望しており
他全員が「いちご」を(第一~第三までに)希望していないとしたら,
Aにケーキを割り当てる(すると,他の誰かに「いちご」が行く)ことと,
他の者に「いちご」を割り当てないようにすることと,
一体どちらを優先するのでしょう?
後者側の事柄を優先するならば,
不人気な物から順に割り振っていけば良いように思います.
(最も総合的に選ばれていないおやつを,それを最もマシに捉えている子供に割り当てる)
投稿2019/06/24 04:59
総合スコア11658
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/06/24 13:32
0
1.1位に選んだおやつを優先的に獲得できるロジック
普通に1位を選ばせるだけなのでロジックも何もありません
2.おやつをなるべく希望に沿うように選択するロジック
※第1~3位のおやつ以外がなるべく行き渡らないようにするロジック
たとえば全員が同じ順番でおやつを選んだ場合
かならず誰かが第4希望を得るしかありません、
1位で被った場合は、再び選択させるのではなく、ランダム(乱数)で良い
ちょっと意味がわからない。
1位でかぶったら話し合いをしないでどちらかがランダムでヒットして
もう一方は2位以降の希望にまわるという意味でしょうか?
基本的にはプロ野球のドラフト1位指名とおなじ方式だと考えればよいでしょう
投稿2019/06/24 01:49
総合スコア114850
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/06/24 04:25
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。