独学エンジニアのクイズにて、hit&blowの集計プログラムを作る課題があったため備忘録も兼ねて記事を記載しました。
※実際の模範解答とは異なります。
1.前提条件
2.要件定義
3.設計/コード内容
4.実行結果
前提条件
作成/学習にあたりDocker、PHPはVer 8.1.1を使用しております。
著作権の観点から、一部要件内容について改変しております。
実際のクイズではPHPUnitを使用することが要求されていますがQiitaではPHPUnitは考慮せずコードを記載します。
要件定義
輝明と嘉男はHIT&BLOWで遊んでいます。ルールは以下の通りです。
- 出題者は重複した数を含まない4桁の数を決める(4442、2233などはNG)
- 回答者は4桁の数を予想する
- 出題者は解答者の予想を判定する。数と桁の療法が同じならヒット、数だけが同じで桁が異なればブローと呼ぶ。例えば正解が5678で回答が2687なら「1ヒット、2ブロー」となる
- 2、3を繰り返し、4桁の数が完全に同じになるまでの回数で勝負を決める
輝明と嘉男は勝負をしているうちに計算するのが面倒となったため、ヒット・ブロー数を数えるプログラムを作成することになりました。
そのプログラムを作成します。
入出力例:
ケース①
入力:出題者:1234 回答者:1234
出力:4ヒット、0ブロー
ケース②
入力:出題者:1234 回答者:6784
出力:1ヒット、0ブロー
ケース③
入力:出題者:1234 回答者:3478
出力:0ヒット、2ブロー
ケース④
入力:出題者:1234 回答者:9865
出力:0ヒット、0ブロー
設計/コード内容
入出力方法
実行については、以下のように第一引数に出題者の数字、第二引数に回答者の数字を入れます。
出力についてはvar_dumpで実施します(なお、実際のクイズではPHPUnitを使用しました)
var_dump(judge(1234, 1234));
// [0]=>4 [1]=>1 と出力されるイメージです
judge内の入出力ロジック
まず、引数は整数の形で受け取ります。最終的に配列で返せるようにします。
function judge(int $questioner, int $answerer): array
{
return [$hitNumber, $finalBlowNumber];
}
judge内の処理
- 出題者の数字を1つずつ判定できるよう、この段階で配列に分割します。
関数としては、str_splitを使用します。
※str_splitの参考ページ
https://www.php.net/manual/ja/function.str-split.php - その後、分割した配列と回答者の数字をhit処理用、blow処理用の関数に入れています。
※最後の引き算については、後述します。
function judge(int $questioner, int $answerer): array
{
$questionerSplit = str_split($questioner, 1);
$hitNumber = HitNumber($questionerSplit, $answerer);
$blowNumber = BlowNumber($questionerSplit, $answerer);
$finalBlowNumber = $blowNumber - $hitNumber;
return [$hitNumber, $finalBlowNumber];
}
HitNumber関数
- 初期値として「result = 0」を設定します。
- HitNumber関数では、answererについても位置の整合性を確認するため、str_splitを使用します。
- foreachを使用し、その中でarray_shiftを用いて出題者と回答者の数値が一致するか順に確認します。
- 一致した場合、resultの数値をカウントアップし、配列全てが確認完了したらreturnで結果を返す
※参考ページ
foreach
https://www.php.net/manual/ja/control-structures.foreach.php
array_shift
https://www.php.net/manual/ja/function.array-shift
function HitNumber(array $questionerSplit, int $answerer): int
{
$result = 0;
$answererSplit = str_split($answerer, 1);
foreach ($questionerSplit as $qSplit) {
$answererValue = array_shift($answererSplit);
if ($qSplit === $answererValue) {
$result++;
}
}
return $result;
}
BlowNumber関数
- 初期値として「result = 0」を設定します。
- foreachで回答者の数値内に出題者の入力した数値があるか確認する。その際に文字列内に何文字含まれるかカウントするsubstr_count関数を使用する
- mb_substr_countでカウントアップした数値をreturnし、返す
※参考ページ
mb_substr_count
https://www.php.net/manual/ja/function.mb-substr-count.php
function BlowNumber(array $questionerSplit, int $answerer): int
{
$result = 0;
foreach ($questionerSplit as $qSplit) {
$result += mb_substr_count($answerer, $qSplit);
}
return $result;
}
最終的なコード
- 引き算について、関数上のreturnではhit数、blow数が独立して計算されるため、hit数が優先されるように引き算で出力しています。
引き算を実行しない場合、例えば以下のケースとなります。
ケース
入力:出題者:1234 回答者:1234
出力:4ヒット、4ブロー
位置も値も全て計算されるため、ブロー数が正しく出力されません。
<?php
function judge(int $questioner, int $answerer): array
{
$questionerSplit = str_split($questioner, 1);
$hitNumber = HitNumber($questionerSplit, $answerer);
$blowNumber = BlowNumber($questionerSplit, $answerer);
$finalBlowNumber = $blowNumber - $hitNumber;
return [$hitNumber, $finalBlowNumber];
}
function HitNumber(array $questionerSplit, int $answerer): int
{
$result = 0;
$answererSplit = str_split($answerer, 1);
foreach ($questionerSplit as $qSplit) {
$answererValue = array_shift($answererSplit);
if ($qSplit === $answererValue) {
$result++;
}
}
return $result;
}
function BlowNumber(array $questionerSplit, int $answerer): int
{
$result = 0;
foreach ($questionerSplit as $qSplit) {
$result += mb_substr_count($answerer, $qSplit);
}
return $result;
}
var_dump(judge(1234,1234));
var_dump(judge(1234,6784));
var_dump(judge(1234,3478));
var_dump(judge(1234,9865));
実行結果
iTermで確認した結果、以下結果が出力されました。
array(2) {
[0]=>
int(4)
[1]=>
int(0)
}
array(2) {
[0]=>
int(1)
[1]=>
int(0)
}
array(2) {
[0]=>
int(0)
[1]=>
int(2)
}
array(2) {
[0]=>
int(0)
[1]=>
int(0)
}