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

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

ただいまの
回答率

89.86%

Pythonでのシフト作成における入れ替え

受付中

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 149

yoshipiman

score 1

前提・実現したいこと

現在、シフト作成のプログラムを作成しています。
【手順】
・日にち、時間帯ごとにシフト希望をリスト化
・それを能力値順にソート
・ソートされたリストから必要人数分取る。(その際取られなかった人のリストを作っておく)
・各シフトに社員が最低1人いない場合、その日にち、時間帯で入れなかった社員をランダムで選んで、入っている人のランダムで入れ替える
・3連勤している人がいた場合、3連勤の真ん中の日にちをその日にち、時間帯で入れなかった人のランダムと入れ替える

このような手順なのですが、最後の2つが途中まででつまずいています。

該当のソースコード

#従業員=["能力値","シフト希望時間","役職","休む度合い","対応度合い","名前"]
#役職=["社員","アルバイト"]
#役職=[0,1]
#シフト希望時間=[11:00-17:00,17:00-22:00,11:00-22:00,"希望無し"]
#シフト希望時間=[0,1,2,9]

#aは社員
a=[5,[2,9,2,2,2],0,1,3,"a"]
#bは社員
b=[3,[2,2,2,9,2],0,1,3,"b"]
#cは主婦
c=[3,[0,0,0,0,9],1,2,1,"c"]
#dはフリーター
d=[4,[2,2,2,9,1],1,2,2,"d"]
#eは大学生(夕方)
e=[1,[1,1,9,1,1],1,1,3,"e"]
#fは大学生(朝)
f=[2,[9,2,9,2,0],1,1,2,"f"]

member=[a,b,c,d,e,f]

shift = []
gozen = []
gogo = []

for i in range(len(member[1][1])):
    for j in member:
        if j[1][i] == 0:
            gozen.append(j)
        elif j[1][i] == 1:
            gogo.append(j)
        elif j[1][i] == 2:
            gozen.append(j)
            gogo.append(j)
    shift.append([gozen, gogo])
    gozen = []
    gogo = []

print(shift[0][0]) #1日目の午前の希望
print(shift[0][1]) #1日目の午後の希望 
print(shift[1][0]) #2日目の午前の希望
print(shift[1][1]) #2日目の午後の希望
print(shift[2][0]) #3日目の午前の希望
print(shift[2][1]) #3日目の午後の希望
print(shift[3][0]) #4日目の午前の希望
print(shift[3][1]) #4日目の午後の希望
print(shift[4][0]) #5日目の午前の希望
print(shift[4][1]) #5日目の午後の希望
print(".............................................")


#必要人数
#1日目=[午前の必要人数,午後の必要人数]
d1=[3,3]
d2=[2,3]
d3=[3,2]
d4=[2,2]
d5=[2,3]

date=[d1,d2,d3,d4,d5]


for day in range(len(shift)):             #昇順のソート(能力値の低い順)               
    for am_pm in range(len(shift[day])):
        shift[day][am_pm].sort()
        print (shift[day][am_pm])        

print(".............................................")


p=[]
q=[]
r=[]
for day in range(len(shift)):                                     #必要人数分取って入れなかった人を別のリストに入れる
    for am_pm in range(len(shift[day])):
        for i in range(len(shift[day][am_pm])-date[day][am_pm]):
             p.append(shift[day][am_pm][i])
             shift[day][am_pm].remove(shift[day][am_pm][i])     
        print (shift[day][am_pm])
        q.extend([p])
        p=[]
    r.extend([q])
    q=[]
print (r)
print(".............................................")


x=[]                             #3連勤チェックは時間帯に関係なく日ごとなので、日ごとの午前、午後を一つにまとめる作業
y=[]
for day in range(len(shift)):                                     
    for am_pm in range(len(shift[day])):
        for i in range(len(shift[day][am_pm])):
            x.append(shift[day][am_pm][i][5])    
    y.extend([x])
    x = []

for i in range(len(y)):
    y[i] = list(set(y[i]))

print(y)
print(".............................................")


for i in range(3):               #3連勤している人の入れ替えのプログラム。途中の入れ替えからが困っています。
    for j in range(len(y[i])):
        if y[i+1][j] in y[i]:
            if y[i+1][j] in y[i+2]:
                 if shift[i+1][j] in shift[i+1]:
                    shift[i+1].append



for day in range(len(shift)):         #社員が各日にち、時間帯に最低1人はいるかのチェック。いなかった時の入れ替えのプログラムで困っています。
    for am_pm in range(len(shift[day])):
        person_list = []        
        for i in shift[day][am_pm]:
            person_list.append(i[2])            
        if 0 in person_list:
            print('社員います')
        else:
            print('社員いません')

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

・各シフトに社員が最低1人いない場合、その日にち、時間帯で入れなかった社員をランダムで選んで、入っている人のランダムで入れ替える

・3連勤している人がいた場合、3連勤の真ん中の日にちをその日にち、時間帯で入れなかった人のランダムと入れ替える

この2つのプログラムの「各シフトに社員が最低1人いない場合」、「3連勤している人がいた場合」まではできたのですが「その日にち、時間帯で入れなかった社員をランダムで選んで、入っている人のランダムで入れ替える」と「3連勤の真ん中の日にちをその日にち、時間帯で入れなかった人のランダムと入れ替える」のプログラムがわからないです。
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • Lhankor_Mhy

    2020/01/15 13:49 編集

    「各シフトに社員が最低1人いない場合、その日にち、時間帯で入れなかった社員をランダムで選んで、入っている人のランダムで入れ替える」とのことですが、ここまで決まっているならコードになりませんか?
    どの部分がわからないのでしょうか?
    たとえば、「各シフトに社員が最低1人いない場合」がコードにできない、などと具体的にわからない部分を記載した方が回答がつくかもしれません。


    何ができていて、何ができていないのか(何がわかっていて、何がわからないのか)を書きましょう
    https://teratail.com/help/question-tips#questionTips3-5-2

    キャンセル

回答 2

0

「その日にち、時間帯で入れなかった社員」は、たとえば、filter(lambda x: x[2] == 0 and ( x[1][0]==0 or x[1][0]==2 ), member)とすると、1日目の午前に入れなかった社員です。

「ランダムで選んで」はrandom.choiceを使うといいでしょう。
https://docs.python.org/ja/3/library/random.html#random.choice

「入れ替え」については、そのまま代入すれば問題ないかと思います。

「時間帯で入れなかった人」については、前述同様filter(lambda x: ( x[1][0]==0 or x[1][0]==2 ) and x[5] != y[i][j], member)のようにして取得できると思います。

社員がいませんと出た後からを具体的にコードでお願い

仰せのままに。

for day in range(len(shift)):#社員が各日にち、時間帯に最低1人はいるかのチェック。いなかった時の入れ替えのプログラムで困っています。
    for am_pm in range(len(shift[day])):
        person_list = []        
        for i in shift[day][am_pm]:
            person_list.append(i[2])            
        if 0 in person_list:
            print('社員います', day, am_pm)
        else:
            print('社員いません', day, am_pm)
            print( 'その日にち、時間帯で入れなかった社員', *filter( lambda x: x[2] == 0 and ( x[1][day]==am_pm or x[1][day]==2 ), member) )
            print( 'ランダム', random.choice( [*filter( lambda x: x[2] == 0 and ( x[1][day]==am_pm or x[1][day]==2 ), member)] ) )
            print( 'その日にち、時間帯のシフト', shift[day][am_pm] )
            print( 'ランダム', shift[day][am_pm][random.randint( 0, len(shift[day][am_pm]) - 1)] )
            shift[day][am_pm][random.randint( 0, len(shift[day][am_pm]) - 1)] = random.choice( [*filter( lambda x: x[2] == 0 and ( x[1][day]==am_pm or x[1][day]==2 ), member)] )
            print( '入れ替え後の、その日にち、時間帯のシフト', shift[day][am_pm] )

途中からでなくても大丈夫なのでコードを教えて

(これはいわゆる丸投げ質問なのでは……)

def swapStraightDayWorker( member, shift, lastOfStraightDay, straightDayWorker):
    """
    シフトは破壊的に入れ替えます。

    lastOfStraightDay : int
        3連勤の最後の日
    straightDayWorker : list
        3連勤した人
    """
    middleDay = lastOfStraightDay - 1
    middleDayShift = shift[ middleDay ]
    #print('入れ替え前シフト', middleDayShift)
    for hourly, hourlyShift in enumerate(middleDayShift):
        chosenWorker = random.choice( [ x for x in member if ( x[ 1 ][ middleDay ] == hourly or x[ 1 ][ middleDay ] == 2 ) and x not in hourlyShift ] )
        if straightDayWorker in hourlyShift:
            i = hourlyShift.index( straightDayWorker )
            hourlyShift[i] = chosenWorker
    #print('入れ替え後シフト', middleDayShift)

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/01/17 17:14 編集

    > 3連勤してしまっているところがありました。

    これは解消が難しいと思います。

    たとえば、

    1 _bcd_
    2 ab_d_
    3 a_c_e
    4 a_c_e

    のようなシフトがあったとして、
    3日目のaの3連勤中日を交換するとなると、bかdしかないわけですが、

    1 _bcd_
    2 ab_d_
    3 _bc_e
    4 a_c_e

    どちらと交換しても別の人の新たな3連勤を生み、しかもこのループの中では発見されません。


    もちろん、シフト交換した時にループを初めから開始するなら発見できるわけですが、そこでも新たな3連勤を生むかもしれません。

    1 _bcd_
    2 a_cd_
    3 _bc_e
    4 a_c_e

    「ランダムにシフト交換を繰り返していけばいつかは解消するはず」と思うかもしれませんが、

    1 _bcd_
    2 a__de
    3 _bc_e
    4 a_c_e

    このケースでは、誰かが3連勤になってしまうため、いわゆる「無限ループ」という終了しないプログラムになってしまいます。



    一般にこのようなスケジューリング問題はNP困難(多項式時間に解決する一般解が存在しない)と言われています。
    正直、私の頭ではついていけない部分が多いです。
    https://www.nurse-scheduling-software.com/tutorial/newpage3.htm
    https://qiita.com/SaitoTsutomu/items/a33aba1a95828eb6bd3f
    ぜひ頑張ってみてください。

    キャンセル

  • 2020/01/17 17:40

    そうですよね。社員が最低1人というのも守らないといけないとなると3連勤を防ぐのは難しいと思いました。
    最後に質問なのですが、シフトの決め方は各日各時間帯のシフト希望を従業員の能力値順にソートしてそこから必要人数を取るという方法なのですが、ソートした中から前から順番に取るので、能力値が同じ人がいた場合、毎回、前の人を取るようになっています。そこで、能力値が同じときはランダムに必要人数分取るようにしたいのですがお願いできますでしょうか。現状の方法は解決方法のところにコードを貼りました

    キャンセル

  • 2020/01/17 18:04

    Python のソートは安定ソートなので、ソート前の順序を保存します。
    一方で、Key引数によって、比較関数を指定することができます。
    https://docs.python.org/ja/3/howto/sorting.html#key-functions

    なので、ソートする前にシャッフルし、リストの1番目の要素だけをKeyにしてソートすれば、アルゴリズムを変えずにランダムに取れるのではないかと思います。
    https://docs.python.org/ja/3/library/random.html#random.shuffle

    キャンセル

0

for day in range(len(shift)-2):                             #3連勤禁止                                                                        
    for am_pm in range(len(shift[day])):
        for i in range(len(shift[day][am_pm])): 
            if shift[day][am_pm][i] in shift[day+1][am_pm]:    
                if shift[day][am_pm][i] in shift[day+2][am_pm]:
                    print("3連勤してる")
                    shift[day][am_pm].remove(shift[day][am_pm][i])
                    shift[day][am_pm].append(random.choice(yasumi[day][am_pm]))
                    print(shift[day][am_pm])
                else:
                    print("3連勤してない")
            else:   
                print("3連勤してない")

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 89.86%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる