【JavaScript】グループ分けアプリを作って護廷十三隊を分けてみた

##はじめに
- 結構な人数で懇親会をしたい
- テーブルが別れている店を予約してしまった
- 同じ部署の人は離れて座ってほしい

こんな条件が揃った時に使うグループ分けアプリを作りました。
試しに最近はまったBLEACHに出てくる護廷十三隊をグループ分けしてみます。（まだ全巻読んでいないので初期メンバーで）

##デモ
4ステップでグループ分けしていきます
1. 氏名と部署を入れたCSVファイルを読み込む → csvの内容が下に表示される
2. グループ数を入力 ＋ [表を作成]をクリック → グループの数だけ表が作成される
3. [START]をクリック → ビンゴっぽく動く
4. [STOP]をクリック → 各グループに振り分けられる

![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533386/20070c76-4e85-5fd8-1575-63c9b7505072.gif)

##CSVを読み込んで画面に表示する
[こちらの記事](https://www.sejuku.net/blog/32532)を参考に実装しました。
うまく読み取ってくれなかったので`<body onLoad='readCsv();'>`でページ読み込み時に関数を呼び出しています。
ファイル選択ボタンを他のボタンとデザインを合わせたかったのでinput要素を`display:none`にします。

```html:index.html
<body onload='readCsv();'>
  <!-- CSV読み込みボタン -->
  <label>
    <form name='myform'>
      csvファイルを選択
      <input type='file' name='myfile' style='display:none'>
    </form>
  </label>

  <!-- CSVの内容を表示 -->
  <h5 id='num'>人数：　名</h5>
  <table class='table table-striped'>
    <thead>
      <tr>
        <th>氏名</th>
        <th>部署</th>
      </tr>
    </thead>
    <tbody id='tbody'>
    </tbody>
  </table>

</body>
```  


```javascript:shuffle.js
function readCsv(){
  document.forms.myform.myfile.addEventListener( 'change', function(e) {
    var result = e.target.files[0];
    var reader = new FileReader();
    reader.readAsText( result );
    reader.addEventListener( 'load', function() {
      var memberArray = reader.result.split('\n');
      memberArray.shift();
      var table = $('.table').children('tbody');
      for(var i = 0; i < memberArray.length; i++){
        memberArray[i] = memberArray[i].split(',');
        table.append('<tr>\
          <td>' + memberArray[i][0] + '</td>\
          <td>' + memberArray[i][1] + '</td>\
          </tr>');
      }
      document.getElementById('num').innerHTML = '人数：' + memberArray.length + '名';
    });
  });
}
```

##グループの数分の表を作る
`result-left`と`result-right`という領域を作って、そこに入力したグループの数だけ表を作ります。

```html:index.html
<input id='group' type='text' placeholder='グループ数（半角数字）'>
<button type='submit' onClick='createArea();'>表を作成</button>

<div id='result-left'></div>
<div id='result-right'></div>
```

```javascript:shuffle.js
function createArea(){
  var group = parseInt(document.getElementById('group').value);
  var row = $('#tbody').children().length;
  var perGroup = Math.ceil(row / group);

  for(var i = 0; i < group / 2; i++){
    $('#result-left').append('<ul>\
      <li>' + (2 * i + 1) +'</li>\
      </ul>');
    for(var j = 0; j < perGroup; j++){
      $('#result-left').children('ul').last().append("<li class='shuffle-area'></li>");
    }
  }

  for(var i = 0; i < Math.floor(group / 2); i++){
    $('#result-right').append('<ul>\
      <li>' + 2 * (i + 1)  +'</li>\
      </ul>');
    for(var j = 0; j < perGroup; j++){
      $('#result-right').children('ul').last().append("<li class='shuffle-area'></li>");
    }
  }
}
```

##ビンゴっぽく動かす
[こちらの記事](https://www.terakoya.work/javascript-jquery-bingo-game-build/)を参考にビンゴっぽく動かします。
`shuffle-area`というクラス（グループの数だけ表を作るときに`<li>`に設定したクラス）をもつ要素に値をランダムに入れて動かしてる感を出します。

```html:index.html
<button type='button' onClick='start();'>START</button>
```

```javascript:shuffle.js
function start(){
  var memberArray = [];
  var row = $('#tbody').children().length;
  for(var i = 0; i < row; i++){
    memberArray.push($('#tbody').find('td').eq(2 * i).text());
  }
  roulette = setInterval(function(){
    var random = Math.floor(Math.random() * memberArray.length);
    var shuffleArea = $('.shuffle-area');
    var count = shuffleArea.length;
    for(var i = 0; i < count; i++){
      shuffleArea.eq(i).text(memberArray[(random + i) % count]);
    }
  }, 10);
}
```

##グループを分けて表示する
動くものをとりあえず作りましたが正直改善の余地しかない。
「同じ部署の人は違うグループにする」という条件を満たすまでランダムにグループ分けをし続けます。グループ数ニアイコール同じ部署の人数の時にwhile文めっちゃまわります...。

```html:index.html
<button type='button' onClick='stop();'>STOP</button>
```

```javascript:shuffle.js
function stop(){
  clearInterval(roulette);
  var status = true;
  var group = parseInt(document.getElementById('group').value);
  var memberArray = [];
  var copyArray = [];
  var groupArray = new Array(group);
  var row = $('#tbody').children().length;

  for(var i = 0; i < row; i++){
    var name = $('#tbody').find('td').eq(2 * i).text();
    var department = $('#tbody').find('td').eq(2 * i + 1).text().replace(/\r?\n/g, '');
    memberArray.push([department, name]);
  }

  while(status){
    status = false;
    memberArray = shuffle(memberArray);
    copyArray = memberArray.concat();
    for(var i = 0; i < group; i++){
      groupArray[i] = copyArray.splice(0, Math.ceil(copyArray.length / (group - i)));
      groupArray[i].sort();
    }

    loop:
    for(var i = 0; i < group; i++){
      for(var j = 0; j < groupArray[i].length - 1; j++){
        if(groupArray[i][j][0] != "" && groupArray[i][j][0] == groupArray[i][j + 1][0]){
          status = true;
          break loop;
        }
      }
    }
  }

  for(var i = 0; i < groupArray.length; i++){
    for(var j = 0; j < groupArray[i].length; j++){
      $('ul').eq(i).children('.shuffle-area').eq(j).text(groupArray[i][j][1]);
    }
    if(j < $('ul').eq(i).children('.shuffle-area').length){
      $('ul').eq(i).children('.shuffle-area').eq(j).text('　');
    }
  }
}

function shuffle(array) {
  for (let i = 0; i < array.length; i++) {
    let rand = Math.floor(Math.random() * (i + 1));
    [array[i], array[rand]] = [array[rand], array[i]]
  }
  return array;
}
```

##TODO
- グループ分けのアルゴリズムを改良する
- グループ数が同じ部署の人数より少ない時にアラートを出す
- 完成したグループをcsv出力する

##GitHub
この記事ではhtmlを部分的に記載しているので全体はこちらに載せています。
https://github.com/rntkym/team-division

##おまけ
何度かグループ分けをして遊んでたら花太郎が胃痛で死にそうなグループができた
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533386/88bb9ea7-75ad-aac6-7912-8a7bd089731b.png" width=40%>