Help us understand the problem. What is going on with this article?

Laravelを使って班分け(グループ分け)してみた

はじめに

既に良記事があるのですが、
総人数n人の場合に、自分の想い通りに班分け(グループ分け)するためにはどうすればいいのか
記録のためにこの記事を書きます。
これは自分の所属するコミュニティー内で出題された特別課題です。
Laravelでやりましたが、素のPHPでもできるかと思います。

要件

・n人の班分けをする
・メンバー新規登録可(名前入力で追加)
・基準は4人1組, 余りが出たら4人の組に入れて5人組にする(人数が少ない場合は適度にちょうど良いバランスにする→6人、7人、11人がイレギュラー)
・例として20人の場合は5人×4グループではなく、4人×5グループとする
・各個人削除可、全員いっぺんに削除も可能にする(ボタン設置)
仕様を変えたい場合微調整して下さい。

記事について

・DB接続など環境構築はやってある前提で省きます
・Eloquentなどについても説明省きます

画面

こんな感じ
スクリーンショット 2019-10-22 14.23.26.png
スクリーンショット 2019-10-22 14.24.58.png
スクリーンショット 2019-10-22 14.25.32.png

コードと解説

ルーティング

web.php
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');

ここは基本的な部分かと思いますので説明省略。

 コントローラー

GroupController.php
<?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'));
    }
}

解説

少し区切って解説していきます。

GroupController.php
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に渡します。

GroupController.php
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();

メンバー全員を名前の配列にします。
スクリーンショット 2019-10-22 14.48.53.png

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

group.blade.php
@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

ポイント

group.blade.php
<button type="submit" onclick="return confirm('本当に全員消去して宜しいですか?')">
全員消去
</button>

全員消去前には確認するモーダルを立ち上げるようにしました。

班分け(グループ分け)結果表示のBlade

devide.blade.php
@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

ポイント

devide.blade.php
@extends('layouts.app3')

これは下記のJavaScriptを挿入するために書き換えました。

app3.blade.php
<head>
<script>
alert('結果発表!!');
</script>
</head>

上記scriptをheadタグ内に入れることで、ページを読み込む前に結果発表というアラートを出すことができます。

devide.blade.php
<?php $key++; ?>

上記コードを入れることで、キーを利用して、班の名前を1班、2班、3班と名付けることができます。

これで完成です。

参考

PHPで配列をn等分する(そして余りをどうするか)
array PHPマニュアル
array_slice 参考記事

おわりに

どうしてこういうコードになるのか理解できると面白いなぁと思います。
自分の理解度チェックにもなりました。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした