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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

7回答

3909閲覧

Pythonで出来るだけ公平なランダム当番表(重複なし)を作ってみましたが重複が出てしまいます

studying_now

総合スコア6

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

1グッド

2クリップ

投稿2018/05/29 07:36

編集2018/05/29 07:45

前提・実現したいこと

Pythonに興味を持ち、先月からwebで調べたりしながら勉強しています。
全くの初心者です。
勉強用に出来るだけ公平な当番表を作ってみました。
(練習用なので無駄な部分が多いかと思います)

実現したいことは、
・人名を連続入力する (例:山田、田中、佐藤...)
・1回の当番あたりの人数を入力する (例:2人)
・53週分の当番表が出力される
・多少の誤差はあるものの、人名ごとに担当回数がほぼ同じになるような
(=出来るだけ公平な)当番表の完成
です。

公平性を保つ為、53週で必要な当番人数の数を上回るように
リストを複製し、複製したリストから人数分ランダムに人名を選び、
選んだ名前はリストから消す、を53回繰り返しています。

しかし、当番数を複数にすると(2人以上)、
どうしても重複してしまいます(例:山田、山田 ← 2人選ばれていない)

重複無しの当番が人数分出力されるように改善したく思っています。

発生している問題・エラーメッセージ

発生している問題 (1)1番大きな問題 重複無しで指定の当番人数分の人名を抽出したいが、重複してしまう。 (2)2番目以降の問題 ・もっとスマートに記述できるなら勉強までに教えていただきたく思っています。 ・Excelに出力する部分の記述がいけないのか['']がついた状態で、セルに入ってしまい、  エクセルの加工がしにくいので、出力時に人名だけがセルに入るように改善したいです。 ・もし可能なら最初に「当番表の年」を聞いて、現在「第--週」と出力されている部分を  その週の「月曜日の日付」で出力したいと思っています。

該当のソースコード

#! python3 # Rota.py # 当番表生成 print('たぶん公平な当番表作成') print('●やっていること●') print('1.当番人数×53週 = 1年に必要な当番人数を算出') print('2.「必要な当番人数」を上回るように「メンバーリスト」を複製') print('3.メンバーリストからランダムにメンバーを選び、選ばれたメンバーはリストから消す') print('4.ランダムで、かつ回数に偏りが生まれにくい当番表の完成(たぶん)') print('5.さらに出来上がったリストをエクセルで出力(適当なのでなおしたい)') print('') #当番表の名前 print('当番表の名前は何にしますか? (例)掃除当番表') print('※ 出力されるエクセルファイルのファイル名になるので注意') rotaname = input() print('当番表の名前は「' + str(rotaname) + '」となります:') #メンバーの名前を聞く orgmembers = [] while True: print('メンバー' + str(len(orgmembers) + 1) + 'の名前を入力してください' + '(終了するにはEnterキーだけ押してください)') name = input() if name == '': break orgmembers = orgmembers + [name] print('メンバーは' + str(len(orgmembers)) + '名で、以下の通りですね:') for name in orgmembers: print(' '+name) #当番1回あたりの人数を聞く print('当番人数は一回当たり何人ですか?(半角の数字で入力)') rotanum = input() print('当番1回当たり' + str(rotanum) + '名ですね:') #当番表出力 print('第53週までの当番表を出力します') import random members = orgmembers #トータル何人が当番か計算 # トータルの人数×53週を計算し、totalmemに格納 totalmem = 53 * int(rotanum) #トータルの当番数を、リストの当番で割って商qと余りmodを算出 # totalmemをorgmembersで割って、その商を算出 q = int(totalmem) // len(orgmembers) mod = int(totalmem) % len(orgmembers) #トータルメンバーリストの作成 # qの回数+1だけ、orgmembersを繰り返しtmlistに追加 tmlist = orgmembers for i in range(int(q)): tmlist = tmlist + orgmembers #トータルメンバーリストのシャッフル # 念のため、tmlistをシャッフルする random.shuffle(tmlist) #当番人数分の名前を抽出 # 最終的なメンバーをfinalmemに格納 # エクセルファイルに最終的なメンバー表を出力 # memに、tmlist内からrotanumの数だけランダム抽出し、格納 # mem1に、memの固有要素数を抽出し、格納(重複が削除される) # memの長さとmem1の長さを比較し、等しくなかった場合はmemの選びなおし、等しかったら次へ # (例:memリスト(選んだメンバーリストの要素数)と、mem1(選んだメンバーリストの個有数)が一致しないと、 # 重複があるということになるので、重複がなくなるまで選びなおす) # memの長さがrotanumより小さかった場合、memの選びなおし # (例:2人当番で重複するとmem1が1となり、memと等しくなってしまい、 # 重複があるのに処理が次に移ってしまう為) # 重複無しの選抜メンバーにfinalmemに第何週かの情報をつけて結果を格納 # Excelに出力 # 53週分ループしたら終わり finalmem = [] totaldelmem = [] import openpyxl wb = openpyxl.Workbook() sheet = wb.active for i in range(53): mem = random.sample(tmlist,int(rotanum)) mem1 = list(set(mem)) if len(mem1) < len(mem): mem = random.sample(tmlist,int(rotanum)) mem1 = list(set(mem)) if len(mem1) != int(rotanum): mem = random.sample(tmlist,int(rotanum)) mem1 = list(set(mem)) sheet['A' + str(i+1)] = '第' + str(i+1) + '週 ' + str(mem) print('第' + str(i+1) + '週 ' + str(mem)) finalmem.append(str(mem)) for j in range(int(rotanum)): delmem = mem[0] tmlist.remove(delmem) mem.remove(delmem) totaldelmem.append(delmem) wb.save(str(rotaname) + '.xlsx') #finalmemに結果が格納されてるけど、うまく加工できない #後で何とかしたい #公平度チェック print('今回の公平度は以下の通り') uniq_tdm = list(set(totaldelmem)) for k in range(len(orgmembers)): tdm = uniq_tdm[0] print('●'+(tdm) + 'の当番回数は') print(totaldelmem.count(tdm)) uniq_tdm.remove(tdm) #おまけ print('エクセルファイルは.pyファイルと同じフォルダに出来てるよ') print('ファイル名は' + str(rotaname) + '.xlsx です')

試したこと

当初重複に対応するため、

memに、tmlist内からrotanumの数だけランダム抽出し、格納
mem1に、memの固有要素数を抽出し、格納(重複が削除される)
memの長さとmem1の長さを比較し、等しくなかった場合はmemの選びなおし、等しかったら次へ
(例:memリスト(選んだメンバーリストの要素数)と、mem1(選んだメンバーリストの個有数)が
一致しないと、重複があるということになるので、重複がなくなるまで選びなおす)
memの長さがrotanumより小さかった場合、memの選びなおし
(例:2人当番で重複するとmem1が1となり、memと等しくなってしまい、
重複があるのに処理が次に移ってしまう為)

という仕組みで回避を試みていますが、これでもやっぱり重複してしまいます。

補足情報(FW/ツールのバージョンなど)

Anaconda3 (64-bit)でPython 3.6.4 を使用しています

KojiDoi👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

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

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

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

guest

回答7

0

考えてみました。
基本的には構成員をシャッフルしたリストからどんどん割り当てていき
なくなれば新たにリストを作成して再割り当てしていきます。
ただ、構成人数が1回の当番人数で割り切れない場合、重複を避けなければならないのがややこしいですね。

Python

1import random 2 3N_MBR = 5 # 構成人数 4N_GRP = 2 # 当番人数 5N_WEEK= 10 # 稼働週 6 7groups = [] # 選抜結果。週毎の当番人 [ [0,1],[2,3]....] 8 9cur_members = [] # 現在の割り当て人員リスト 10for i in range( N_WEEK): 11 12 cur_grp = [] 13 14 # 残り割り当て人員が足りない 15 if len(cur_members) < N_GRP: 16 cur_grp = cur_members[:] # 残員をすべて割り当て 17 18 # 新たな割り当てリストを作成 19 cur_members = [i for i in range(N_MBR)] 20 random.shuffle(cur_members) 21 22 N_REMAIN = N_GRP-len(cur_grp) # 残り必要な人数 23 del_members = [] 24 25 # すでに割り当て済みと重複しない人員を選抜 26 for i in range(N_REMAIN): 27 for m in cur_members: 28 if m not in cur_grp: 29 del_members.append(m) 30 cur_grp.append(m) 31 break 32 33 # 割り当て済みの人員を割り当てリストから削除 34 for r in del_members: 35 cur_members.remove(r) 36 37 else: 38 cur_grp = cur_members[:N_GRP] # 先頭から割り当て 39 del cur_members[:N_GRP] # 割り当て済みは削除 40 41 groups.append(cur_grp) 42 43# 結果 44for g in groups: 45 print(g) 46

投稿2018/05/29 09:22

can110

総合スコア38266

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

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

KSwordOfHaste

2018/05/29 09:33

重複排除がややこしいといっても他の方法に比べてわかりやすいのではないでしょうかw 満たさないといけない制約を論理によって自動的に排除できる点が分かり易さのポイントである気がします!
studying_now

2018/05/29 09:35

ご回答頂きありがとうございます。 理解に時間がかかる為、完全に理解できたとはいえない状況ですが、 何回かrunしてみたところ、重複無しで選択できています! これから頂いた記述をしっかり理解したいと思いますが、 取り急ぎ、お礼申し上げたく返信させていただきました。 ありがとうございます!
can110

2018/05/29 09:42

動作は問題ないと思うのですが、重複削除部分がなんか泥臭いな~と思いつつ回答しちゃいました。
studying_now

2018/05/29 10:31

いえいえ、大変勉強になります!
guest

0

もういい答えがありますが、せっかく書いたので。

python

1import random 2 3names = list('abcdefghijklmnopq') 4nums = 5 5 6names_ = names[:] 7random.shuffle(names_) 8 9hist = [] 10for _ in range(54): 11 t = min(len(names_), nums) 12 ans = [names_.pop() for _ in range(t)] 13 if t < nums: 14 names_ = list(set(names) - set(ans)) 15 random.shuffle(names_) 16 ans_ = [names_.pop() for _ in range(nums-t)] 17 names_ += ans 18 random.shuffle(names_) 19 ans += ans_ 20 print(ans) 21 assert len(ans)==len(set(ans)) 22 hist += ans 23 24from collections import Counter 25 26c = Counter(hist) 27print(c)

投稿2018/05/29 09:36

mkgrei

総合スコア8560

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

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

can110

2018/05/29 09:39

あ~なるほど! setを使うと簡潔に書けますね。
mkgrei

2018/05/29 09:44

random.choicesが重複ありでなければ、と考えながら、ライブラリを漁ってみたり… if文内のリストの合成の順序が重要である割に、一意的ではないのが少し悩ましいところですね。
studying_now

2018/05/29 09:46

ご回答頂き、ありがとうございます! 頂いた内容を理解するのに時間が必要ですが、 何回かrunしてみたところ、早くて重複無しですね! ありがとうございます! これから頂いた記述をしっかり理解したいと思いますが、 取り急ぎ、お礼申し上げたく返信させていただきました。 ありがとうございます!
can110

2018/05/29 09:53

set減算で重複削除したあとにシャッフルと、シャッフル後に重複削除。 たしかに合成順の違いから結果の違い(偏り)が生じないかちょっと気になりますね。
KSwordOfHaste

2018/05/29 13:25

お二人とも ・同じ日にそれぞれ異なる人で当番グループを形成すること ・それぞれの人の総当番日数に偏りがないこと を条件に解いておられ、ある人が誰と組になるかは乱数に任せる方針と思います。グループで一緒になった人の頻度の分布や多分正規分布っぽくなると思います。メンバー数9人、当番人数/週=3、100週で結果を集計してみたところ、can110さん、mkgreさんともに差があるようには見えませんでした(ともにmin=12~24, max=44~58, 平均=33.3, 標準偏差6~7ぐらい) 例えば当番割り当てでし〇かちゃんと16回、〇ャ〇ア〇と58回同じグループになるなんてこともありそんなとき○び○君は誰かに泣きつく気がしますが、NP困難or完全だったりするんでしょうか・・・
hayataka2049

2018/05/29 13:32

横槍。私もさっき組み合わせを先に列挙してそこから選べば・・・と思って回答書きかけたんですけど、単に可能な組み合わせからランダムに選ぶと、当番日数の偏りの判定がないので、悲しいことになります。 組み合わせの集合から、各組み合わせに含まれる要素の回数に偏りがないように選ぶとすると、どんな方針が良いでしょうかね? いまいちうまいのが思いつかなくて
can110

2018/05/29 13:38 編集

検証ありがとうございます。 > 例えば当番割り当てでし〇かちゃんと16回、〇ャ〇ア〇と58回同じグループになるなんてこともあり それはやだなあw 何をもって「公平」かによりますが、いっそ初期状態はランダムからスタートし、あとは構成員m人から当番員n人の全組み合わせ順で割り当ててもらったほうが(少なくとも私は)良いです。
KSwordOfHaste

2018/05/29 13:42 編集

自分もhayataka2049さんの方法をコメントしちゃったんですが、その方針だと下手に組み合わせをランダムに選ぶと今度は個人の当番日数の分布が正規分布になり...誰かと58回同じになるのは我慢してもらっても当番日数自体が正規分布だとクラスで暴動がおきますね... can110さんがおっしゃるような規則的な割り当てが現実的な気がしました。
can110

2018/05/29 13:53 編集

はい。というか結局KSwordOfHasteさんの回答で示した筋が妥当と思います。 当事者として考えるとジ○イ○ンと58回と当たる可能性のあるルールはやだな、思いました(自分でそんな回答しといて何ですが)。
KSwordOfHaste

2018/05/29 13:57

し〇かちゃんと絶対一緒に当番できなくなるのもこれまた寂しいのでなんとも言い難いですが・・・本件からはずれますが、実際の割り当て問題には「Aさんはこの日はダメ」とか「BとCは組み合わせちゃダメ」といった制約がありそうなのでもっと複雑な問題なのかも知れませんね。
can110

2018/05/29 14:08

> 実際の割り当て問題には「Aさんはこの日はダメ」とか「BとCは組み合わせちゃダメ」 現実問題としてはこれが必ず入りますね。そのとたんに「公平」な割り当ては絶望的になりますがw 現実的には、各員の総労働時間を平均化するルールが優先されるかと思います。
guest

0

コメントを読む限り、『組み合わせ最適化』 で解く課題のような気がしてきました。(シフトスケジュール最適化の派生?)
と言う事で 試しにPuLPで書いてみました

今回いれた制約事項は

  • メンバーは全員で5人
  • 一回の当番は2人
  • 全てのメンバーができるだけ同じ回数だけ当番を行う
  • 2回連続の当番を避ける

です

出来るだけ当番を組む相手がバラバラになるようにする為の制約は入れておりません - 面倒そうなので・・・

Python

1import pulp 2import numpy as np 3 4# メンバー数 5members = 5 6# 週 7weeks = 53 8# 当番の人数 9duty_number = 2 10# 一人当たりの最低当番数 11minimum_number_of_duties = duty_number * weeks // members 12 13prob = pulp.LpProblem() 14 15### 目的関数は無し 16prob += 0 17 18# 変数を生成 (0: 当番なし, 1: 当番) 19var = pulp.LpVariable.dicts("duty", (range(members),range(weeks)), 0,1, 'Binary') 20 21### 制約条件 22# 一回あたりの当番の人数の制約 23for w in range(weeks): 24 prob += pulp.lpSum([var[m][w] for m in range(members)]) == duty_number 25# 一人当たりの最低当番数の制約 26for m in range(members): 27 prob += pulp.lpSum([var[m][w] for w in range(weeks)]) >= minimum_number_of_duties 28# 2回連続の当番をしない 29for w in range(weeks-1): 30 for m in range(members): 31 prob += var[m][w] + var[m][w+1] <= 1 32 33### 解く 34s = prob.solve() 35 36# 結果表示 37for w in range(weeks): 38 print("Week{:02d} : ".format(w), end='') 39 for m in range(members): 40 print(int(pulp.value(var[m][w])), end=' ') 41 print() 42for m in range(members): 43 print("Member{:02d} : ".format(m), end='') 44 print("{:d}回".format(sum(int(pulp.value(var[m][w])) for w in range(weeks)))) 45 46#Week00 : 0 1 0 1 0 47#Week01 : 0 0 1 0 1 48#Week02 : 1 1 0 0 0 49#Week03 : 0 0 1 0 1 50#Week04 : 1 1 0 0 0 51#Week05 : 0 0 0 1 1 52#Week06 : 1 0 1 0 0 53#Week07 : 0 1 0 0 1 54#Week08 : 1 0 0 1 0 55#Week09 : 0 1 0 0 1 56#Week10 : 0 0 1 1 0 57#Week11 : 0 1 0 0 1 58#Week12 : 0 0 1 1 0 59#Week13 : 0 1 0 0 1 60#Week14 : 1 0 0 1 0 61#Week15 : 0 1 0 0 1 62#Week16 : 1 0 1 0 0 63#Week17 : 0 1 0 1 0 64#Week18 : 1 0 0 0 1 65#Week19 : 0 0 1 1 0 66#Week20 : 1 0 0 0 1 67#Week21 : 0 1 0 1 0 68#Week22 : 1 0 1 0 0 69#Week23 : 0 0 0 1 1 70#Week24 : 0 1 1 0 0 71#Week25 : 1 0 0 0 1 72#Week26 : 0 0 1 1 0 73#Week27 : 1 0 0 0 1 74#Week28 : 0 0 1 1 0 75#Week29 : 1 0 0 0 1 76#Week30 : 0 0 1 1 0 77#Week31 : 0 1 0 0 1 78#Week32 : 1 0 0 1 0 79#Week33 : 0 0 1 0 1 80#Week34 : 1 0 0 1 0 81#Week35 : 0 0 1 0 1 82#Week36 : 1 1 0 0 0 83#Week37 : 0 0 1 1 0 84#Week38 : 1 1 0 0 0 85#Week39 : 0 0 1 1 0 86#Week40 : 1 1 0 0 0 87#Week41 : 0 0 1 0 1 88#Week42 : 0 1 0 1 0 89#Week43 : 1 0 1 0 0 90#Week44 : 0 1 0 1 0 91#Week45 : 1 0 0 0 1 92#Week46 : 0 1 1 0 0 93#Week47 : 0 0 0 1 1 94#Week48 : 1 1 0 0 0 95#Week49 : 0 0 0 1 1 96#Week50 : 0 1 1 0 0 97#Week51 : 1 0 0 0 1 98#Week52 : 0 1 1 0 0 99#Member0 : 21回 100#Member1 : 21回 101#Member2 : 21回 102#Member3 : 21回 103#Member4 : 22回

投稿2018/05/30 02:46

magichan

総合スコア15898

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

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

0

基本的にはcan110さんのアイディアと同じなんですが、「割り切れない場合重複を避けなければならない」問題を片付けるために、「だったら割り切れるようにすればいいじゃん」という発想で書いてみました。

まず、当番人数の倍数となるような長さの「ランダムな人のリスト」を作ります。これは、大本の「人リスト」をシャフルしたのちに、リストの前の方にいる人をリサイクルすることで行います。
このリストから一週分づつリストを出力していき、尽きたら再度「ランダムな人のリスト」を作るところから繰り返します。

リサイクルされる人は2回当番が回ってくるわけですが、毎回シャフルした後にリサイクルするので、全体としては誰もが公平にリサイクルされるはず。…なのですが、やってみると微妙にばらついてしまいました。リサイクルメンバーは大本リストから順番に割り当てるのがいいかもしれません。

python

1#!/usr/bin/env python3 2import random 3import csv 4random.seed() 5 6last_week_cnt = 53 # 週数 7rotanum = 3 # 当番の人数 8 9#orgmembers = ["西住", "武部", "五十鈴", "秋山"] 10#orgmembers = ["西住", "武部", "五十鈴", "秋山", "冷泉", "角谷", "小山", "河嶋", "磯辺"] 11#orgmembers = ["西住", "武部", "五十鈴", "秋山", "冷泉", "角谷", "小山", "河嶋", "磯辺", "近藤"] 12orgmembers = ["西住", "武部", "五十鈴", "秋山", "冷泉", "角谷", "小山", "河嶋", "磯辺", "近藤", "河西"] 13 14divisible_cnt = (len(orgmembers) // rotanum) * rotanum 15len_dutylist = len(orgmembers) 16len_dutylist += 0 if (divisible_cnt==len(orgmembers)) else (rotanum - len(orgmembers) % rotanum) 17week_dutylist = len_dutylist // rotanum 18 19with open("dutylist.txt", "w", newline="") as fho: 20 writer = csv.writer(fho, delimiter = "\t", quoting = csv.QUOTE_NONE) 21 for week_cnt in range(1, last_week_cnt+1, week_dutylist): 22 # 当番表断片を作る 23 dutylist = orgmembers[:] 24 random.shuffle(dutylist) 25 # 端数が出たときはリスト前部のメンバーをリサイクルしてだぶらせる 26 dutylist += [] if divisible_cnt==len(orgmembers) else dutylist[:len_dutylist - len(orgmembers)] 27 28 # 週ごと出力 29 for i in range(0, week_dutylist): 30 week_cnt2 = week_cnt + i 31 s = i * rotanum 32 if(week_cnt2 > last_week_cnt): 33 break 34 out = [week_cnt2] + dutylist[s : s + rotanum] 35 print(out) 36 writer.writerow(out)

投稿2018/05/30 01:40

KojiDoi

総合スコア13671

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

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

KSwordOfHaste

2018/05/30 04:49

しつこくてスミマセン。 特定のメンバーと一緒になる頻度に制約を付けようとすると・・・それはvar[m0][w]*var[m1][w]という項が出てきてしまい、線形問題じゃなくなるから非線形問題として捉えないとダメなのかな・・・と気付きました。多分非線形計画法(?)で解かないといけないのですよね・・・
KojiDoi

2018/05/30 05:13

すいません、その辺は全く考慮に入れてません。数学は苦手なので線形計画法もよくわかっていません(昔学校で習ったと思いますが忘れました)。
KSwordOfHaste

2018/05/30 05:18

でも一次式で表現できる制約である限りはとても簡単に応用できる便利なライブラリーですよね・・・「Aさんはこの日に割り当てられない」といった制約ならとても簡単に思えます。また「BさんとCさんを一緒にしちゃだめ」はvar[m_b][w] + var[m_c][w] < 2と書けるのでこれも簡単にできそうです!
guest

0

えーと・・・この目的には「完全にランダム」は本当は適してないと思います。

重複するかどうか以前に1日に当番に当たった人が2日にも当番にあたるという確率は0じゃないですし、ランダムにするという意味からはずれてないのでNGとも言えないと思います。

プログラミングの問題というよりは仕様の問題として「当番対象の人たちの順番だけ決めて置き」あとは「その順番に従って割り当てる」方法が一番公平感があると思います。

その際に必要なのは当番対象の人たちを最初だけランダムに並べることだけです。

そうしておいて並び替えた順番がA, B, C, D, Eさんの順番に並んだら

1日 A, B
2日 C, D
3日 E, A
4日 B, C
...以下同様

こうしたらよいと思います。

いやいやそういうことじゃなくて!といいたくなるかも知れませんけどw;

投稿2018/05/29 07:48

編集2018/05/29 07:49
KSwordOfHaste

総合スコア18394

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

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

studying_now

2018/05/29 08:00 編集

ご回答頂き、ありがとうございます。 すみません。「完全にランダム」というのは目指しておりません。 下のほうで、公平度の指標としてカウントしているように、あくまで 「53週内で当番になる回数が出来るだけ公平」であることをまず目指しています。 そのため、何週か連続で当番になることがあっても、当番回数さえできるだけ公平であれば いいと思っています。 そして次に、組み合わせが固定化してしまうのを避ける為、 つたない知識で出来るだけ工夫してみたのですが、なんだかうまくいかないという状況です。 つまり、AさんとBさんがいつも同じ組み合わせである、という状況を避けたいのです。 まとめると、当番自体が何週か連続になっても、(偶然組み合わせが同じになるのは仕方ないとして) 組み合わせが出来るだけ変わっていて、53週内の当番回数が出来る限り同じ、 というのを目指しています。 頂いた方法をまず真っ先に考えたのですが、組み合わせが固定化してしまうため、 ランダムにピックアップするというのをやってみた結果、うまくいかない、という状況です。
KSwordOfHaste

2018/05/29 08:08

失礼しました!この回答はスルーしていただいた方が・・・ ちなみにこの条件はなかなか難しい気がします。「組み合わせが固定化する」のをいかにばらけさせるかがポイントなのでしょうけど、充分人数が多いなら同一人物を選ばないようにするだけでも充分ランダムになると思えます。しかし要素の数が限られていてAさんが他の人となるべく均等に当番になるようにということならそれなりのプログラム上の制約を制御した方がよい気がします。 例えばA~Eさんまでとしたらその全ての組み合わせを生成しておき、人単位ではなくその組み合わせの集合から選ぶとか?
studying_now

2018/05/29 08:32 編集

端的には、冒頭に書いたように、この記述を修正して、 出てしまう「重複を避けたい」というのが質問なのですが、難しいですかね。。。 難しいことなのかどうかの判断もつかず困っています。 よりよい選び方へのご提案を頂いているのだとは思いますが、 重複に関連したものではないと感じます。 繰り返しになりますが、53週内の当番数が出来る限り公平であれば問題ないのです。 例えば、総勢5人(例えばAさんからEさん)で1回当たり2人の当番だと、 53週で1人あたりの当番回数は21回ないし22回です。 繰り返し書かせていただいている通り、この誤差は問題ないと考えています。 (以下追記します) 何週か連続で当番にあたってしまった人も、早いうちに当番が終わってOKとなることを 前提としています。 (追記終わり) 問題は、2人の当番をピックアップすると、この記述だと1回の当番2人に、(A、A)と、 同じ人名がピックアップされてしまう点です。 これを避けたい、というのが質問の内容です。
KSwordOfHaste

2018/05/29 08:15

あーでもどうしても現実を考えてしまいます。40人いて1回あたり2人が当番として10日後にまわってきたら文句いいたくなります。個々の人があまりに連続して当番にならないようにも配慮すべきでそちらとまんべんなく組みにするというのを両立するのはなかなかややこしそう。やっぱり規則的に巡回させた方が簡単かもしれません。 A,B C,D E,A B,D<=ここからペアを一つずらす C,E A,C ... みたいな・・・
KSwordOfHaste

2018/05/29 08:23 編集

失礼、脱線してましたね。 >(A、A)と、同じ人名がピックアップされてしまう こういう問題は同じ人を同じ日に割り当てようがなような制約制御を最初から考えるのが自然と思います。例えばある日の当番をAと決めたら同じ日の当番を決めるのはAを抜いた集合から決めるようにします。 二人の当番を決めるのにグループ1、グループ2を独立に決めるのではなくてグループ1から一人候補を抜き出してある日に割り当てたら、グループ2からその人を一旦抜き出した残りから候補を決めるといった方法が使えませんか?ランダムに選んでしまい同じ人を割り当てたらやり直しというのはごく簡単な少数の集合を相手にするならまだしも普通はそういう非効率な論理は避けると思います。
studying_now

2018/05/29 08:26 編集

本当に当番を回す場合はそうかもしれません。 しかし、冒頭書いたとおり、記述の練習ですので。。。 私の質問はそんなに難易度が高いのでしょうか。。。? ピックアップした当番の中に重複をなくしたいだけなのですけれど、 話がどんどん違う方向にいっている気がします。 記述で修正するのは無理なんですかね。。。
studying_now

2018/05/29 08:25

すみません、2つ目のご回答を頂いている間に送信していたようです。 一度選んだものを次回の選抜の集団から一旦抜き出しておく、というのは興味深いです。 しかし全く皆目見当がつきません。。。
KSwordOfHaste

2018/05/29 08:36

ゴメンナサイ現実問題として妥当な解法の論理を考えてしまいました。質問2の方に着目していたわけです。 >記述の練習ですので。。。 コードを書く前にアルゴリズムが問題になるのでアルゴリズムを色々アイデアだししてたつもりでしたがどうも暴走しちゃいました。すみません! >難易度が高いのでしょうか。。。? 高くないけど低くもない印象です。 >話がどんどん違う方向にいっている気がします。 それについては申し訳ないです! >記述で修正するのは無理なんですかね。。。 記述=アルゴリズムのことですよね?満足できるような方法はあるはずと思います。自分が簡単なアイデアを思いつけないだけです。もちょっと待ってればよいアドバイスがつくかも知れません。 自分は今の質問者さんの論理をなるべく生かした形でのよい提案をすぐに思いつけません。 申し訳ない!
studying_now

2018/05/29 08:46

いえいえ、いろいろとご示唆いただいてありがとうございます。 私自身がアドバイスを生かすような知識がないために、すみません。 Pythonの記述のことを指して言っていました(用語の使い方もおかしいかもしれません) 難易度が低くないとのこと、わかりました。 なんだか身の丈を大幅に超えたことをしようとしていたみたいです。
KSwordOfHaste

2018/05/29 08:49

身の丈を超えているとは考えるべきでないです! 誰だってちょっと難しい問題にぶつかるとそう簡単には解けないもんです。 ベストでなくても色々解法を試している今の質問者さんのコードはちゃんと経験になるはずですよ! ちなみにPythonでどうこうの問題ではなくてアルゴリズム(解法)の問題だと思います。まずはうまくいく方法を考えるのが大事です。それさえ思いつけばPythonででもなんででも書けますよ。
guest

0

週ごとに当番分の人数を選ぶのではなく、人ごとに回数分の当番の週を選べば、同じ週を回数分選べるという処理がありますが、重複したらやり直しなどもなくなると思います。

追記
よく考えたら、同じ週が残れば、同じ問題を抱えてしまいます。

投稿2018/05/29 08:56

編集2018/05/29 09:04
tmp

総合スコア277

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

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

studying_now

2018/05/29 09:20

回答ありがとうございます。 人を週に割り当てるのではなく、週(1-53)を人に割り当てるということでしょうか。 確かにそれだと重複は避けられませんね。。。 ピックアップしたものの中に重複がないようにする、という記述は出来ないでしょうか?
tmp

2018/05/29 09:29

どっちの割り当てにしても途中の選択時には、重複はなくしたとしても、最後のあまりものが重複してしまう恐れがあります。 その場合、再選択などしても重複は解消されないので、無限ループになってしまいます。
tmp

2018/05/29 09:40

同じ週が余らないように、当番の週を順番に割り当てた後、シャッフルするという方法もありますが、組み合わせにパターンがでてくる気がします。
guest

0

javascriptで書いてみました。
参考まで。

let func2 = ()=>{ // 前回の選択結果と必ず違う選択結果を返す乱数取得処理 const choice = (max, prev = -1)=>{ while(true){ let next = Math.floor(Math.random() * max); if(next != prev) return next; } }; let memberList = ['a','b','c','d','e','f','g']; // メンバー一覧 let count = [0,0,0,0,0,0,0]; // 各メンバーの当番回数 let candidates = []; // 当番抽出用の配列 for(let i = 0; i < 53; i++){ // 処理先頭で抽出用配列のサイズが0ならメンバー一覧から再抽出 if(candidates.length == 0){ candidates = memberList.filter(n=>true); } let c1 = choice(candidates.length); // 最初の抽出後、抽出用配列のサイズが1ならメンバー一覧から再抽出する準備 let isNewCandidates = false; if(candidates.length == 1){ isNewCandidates = true; c1 = memberList.indexOf(candidates[c1]); candidates = memberList.filter(n=>true); } let c2 = choice(candidates.length, c1); // 当番の出力 console.log((i+1) + " : " + candidates[c1] + " & " + candidates[c2]); // 当番回数の加算 count[memberList.indexOf(candidates[c1])]++; count[memberList.indexOf(candidates[c2])]++; // 抽出用配列から今回の抽出結果を削除 candidates = candidates.filter((n,ind)=>{ if(!isNewCandidates){ if(ind == c1) return false; } if(ind == c2) return false; return true; }); } memberList.forEach((n,i)=>{ console.log(n + "::" + count[i]);}); };

投稿2018/05/29 08:37

tkturbo

総合スコア5572

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

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

studying_now

2018/05/29 08:48

ご回答頂き、ありがとうございます。 こんなにも短時間に書いていただいておどろきました! しかし、私はPythonを先月本を買って勉強し始めたばかりで、javascriptは全く分かりません。 これから頂いたものを理解する為にお時間いただければと思います。 せっかくのご回答を理解できず、大変申し訳ございません。。。
tkturbo

2018/05/29 08:57

ポイントは ・「前回の選択結果と必ず違う選択結果を返す乱数取得処理」を準備すること ・1回の抽出が終わったら「抽出用配列から今回の抽出結果を削除」すること ↑これを念頭に置いておけばどの言語でも実装可能なはずです。 頑張ってください。
studying_now

2018/05/29 09:21

ご回答頂きありがとうございます。 抽出用の配列とは別に、選択用の配列を用意しておく、ということでしょうか。 考えて見ます!アドバイスありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問