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

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

新規登録して質問してみよう
ただいま回答率
85.30%
Vue.js

Vue.jsは、Webアプリケーションのインターフェースを構築するためのオープンソースJavaScriptフレームワークです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

2回答

1502閲覧

Vue.jsでシフト表を作成したい

miffy-n

総合スコア1

Vue.js

Vue.jsは、Webアプリケーションのインターフェースを構築するためのオープンソースJavaScriptフレームワークです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

1クリップ

投稿2023/07/21 09:09

実現したいこと

Vue.jsでシフト表を作成したい

前提

【条件】メンバーは15人、週にみんな2回出勤、1日の出勤人数は6人

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

現状は、ボタンをクリックすると1ヶ月分(4週分)のシフト表が表示されるようにはできました。
しかし、同じ曜日に所々、名前の重複が出てしまいます。
重複することなくメンバー全員が週に2回表示されるには、どのように記述すれば良いのでしょうか?

該当のソースコード

html

1<!DOCTYPE html> 2<html lang="ja"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <link rel="stylesheet" href="style.css"> 8 <title>シフト表自動作成(Vue.js)</title> 9 </head> 10 <body> 11 <div id="app" class="df"> 12 <div class="tbl"> 13 <table> 14 <thead> 15 <tr> 16 <th></th> 17 <th></th> 18 <th></th> 19 <th></th> 20 <th></th> 21 </tr> 22 </thead> 23 <tbody id="list"> 24 <tr v-for="week in names6" :key="week.weekNumber"> 25 <td v-for="day in week.days" :key="day.dayName"> 26 <div v-for="name in day.names" :key="name">{{ name }}</div> 27 </td> 28 </tr> 29 </tbody> 30 </table> 31 </div> 32 <div class="btn"> 33 <button v-on:click="buttonClick">ボタン</button> 34 </div> 35 </body> 36 <script src="https://cdn.jsdelivr.net/npm/vue@3.2.37/dist/vue.global.js"></script> 37 <script src="main.js"></script> 38</html> 39

css

1@charset "UTF-8"; 2 3table, th, td { 4 border: 1px #111 solid; 5 border-collapse: collapse; 6} 7 8th { 9 color: #fff; 10 background-color: #777; 11 width: 110px; 12} 13 14td { 15 text-align: center; 16} 17 18button{ 19 position: relative; 20 left: 30px; 21 top: 10px; 22} 23 24.df{ 25 display: flex; 26} 27 28tbody { 29 word-spacing: 20px; 30} 31

js

1Vue.createApp({ 2 data: function () { 3 return { 4 names: ["佐藤", "伊藤", "遠藤", "鈴木", "田中", "小林", "千葉", "加藤", "山崎", "中村", "高橋", "渡辺", "山本", "吉田", "石田", "佐藤", "伊藤", "遠藤", "鈴木", "田中", "小林", "千葉", "加藤", "山崎", "中村", "高橋", "渡辺", "山本", "吉田", "石田"], 5 names6: [], 6 } 7 }, 8methods: { 9 //ボタンが押された時の処理 10 buttonClick() { 11 //4週分繰り返す 12 for (let i = 0; i < 4; i++) { 13 //月~金をdaysOfWeekに格納 14 const daysOfWeek = ["月", "火", "水", "木", "金"]; 15 //namesの配列からデータの一部分だけ取り出しnamesCopyに格納 16 const namesCopy = this.names.slice(); 17 const weeks = 2; 18 //weeksを繰り返す 19 for (let i = 0; i < weeks; i++) { 20 const weekSchedule = { weekNumber: i + 1, days: [] }; 21 daysOfWeek.forEach((day) => { 22 const daySchedule = { dayName: day, names: [] }; 23 //6回繰り返す 24 for (let j = 0; j < 6; j++) { 25 if (namesCopy.length === 0) break; 26 //namesCopyの要素数を取得→ランダム→randomIndexに格納 27 const randomIndex = Math.floor(Math.random() * namesCopy.length); 28 //spliceで置き換えて、daySchedule.namesに追加する 29 daySchedule.names.push(namesCopy.splice(randomIndex, 1)[0]); 30 } 31 weekSchedule.days.push(daySchedule); 32 }); 33 //names6という空の配列に追加する 34 this.names6.push(weekSchedule); 35 } 36 } 37 }, 38}, 39mounted() { 40 for (let i = 0; i < buttonClick(); i++) { 41 } 42}, 43 44}).mount("#app") 45

やり方自体が間違っているのかもしれませんが、実現するためのヒントなどアドバイスいただければと思います。

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

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

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

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

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

guest

回答2

1

ベストアンサー

シャッフル済み15人×2をくっつけて30人の配列を作る戦略で書いてみました。くっつける時に、1回目の末尾3人と2回目の先頭3人の重複チェックをしています。

もちろん、この戦略の場合は各人のシフトは前半1回+後半1回に限られてしまいますので、その制約については要望と異なるかもしれません。

js

1Vue.createApp({ 2 data: function () { 3 return { 4 names: ["佐藤", "伊藤", "遠藤", "鈴木", "田中", "小林", "千葉", "加藤", "山崎", "中村", "高橋", "渡辺", "山本", "吉田", "石田"], 5 names6: [], 6 } 7 }, 8 methods: { 9 //ボタンが押された時の処理 10 buttonClick() { 11 function shuffle(src) { 12 const a = [] 13 for (const x of src) { 14 const j = Math.floor(Math.random() * (a.length + 1)); 15 a.push(a[j]) 16 a[j] = x 17 } 18 return a 19 } 20 //4週分繰り返す 21 for (let i = 0; i < 4; i++) { 22 //月~金をdaysOfWeekに格納 23 const daysOfWeek = ["月", "火", "水", "木", "金"]; 24 //namesの配列からデータの一部分だけ取り出しnamesCopyに格納 25 const names1 = shuffle(this.names) 26 const names2 = (names1 => { 27 let temp = shuffle(this.names) 28 while (names1.slice(-3).some(e => temp.slice(0, 3).includes(e))) { 29 temp = shuffle(this.names) 30 } 31 return temp 32 })(names1) 33 const namesCopy = [...names1, ...names2]; 34 const weeks = 2; 35 //weeksを繰り返す 36 for (let i = 0; i < weeks; i++) { 37 const weekSchedule = { weekNumber: i + 1, days: [] }; 38 daysOfWeek.forEach((day) => { 39 const daySchedule = { dayName: day, names: [] }; 40 //6回繰り返す 41 for (let j = 0; j < 6; j++) { 42 if (namesCopy.length === 0) break; 43 daySchedule.names.push(namesCopy.shift()); 44 } 45 console.assert(new Set(daySchedule.names).size === 6 || i === 1) 46 weekSchedule.days.push(daySchedule); 47 }); 48 //names6という空の配列に追加する 49 this.names6.push(weekSchedule); 50 } 51 } 52 }, 53 }, 54 mounted() { 55 for (let i = 0; i < buttonClick(); i++) { 56 } 57 }, 58 59}).mount("#app")

追記

ご解決のようですが、せっかくなので愚直に再帰するコードを提示します。

js

1 const shuffle = (src) => 2 src.reduce((a, x) => { 3 const j = Math.floor(Math.random() * (a.length + 1)); 4 [a[a.length], a[j]] = [a[j], x]; 5 return a; 6 }, []); 7 const origNames = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; 8 const num = 5; 9 const len = origNames.length / num; 10 const arr = Array.from(new Array(num), () => []); 11 12 const names = shuffle(origNames) 13 // ↓たぶん最悪 14 // const names = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15]; 15 16 const setMember = (names, arr) => { 17 [names, arr] = [structuredClone(names), structuredClone(arr)]; 18 const selection = names.shift(); 19 for (const row of arr) { 20 // その日がいっぱいなら次の日 21 if (row.length == len) continue; 22 // 重複するなら次の日 23 if (row.includes(selection)) continue; 24 row.push(selection); 25 26 // 再帰終了 27 if (names.length === 0) return [names, arr]; 28 29 // 再帰 30 const re = setMember(names, arr); 31 32 // 探索失敗なら別の枝を探して次の日 33 if (!re) { 34 row.pop(); 35 continue; 36 } 37 return re; 38 } 39 // 全ての日に置けないなら探索失敗 40 return false; 41 } 42 43 console.log('re:', setMember(names, arr)[1])

投稿2023/07/22 02:47

編集2023/07/24 01:27
Lhankor_Mhy

総合スコア37444

miffy-n👍を押しています

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

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

winterboum

2023/07/22 07:51

>シャッフル済み15人×2をくっつけて30人の配列を作る ですと 重複しなくなる のはメリットなのですが、これですと週前半後半に均等になります。 それがベターなのか、「ときには前半に固まってくれたほうが遊びに行きやすい」が有る方がベターなのか。。。 「15人×2をくっつけて30人の配列を作ってシャッフル 」だと ときには偏るが 重複対策が必要 なのが欠点。 Lhankor_Mhy 案でやって不満が有るようなら後者ですかね。
Lhankor_Mhy

2023/07/22 08:02

> これですと週前半後半に均等になります。 回答にも書きましたが、その辺りは問題なんですよねー。別解考えますか。
guest

1

JSでやりますか。楽しんでください。
いろいろなアプローチはありますが、いずれにせよトライアンドエラーが必要です。

1週間分を作るのに例えば

  1. namesの配列 を2回使って 30人分の配列を作る。 w_names
  2. それをランダムに並べ替えて頭から6人取る。もしくは ランダムに6人取り出す。
  3. その中に重複が有ったら 2に戻る
  4. 重複が無いならその6人をx日目のシフトにして、w_namesから取り除く
  5. 4日分おわるまで 2に戻る
  6. 残りが5日目だが、ここに重複があったら 1に戻って最初からやり直し。

6 で1に戻らずに一日分戻る という方法も有りますが、その場合、「4日目の場合を尽くしたから3日目に戻る」という処理が入るので jsではとても大変そうだし、また 「結局2日目にもどりました」までにエライ計算量になるので、1に戻っちゃうのが簡便
この方法で 2←→ 3のループが永遠に終わらないこともあり得るので、N回繰り返したら1に戻る とか入れておくのが良いかも

追記
2,3 を
そこから ランダムに6人取り出す。重複してたらその人は戻して他の人を取り直す
にしたほうが良いかな。これなら、重複なく6人取れなかったら1に戻る ですね。
6人目で行き詰まったら5人目探しに戻る
。。。。
2人目で行き詰まったら1人目に戻る
1人目の新しい候補なくなったら 1に戻る
もできますが、これだとまた計算量増えるかな。

投稿2023/07/21 12:40

編集2023/07/22 00:57
winterboum

総合スコア23651

miffy-n👍を押しています

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.30%

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

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

質問する

関連した質問