シャッフルランチやグループ分けをいい感じに効率よくやる手法

社内のチームビルディングの一環としてシャッフルランチを行っている会社さんは結構おられるのではないでしょうか？いざ運用してみると様々な問題が出てきます。今回はシャッフルランチやグループ分けを効率良く行える手法を紹介したいと思います。

#運用上の課題は？
実際にシャッフルランチを運用してみた方はわかるかもしれませんが、メンバー選出の作業や運用は大変になりがちです。職種がいい感じに混ざるようにとか、普段席が離れてて話したことのない人同士をうまく組み合わせてみよう、とか過去との重複を避けたりしながらメンバーを選出することはとても難しい問題です。運用がめんどくさくなると、シャッフルランチの開催自体が行われなくなり、本末転倒になります。

#コードで解決
前述の通り、人の手を介してグループを決めるという事は極めて大変な作業です。
こういうことは機械にやらせてしまおうと思い、シャッフルランチのようなグルーピングをうまくやってくれるような手段が無いか探していたところ`Kickstarter`社の取り組みが非常に参考になりました。こちらの運用で使われているスクリプトはOSSとして[Github](https://github.com/fredbenenson/lunch-roulette)でも公開されています。

今回はそのライブラリをベースに、シャッフルランチのグループ分けをうまくする方法を説明していきたいと思います。

#満たさなければいけない前提条件
シャッフルランチには満たさなければいけない条件が多数あります、例えば以下の様な条件です。

- ランチグループは最大限多様性に富んでいる必要がある、理想的には全員違うチーム、職種から選出されるべき
- ランチグループは過去のランチグループとの重複を避けるべき
- ランチグループの多様性を自分達で独自に定義できるようにするべき
	- チームや特技、座席配置や入社日等
- CSVやGoogleDocsからデータを入力できるようにするべき

実装にあたってはこのような複雑な条件を考慮したアルゴリズムが必要になります。

#どのようにしてグループの多様性を求めるのか?
メンバーの多様性を定量化し、最大化されたグループを選出するためには
特定のアルゴリズムによって、グループの多様性を計算できるようにします。
一般的にこのような特定のグループを任意の特徴量に応じて、多様性を最大化しながらグルーピングすることを`MDGP (Maxmimally Diverse Grouping Problem)`と呼ばれます。詳しくは[こちら](http://www.optsicom.es/mdgp/)を参考にしてみてください。

ここではグループの多様性を以下の公式で求めています。

<img width="669" alt="" src="https://qiita-image-store.s3.amazonaws.com/0/17896/0f4d838b-499d-10ad-84b5-3b3472fb726e.png">


それぞれの特徴の標準偏差を足し合わせたものをグループのスコアとし、それぞれの特徴にウェイトを掛けて各グループのスコアを算出しています。

#実行してみる
それではまず`lunch-roulette`を試してみましょう。
https://github.com/fredbenenson/lunch-roulette/blob/master/data/staff.csv

##各種設定をする
###社員データを用意する
サンプルで`data/staff.csv`にもダミーデータが用意されていますが、自分達で用意してみましょう。
左から
`user_id,name,email,start_date,table,team,specialty,lunchable,previous_lunch`
になりますが、初回は`previous_lunch`は空白で問題ありません。

<img width="927" alt="" src="https://qiita-image-store.s3.amazonaws.com/0/17896/a1dbc0e4-8105-4e96-470b-e305ce6d6f6a.png">


###チームの設定等をYAMLで行う
チームや特技などの設定は`config/mappings_and_weights.yml`に記述します。
`team_mappings`には各チームを記入し、それぞれのIDを振ります。
`specialty_mappings`は個別の特技を記入します。(エンジニアの中でもMobileとかBackend等)
`weights`にはそれぞれの特徴をどれ位の比重でスコアに反映させるかを設定します。座席テーブル番号、入社日、チーム、特技のそれぞれに傾斜をかけて微調整することができます。

```config/mappings_and_weights.yml
  team_mappings: #チーム
    board: 0
    engineer: 10
    designer: 20
    sales: 30
    sa: 40
    corporate: 50
    bizdev: 60
    iqon: 70
  specialty_mappings: #特技
    backend: 0
    frontend: 10
    mobile: 20
  weights: #比重
    table: 0.4
    days_here: 0.2
    team: 0.9
    specialty: 0.1
  min_lunch_group_size: 4 #最小グループサイズ
  min_group_score: 0.0001 #最小グループスコア
  time_decay_constant: 80.0 #前回のランチの日数をどれぐらい考慮するかの係数
```
比重の部分は最初はデフォルト値でも構いません。

##実行する
`data/staff.csv`に配置したあとは下記コマンドで実行できます
ここでは実行時にRubyのバージョンは`2.4`を使用しています。

```ruby
ruby lib/lunch_roulette.rb  data/staff.csv
```
ちなみにこのコマンドの引数は以下を取ることができます。

```
Usage: ruby lunch_roulette_generator.rb staff.csv [OPTIONS]
    -n, --min-group-size N           Minimum Lunch Group Size (default 4)
    -i, --iterations I               Number of Iterations (default 1,000)
    -m, --most-varied-sets M         Number of most varied sets to generate (default 1)
    -l, --least-varied-sets L        Number of least varied sets to generate (default 0)
    -v, --verbose                    Verbose output
    -d, --dont-write                 Don't write to files
    -s, --output-stats               Output a csv of stats for all valid generated sets
    -h, --help                       Print this help
```


実行されると`data/output`フォルダに2つのファイルが生成されます。

```
data/output/0.847_top_00a7f25cb22265ed962492c29201eaf6.csv
data/output/staff_0.847_00a7f25cb22265ed962492c29201eaf6.csv
```

それぞれ

- ベストなスコアを叩きだしたランチグループの組み合わせ
- 新しいスタッフリストのCSVファイル

の2つのファイルが生成されます。
新しく生成されたスタッフリストのCSVは次回のstaff.csvとして使います。(生成されたファイルにはprevious_lunchgroupが追記されています)

出力されたファイルの結果を見てみましょう。

```
score,2,3,4
0.10425128542433446,"","","","藤谷友美 (bizdev, Table 3), 北亜子 (iqon, Table 3), 長尾実結 (sales, Table 2), 小西遥花 (engineer - backend, Table 5)"
0.13629589177404297,"","","","奈良正和 (engineer - frontend, Table 6), 入江悦子 (engineer - mobile, Table 7), 磯野勇一 (iqon, Table 3), 中岡萌香 (board - backend, Table 5)"
0.12281160592267512,"","","","萩原信義 (sales, Table 2), 橋爪昌 (designer, Table 4), 小栗道夫 (engineer - mobile, Table 7), 堀井年紀 (iqon, Table 4)"
0.045568606700690906,"","","","唐沢麻奈 (sales, Table 2), 鎌田邦夫 (sa, Table 5), 桑田利彦 (designer, Table 4), 津村強 (engineer - backend, Table 5), 吉永>昌二 (designer, Table 4), 新村若葉 (engineer - mobile, Table 7)"
0.08447009884549869,"","","","奥野周二 (board, Table 1), 谷内行雄 (sa, Table 5), 山本愛音 (sales, Table 2), 金森利恵 (engineer - backend, Table 5)"
0.13159212361084666,"","","","河合今日子 (engineer - data, Table 8), 鈴木真歩 (iqon, Table 3), 高倉奈緒子 (engineer - frontend, Table 6), 田上真希 (sales, Table 2)"
0.11325816589686104,"","","","秋葉登美子 (corporate, Table 1), 小沼寛 (designer, Table 4), 高谷公子 (engineer - mobile, Table 7), 花井貞子 (board, Table 3)"
0.11895249975140958,"","","","篠原心菜 (designer, Table 4), 柳田武秀 (sa, Table 5), 河崎聖 (engineer - mobile, Table 7), 新倉奈々 (iqon, Table 3)"
SUM: 0.8572002779263592,0,0,0
```
(※データの氏名等はダミーです）

職種や座席などを考慮して、最大限多様性が生まれるようないい感じのグループが生成されているのがお分かりいただけますでしょうか？
一度設定するだけであとは、プログラムを実行するだけで簡単に使うことができます。運用が楽ですね。

ここで紹介したスクリプトは必要最低限のCSVファイルの入出力しか備えていませんので、手を加えてGoogle　Docsや、自動的に出力されたスタッフリストを使い回すように書き換える等、各自カスタマイズして運用に適する形にして運用するといいでしょう。

#終わりに
もちろん、シャッフルランチだけでなく、開発合宿の部屋決めやチーム分けにも利用できますし、色々と応用が可能です。
実際に僕らも開発合宿の部屋決めや、ランチグループのメンバー選出に利用してみましたが、かなりいい感じのグループが作成でき、評判も上々でした。
簡単にいい感じにチーム分けをしたいという方、とても便利ですのでぜひ使ってみてください。