はじめに
既に良記事があるのですが、
総人数n人の場合に、自分の想い通りに班分け(グループ分け)するためにはどうすればいいのか
記録のためにこの記事を書きます。
これは自分の所属するコミュニティー内で出題された特別課題です。
Laravelでやりましたが、素のPHPでもできるかと思います。
要件
・n人の班分けをする
・メンバー新規登録可(名前入力で追加)
・基準は4人1組, 余りが出たら4人の組に入れて5人組にする(人数が少ない場合は適度にちょうど良いバランスにする→6人、7人、11人がイレギュラー)
・例として20人の場合は5人×4グループではなく、4人×5グループとする
・各個人削除可、全員いっぺんに削除も可能にする(ボタン設置)
仕様を変えたい場合微調整して下さい。
記事について
・DB接続など環境構築はやってある前提で省きます
・Eloquentなどについても説明省きます
コードと解説
ルーティング
Route::get('/sub', 'GroupController@group')->name('sub.group');
Route::post('/sub/delete/{id}', 'GroupController@delete')->name('group.delete');
Route::post('/sub/all/delete', 'GroupController@all_delete')->name('all.delete');
Route::get('/sub/all/delete', 'GroupController@all_delete');
Route::post('/sub/add', 'GroupController@add')->name('group.add');
Route::post('/sub/devide', 'GroupController@devide')->name('group.devide');
ここは基本的な部分かと思いますので説明省略。
## コントローラー
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\SubUser;
class GroupController extends Controller
{
public function group()
{
$sub_users = SubUser::get();
$count = SubUser::count();
return view('sub.group', compact('sub_users', 'count'));
}
public function add(Request $request)
{
$validate_rule = [
'name' => 'required',
];
$message = [
'name.required' => '名前:必須項目です',
];
$this->validate($request, $validate_rule, $message);
$sub_user = new SubUser;
$sub_user->name = $request->name;
$sub_user->save();
return redirect()->route('sub.group')->with('flash_message', '追加しました');
}
public function delete($id)
{
$sub_user = SubUser::find($id);
$sub_user->delete();
return redirect()->route('sub.group')->with('flash_message', '消去しました');
}
public function all_delete()
{
SubUser::query()->delete();
return redirect()->route('sub.group')->with('flash_message', '全員消去しました');
}
public function devide()
{
$count = SubUser::count();
$sub_users = SubUser::pluck('name')->toArray();
shuffle($sub_users);
$division = floor($count / 4);
$remainder = $count % 4;
$offset = 0;
$groups = array();
if ($count <= 5) {
$groups[] = $sub_users;
} elseif ($count == 6) {
$groups = array_chunk($sub_users, 3);
} elseif ($count == 7 || $count == 11) {
$groups = array_chunk($sub_users, 4);
} else {
for ($i = 0; $i < $division; $i++) {
if (empty($remainder)) {
$length = 4;
} else {
$length = 5;
//余りを0になるまで減らす
$remainder--;
}
$groups[] = array_slice($sub_users, $offset, $length);
$offset += $length;
}
}
return view('sub.devide', compact('groups'));
}
}
解説
少し区切って解説していきます。
public function group()
{
$sub_users = SubUser::get();
$count = SubUser::count();
return view('sub.group', compact('sub_users', 'count'));
}
DBのsub_usersテーブルからデータを全て取得。
(usersは既に登録済みだったので。membersとかでもいいかもしれません)
総人数も知りたいので、$countで取得し、それをviewに渡します。
public function add(Request $request)
{
$validate_rule = [
'name' => 'required',
];
$message = [
'name.required' => '名前:必須項目です',
];
$this->validate($request, $validate_rule, $message);
$sub_user = new SubUser;
$sub_user->name = $request->name;
$sub_user->save();
return redirect()->route('sub.group')->with('flash_message', '追加しました');
}
これはメンバー新規登録の部分です。
入力がないのに登録ボタンを押された場合、何らかのアクションが起こった方がいいので
バリデーションをかけました。
フォームから入力された情報をDBに新規保存し、flash messageで追加した旨を表示します。
public function delete($id)
{
$sub_user = SubUser::find($id);
$sub_user->delete();
return redirect()->route('sub.group')->with('flash_message', '消去しました');
}
public function all_delete()
{
SubUser::query()->delete();
return redirect()->route('sub.group')->with('flash_message', '全員消去しました');
}
deleteが個別削除。そのメンバーのidから、その人だけを削除しています。
all_deleteは全員削除です。
実装するにあたり一番重要なところ
public function devide()
{
$count = SubUser::count();
$sub_users = SubUser::pluck('name')->toArray();
shuffle($sub_users);
$division = floor($count / 4);
$remainder = $count % 4;
$offset = 0;
$groups = array();
if ($count <= 5) {
$groups[] = $sub_users;
} elseif ($count == 6) {
$groups = array_chunk($sub_users, 3);
} elseif ($count == 7 || $count == 11) {
$groups = array_chunk($sub_users, 4);
} else {
for ($i = 0; $i < $division; $i++) {
if (empty($remainder)) {
$length = 4;
} else {
$length = 5;
//余りを0になるまで減らす
$remainder--;
}
$groups[] = array_slice($sub_users, $offset, $length);
$offset += $length;
}
}
return view('sub.devide', compact('groups'));
}
}
$countはメンバーの総人数です。
SubUser::pluck('name')->toArray();
shuffle($sub_users);
全員をシャッフル。
$division = floor($count / 4);
総人数を4で割り、その整数を$divisionに入れます。(基準にしたい人数で割る)
4人が何組いるかを表しています。
$remainder = $count % 4;
4で割った余りが幾つになるか調べます。(基準にしたい人数で割る)
余りの数だけ、5人組が必要ということになります。
$offset = 0;
array_slice(配列(sub_users), どこから切り出すか(offset), 要素の長さ(length));
$offsetというのはarray_sliceを使うときに、配列のどこから切り出したいか、設定するために作っています。(後で出てきます)
班分けするにあたり、一番最初は先頭から切り出したいので0にします。
$groups = array();
配列を初期化
viewに渡すデータを配列にしたい、後々配列を追加したいので、この処理をしています。
if ($count <= 5) {
$groups[] = $sub_users;
} elseif ($count == 6) {
$groups = array_chunk($sub_users, 3);
} elseif ($count == 7 || $count == 11) {
$groups = array_chunk($sub_users, 4);
総人数5人以下の時はシャッフルした配列をそのまま出力します。
6人(3,3) と 7人(4,3) と 11人(4,4,3) の時、array_chunkを使います。
array_chunk(配列(sub_users), サイズ(数字));
サイズで配列を分割、余りにはそのまま配列に残ります。
ちょうど良く割れる場合以外は、最後の部分の要素数はサイズより小さくなります。
} else {
for ($i = 0; $i < $division; $i++) {
if (empty($remainder)) {
$length = 4;
} else {
$length = 5;
//余りを0になるまで減らす
$remainder--;
}
$groups[] = array_slice($sub_users, $offset, $length);
$offset += $length;
}
}
その他の場合は全てarray_sliceを使います。
array_slice(配列(sub_users), どこから切り出すか(offset), 要素の長さ(length));
という使い方をします。
for ($i = 0; $i < $division; $i++) {
divisonは4人が何組必要か、でした。
3組必要(12〜15人)であれば、forの中身が3回繰り返されるということになります。
if (empty($remainder)) {
$length = 4;
} else {
$length = 5;
//余りを0になるまで減らす
$remainder--;
}
remainderは余りです。
もし余りがなければ(empty(remainder))、全組4人でいいので、lengthは4になります。
もし余りが3あるとすると、1人×3あぶれているということです。
その場合は最初の3組に一人ずつ足したい、最初の3組だけ5人組にしたいということで、
ということは、余りの分だけ5人組を作る必要がある、ということで
$remainder--;
これを付け加えています。
(総人数11人の場合は、2余り3となり、余りの方が商より大きいので5人が3組作れません。)
$groups[] = array_slice($sub_users, $offset, $length);
既にある配列に作った配列を追加します。
配列に配列を追加
$offset += $length;
offsetは配列のどこから切り出すか、なので、最初の4-5人を切り出したあと、次はどこから切り出せばいいかというと、5-6人目から(ちょうど、長さ分プラスしたところから)となります。
この処理をしないと毎回配列の最初から4人か5人切り取ってくることになります。
return view('sub.devide', compact('groups'));
そして最後にviewにgroupを渡して終わりです。
#入力/削除画面、メンバー表示画面のBlade
@extends('layouts.app')
@section('sub.group')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Dashboard</div>
<br>
@if ($errors->any())
<div class ="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@if (session('flash_message'))
<div class="flash_message">
{{ session('flash_message') }}
</div>
@endif
@if ($sub_users->isEmpty())
メンバーがいません<br>
@else
人数:{{$count}}名<br>
<table border="1">
<tr>
<th>名前</th>
<th>削除</th>
</tr>
@foreach ($sub_users as $sub_user)
<tr>
<td>{{$sub_user->name}}</td>
<td>
<form action="{{ route('group.delete', ['id' => $sub_user->id])}}" method="POST">
{{ csrf_field() }}
<input type="hidden" name="id" value="{{$sub_user->id}}">
<button type="submit">
削除
</button>
</form>
</td>
@endforeach
</table><br>
<form action="{{ route('all.delete')}}" method="POST">
{{ csrf_field() }}
@foreach ($sub_users as $sub_user)
<input type="hidden" name="id" value="{{$sub_user->id}}">
@endforeach
<button type="submit" onclick="return confirm('本当に全員消去して宜しいですか?')">
全員消去
</button>
</form>
@endif
<br>
<form action="{{ route('group.add')}}" method="POST">
{{ csrf_field() }}
<label>名前:<input id="name" type="text" name="name"></label>
<button type="submit">
登録
</button>
</form><br>
<form action="{{ route('group.devide')}}" method="POST">
{{ csrf_field() }}
<button type="submit">
班分け
</button>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
ポイント
<button type="submit" onclick="return confirm('本当に全員消去して宜しいですか?')">
全員消去
</button>
全員消去前には確認するモーダルを立ち上げるようにしました。
班分け(グループ分け)結果表示のBlade
@extends('layouts.app3')
@section('sub.devide')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Dashboard</div>
<br>
@if ($errors->any())
<div class ="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@if (session('flash_message'))
<div class="flash_message">
{{ session('flash_message') }}
</div>
@endif
@foreach ($groups as $key => $group)
<?php $key++; ?>
{{$key}}班
<table border="1">
<tr>
<th>名前</th>
</tr>
@foreach ($group as $name)
<tr>
<td>{{$name}}</td>
</tr>
@endforeach
</table>
<br>
@endforeach
</div>
</div>
</div>
</div>
</div>
@endsection
ポイント
@extends('layouts.app3')
これは下記のJavaScriptを挿入するために書き換えました。
<head>
<script>
alert('結果発表!!');
</script>
</head>
上記scriptをheadタグ内に入れることで、ページを読み込む前に結果発表というアラートを出すことができます。
<?php $key++; ?>
上記コードを入れることで、キーを利用して、班の名前を1班、2班、3班と名付けることができます。
これで完成です。
#参考
PHPで配列をn等分する(そして余りをどうするか)
array PHPマニュアル
array_slice 参考記事
#おわりに
どうしてこういうコードになるのか理解できると面白いなぁと思います。
自分の理解度チェックにもなりました。