配列のグループ分け
Laravelを使って班分け(グループ分け)してみたというのを見たのですが、devide
関数ややこしくない?
PHPには配列を分割するarray_chunkという関数が最初から用意されているのですが、参照先の参照先にもあるように末尾処理が微妙です。
10個の配列を4分割すると[3個, 3個, 3個, 1個]
になってしまうのです。
そんなわけで元記事ではarray_sliceで切り出しているのですが、正直なところわかりにくいので、もっと簡単にやりましょう。
/**
* 配列を分割して返す
* @param array 元配列
* @param int 分割数
* @return array 元配列を分割したやつ
*/
function divide(array $arr, int $division):array{
$ret = [];
$cnt = 0;
foreach ($arr as $k => $v) {
$ret[$cnt++ % $division][$k] = $v;
}
return $ret;
}
$a = range(0, 9);
shuffle($a);
$ret = divide($a, 4); // [3個, 3個, 2個, 2個]
できた。
元記事ではしていなかったのですが、一応キーも保持するようにしています。
おわりに
作ってから気付いたんだけど動作違うわこれ。
array_chunk
や元記事は、分割後の配列も並んでいる順に整列します。
ABCDEFGHIJ
が並んでいたら[ABC, DEF, GH, IJ]
となります。
対してこちらは、レジに並ぶ客のように分かれます。
ABCDEFGHIJ
は[AEI, BFJ, CG, DH]
になるということです。
そんなわけで元記事をそのまま差し替えるには至りませんでした。
残念。
ここまで全部間違い
以下2019/10/24追記
ここまでリリースしたあとで突っ込まれたわけですが、元記事を読み返してみたら設問が根本的に違う。
・1組あたりの人数は4人で、グループ数は可変。
・端数が出たら5人組にする。
・人数が少ない場合は特殊処理。
いったい何をキメていたら、この設問から上のdivide()
みたいな関数を作れるんですかね??
ということで作りなおしましょう。
人数が少ないときの挙動がよくわからないので列挙しておきます。
・5人以下:[n]
・6人:[3, 3]
・7人:[4, 3]
・8人:[4, 4]
・9人:[5, 4]
・10人:[5, 5]
・11人:[4, 4, 3]
・12人:[4, 4, 4]
・13人以上:定義通り
グループあたり人数を可変にするとこのあたりの動作がよくわからなくなるので、以下では4人固定としておきます。
/**
* 配列を分割して返す
* @param array 元配列
* @return array 元配列を分割したやつ
*/
function divide(array $arr): array {
$cnt = count($arr);
// 6未満
if ($cnt < 6) {
return [$arr];
}
// 11未満
if ($cnt < 11) {
return array_chunk($arr, ceil($cnt / 2));
}
// 4の倍数と11
if (!($cnt % 4) || $cnt === 11) {
return array_chunk($arr, 4);
}
// それ以外
$ret = array_chunk($arr, 4);
$extra = array_pop($ret);
$cnt = 0;
foreach ($extra as $v) {
$ret[$cnt++][] = $v;
}
return $ret;
}
// 確認
for ($a = 1; $a < 20; $a++) {
var_dump( divide(range(1, $a)) );
}
うーん、元記事とたいして変わってない。
これならわざわざ書き換える意味がないかんじですね。
先にグループ数を求めてから配分したら多少行数が減るかな?
function divide(array $arr): array {
// 分割数
$cnt = count($arr);
if ($cnt < 6) {
$division = 1;
} elseif ($cnt < 11) {
$division = 2;
} elseif ($cnt < 12) {
$division = 3;
} else {
$division = floor($cnt / 4);
}
// 分割する
$cnt = 0;
foreach ($arr as $k => $v) {
$ret[$cnt++ % $division][$k] = $v;
}
return $ret;
}
行数は減りましたが、読みやすくなったかというとどうでしょうね。
というか11が厄介だな。
やろうと思えば$division
の算出は1行でできますが、さらにわかりにくくなるだけなので辞めましょう。
function divide(array $arr): array
{
$cnt = 0;
foreach ($arr as $k => $v) {
$ret[$cnt++ % (($c = count($arr)) < 12 ? $c < 11 ? 1 + floor($c / 6) : 3 : floor($c / 4))][$k] = $v;
}
return $ret;
}
さて、今度こそ設問に間違いはないよな?よな?