質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

Q&A

解決済

6回答

1319閲覧

マッチングのロジックについて

OtaMasato

総合スコア44

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

0グッド

3クリップ

投稿2019/06/24 00:55

編集2019/06/24 04:10

実際の業務の内容を簡略化していますが、以下のようなケースについて、ロジックを考えています。
皆さんならどのようにロジックを考えますか。
ロジック(アルゴリズム)なので、言語は問いません。

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ページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

gentaro

2019/06/24 01:35

回答者に対して問題を出すのは「やってほしいことだけを記載した丸投げの質問」という理由で低評価が付きやすいです。自分ならどう考えるのか、その解決策にどのような問題があると認識しているのかを書きましょう。
OtaMasato

2019/06/24 04:13

ご指摘ありがとうございます。「自分ならどう考えるのか」と「どのような問題点があるのか」について追記しました。
guest

回答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
tiitoi

総合スコア21956

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

OtaMasato

2019/06/24 21:12

ご回答ありがとうございます。「2部グラフの最大重みマッチング」なんてロジックがあるんですね。 まだ内容理解できてませんが、言葉の意味からするとドンピシャのように思えます。 また、Pythonのコードご提示ありがとうございます。 今回のロジックをコードに落とし込む際に大変参考になります。
tiitoi

2019/06/25 03:58

整数計画問題として解く方法も思いついたので追記しました。 今回の問題は次のいずれかで解くのがいいと思います。 グラフ理論で解く: 第1希望 > 第2希望 > 第3希望となるような重み付けをした2部グラフ作成 → 最大重みマッチング 整数計画問題として解く: 追記のように定式化 > まだ内容理解できてませんが 問題の定式化が理解できれば、アルゴリズム自体は理解できなくても問題ないです。 実際は自分で1から実装するより、ライブラリを使って解くことをおすすめします。 Python の場合、グラフ問題 → networkx、整数計画問題 → Pulp というライブラリで解けますが、 もし他の言語をお使いの場合でも同様にライブラリがあるので、それを使うといいかと思います。
OtaMasato

2019/06/25 17:49

追加のご提案ありがとうございます。 整数計画問題についても見てみます。 また、pythonのコードについても環境作ってみたので、実際に動かしてみます。
OtaMasato

2019/06/26 15:39

「2部グラフの最大重みマッチング」を試してみたところ、いい感じに結果を得ることができました!ご協力いただきありがとうございます。 整数計画問題の方は、貼り付けの仕方が悪かったのか、途中でエラーになっていました。ちょっと見てみようと思います。
guest

0

1のロジックは、

1.一人しか1位に選んでいないおやつを探して確定する。
2.残りの人達が2位に選んでいるおやつが、1のおやつでもなく他に選んでいる人がいない場合は確定する。
3.まだ残っている場合は、ランダムで確定する。

投稿2019/06/24 05:36

kasa0

総合スコア578

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

OtaMasato

2019/06/24 21:10

1のロジックはご提案の内容がきれいですね。 (私の複数人だったら乱数で決定する→複数人いなければ決定する、にされていますね) ありがとうございます。
guest

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
mather

総合スコア6753

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kasa0

2019/06/24 05:27

2の場合は、0点の人がいないという条件で絞り込めますね。
mather

2019/06/24 05:34

いえ、そうは行かないと思います。 ワーストケースとして希望しないおやつが全員同じということがありえます。
kasa0

2019/06/24 05:40 編集

はい、必ずではありませんが、絞り込み条件として使用できるということです。 同じ得点なら0点の人がいない方を選択とか。
mather

2019/06/24 06:03

う〜ん。絞り込みっていう言い方がおかしいと思います。 「希望しないおやつが全員同じ」というケースが発生した時点で、どんな組み合わせを使っても0点の人がいますし、目的は「おやつをなるべく希望に沿うように選択する」なので絞り込みも何もなくて組み合わせの中で最高点のものが選ばれるだけだと思います。
kasa0

2019/06/24 06:51

最高点が[3,2,2,0],[3,2,1,1]の2パターンの場合はどうですか?
mather

2019/06/24 07:01

そういう意味の絞り込みなのですね。 配点に関しては「なるべく希望に沿うように」のバイアス次第だと思います。 例えば配点を[100, 10, 1, 0] (なるべく高い希望にフィットさせる)としてもいいですし、[10. 9. 8. 0](希望していないおやつをなるべく選ばない)とするのもいいでしょう。
OtaMasato

2019/06/24 21:08

ご回答ありがとうございます。 すみません、理解が追い付いていないのですが、「全員第一希望のおやつが来た => 3 x 4 = 12」はどのように求められているのでしょうか。 少年Aと少年Bがケーキを第1希望とし、少年C、少年Dがケーキを第2希望とした場合、3+3+2+2で10になると思いますが、この10という数字と、少年A、Bの乱数対決がどのように紐づくのかがわかりませんでした。 理解が追い付いていなく、申し訳ありません。
OtaMasato

2019/06/25 17:52

> いや、問題設定に従うと全員違うおやつになるはずですよね? > もし少年Aが「ケーキ」だったときには3点としてカウントし、他の少年も同様に配られたおやつで採点するだけです。 ご指摘ありがとうございます。 考え違いしていました。 希望に対して点数をつけるのではなく、配布した場合について点数をつけるということですね。 ちょっと計算してみます。
guest

0

組み合わせは高々24通りなので、今見えてる3つのルールに対してスコアを付け、最大となる組み合わせを提示すればよいかと。

投稿2019/06/24 02:04

編集2019/06/24 02:11
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

OtaMasato

2019/06/24 04:18

スコアは私も最初考えていたのですが、具体的にどのようにロジックに落とし込めばよいのかわかりませんでした。自分の選択(1~3位)だけでなく、他の子どもたちの選択も考慮してスコアを付ければいい感じにできそうですね。(人気のないおやつを選択している場合は高スコアのように) ご回答ありがとうございます。
guest

0

子供Aが「ケーキ」「プリン」「いちご」の順(ケーキが第一希望)で希望しており
他全員が「いちご」を(第一~第三までに)希望していないとしたら,
Aにケーキを割り当てる(すると,他の誰かに「いちご」が行く)ことと,
他の者に「いちご」を割り当てないようにすることと,
一体どちらを優先するのでしょう?

後者側の事柄を優先するならば,
不人気な物から順に割り振っていけば良いように思います.
(最も総合的に選ばれていないおやつを,それを最もマシに捉えている子供に割り当てる)

投稿2019/06/24 04:59

fana

総合スコア11658

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

OtaMasato

2019/06/24 13:32

> 子供Aが「ケーキ」「プリン」「いちご」の順(ケーキが第一希望)で希望しており > 他全員が「いちご」を(第一~第三までに)希望していないとしたら, > Aにケーキを割り当てる(すると,他の誰かに「いちご」が行く)ことと, > 他の者に「いちご」を割り当てないようにすることと, > 一体どちらを優先するのでしょう? 2のロジックでは、Aにケーキを割り当てるのではなく、「いちご」を割り当てたいと考えています。 > 後者側の事柄を優先するならば, > 不人気な物から順に割り振っていけば良いように思います. > (最も総合的に選ばれていないおやつを,それを最もマシに捉えている子供に割り当てる) やっぱりそうなりますよね。 後から追記しましたが、私のロジックはご指摘の内容になっていると思います。 不格好に見えるのですが・・・。
guest

0

1.1位に選んだおやつを優先的に獲得できるロジック

普通に1位を選ばせるだけなのでロジックも何もありません

2.おやつをなるべく希望に沿うように選択するロジック

※第1~3位のおやつ以外がなるべく行き渡らないようにするロジック

たとえば全員が同じ順番でおやつを選んだ場合
かならず誰かが第4希望を得るしかありません、

1位で被った場合は、再び選択させるのではなく、ランダム(乱数)で良い

ちょっと意味がわからない。
1位でかぶったら話し合いをしないでどちらかがランダムでヒットして
もう一方は2位以降の希望にまわるという意味でしょうか?

基本的にはプロ野球のドラフト1位指名とおなじ方式だと考えればよいでしょう

投稿2019/06/24 01:49

yambejp

総合スコア114850

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

OtaMasato

2019/06/24 04:25

ご回答ありがとうございます。 分かりづらくて申し訳ありません。 > 引用テキスト普通に1位を選ばせるだけなのでロジックも何もありません 実装するにはロジックは必要と思うのですが・・・ 私のイメージについて追記させていただきました。 > たとえば全員が同じ順番でおやつを選んだ場合 > かならず誰かが第4希望を得るしかありません、 はい、この場合はやむを得ないかと思います。 ただ、例えば、ケーキを全員が第1希望として、メロンの希望が第2希望1人だけだったとしたら、メロンの第2希望の人がケーキを取ると、メロンは希望しない人にわたってしまうことになるので、2のロジックはこの点を考慮したいといった意図になります。(1のロジックはこの点を考慮しないことになります) > ちょっと意味がわからない。 > 1位でかぶったら話し合いをしないでどちらかがランダムでヒットして > もう一方は2位以降の希望にまわるという意味でしょうか? その認識であっています。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問