はじめに
paizaの問題集にある「五目並べ」の問題で、横
縦
斜め
の値が揃っているかを調べる際、主に縦
と斜め
の判定でかなり悩んだので備忘録として残します。
追記
2021/9/22 : コメントでより簡潔なコードをご教示頂いたので、最後の項目(追記)に追加しました。
問題の内容
問題にも書いていますが、ざっくり言うとこんな感じです。
・盤面の各マスには、O
か X
か .
が書かれている。
・同じ記号が縦
か横
か斜め
に連続で5つ
並んでいれば、その記号のプレイヤーが勝者となる。
・その勝者の記号を表示させる。勝負がついていなければD
を表示。
もくじ
1. 値の取得
いつものように標準入力を取得。
取得したXXOXO
を X
X
O
X
O
というように1文字ずつ分解していく。
// 標準入力を1行ずつ取得
while($line = fgets(STDIN)) {
$tmp[] = trim($line);
}
// $tmpの中身(イメージ)
// $tmp = {
// [0] => "XXOXO"
// [1] => "OXOXX"
// [2] => "OOOOO"
// [3] => "OXOX."
// [4] => "XOXXO"
// }
// 1行の文字列をさらに分解
foreach ($tmp as $value) {
$str_split[] = mb_str_split($value);
}
// $str_splitの中身(イメージ)
// $str_split = {
// [0] => {
// [0] => "X"
// [1] => "X"
// [2] => "O"
// [3] => "X"
// [4] => "O"
// }
// [1] => {
// [0] => "O"
// [1] => "X"
// [2] => "O"
// [3] => "X"
// [4] => "X"
// }
// 以下略
2. 縦の判定
縦の判定は、以下のように実装。
// 縦の判定
for($i = 0; $i < 5; $i++) {
foreach ($str_split as $str) {
// ① 配列の行と列を入れ替える
$column[$i][] = $str[$i];
}
// ② "X"と"O"の数を数える
$columnCount[] = array_count_values($column[$i]);
if($columnCount[$i]["X"] === 5) {
echo "X";
return;
} elseif($columnCount[$i]["O"] === 5) {
echo "O";
return;
}
};
① では以下のように新しい配列$column
に同じ添字同士の値を格納していきました。
最終的に以下のように縦の列同士だった値が配列に格納される形となります。
② では上記で作成した配列の中に、X
とO
がそれぞれいくつあるのか判定しています。
この部分については、以下の左上から右下
を判定のところで触れています。
$columnCount[] = array_count_values($column[$i]);
if($columnCount[$i]["X"] === 5) {
echo "X";
return;
} elseif($columnCount[$i]["O"] === 5) {
echo "O";
return;
}
3. 斜めの判定
斜めの判定は、左上から右下
と右上から左下
の2パターンを判定しています。
// 左上から右下
for($i = 0; $i < 5; $i++) {
// ① 左上から右下の列を配列に格納
$aboveLeft[] = $str_split[$i][$i];
};
// ② "X"と"O"の数を数える
$aboveLeftCount = array_count_values($aboveLeft);
if($aboveLeftCount["X"] === 5) {
echo "X";
return;
} elseif($aboveLeftCount["O"] === 5) {
echo "O";
return;
}
// 右上から左下
for($i = 0, $j = 4; $i < 5; $i++, $j--) {
// ① 右上から左下の列を配列に格納
$aboveRight[] = $str_split[$i][$j];
};
// ② "X"と"O"の数を数える
$aboveRightCount = array_count_values($aboveRight);
if($aboveRightCount["X"] === 5) {
echo "X";
return;
} elseif($aboveRightCount["O"] === 5) {
echo "O";
return;
}
左上から右下
左上から右下へは、規則的に添字が揃っているため、以下のようにコードを書くことができます。
for($i = 0; $i < 5; $i++) {
$aboveLeft[] = $str_split[$i][$i];
};
左上から右下の列を配列に格納した後は、配列の中にX
とO
がそれぞれいくつあるかを調べます。
同じ文字が5つ揃っている = 勝者
となるため条件分岐で、5つ文字が揃っていればその記号を表示させるというようにしています。
if($aboveLeftCount["X"] === 5) {
echo "X";
return;
} elseif($aboveLeftCount["O"] === 5) {
echo "O";
return;
}
配列の中の要素を数える
上記のように配列の中にある要素がいくつあるか
を調べるにあたり、array_count_values()
を用いました。
右上から左下
右上から左下へは、片方の添字は0から4
へ増加、もう片方の添字は4から0
へ減少するという法則があります。
この法則を利用して以下のようにコードを書くことができます。
for($i = 0, $j = 4; $i < 5; $i++, $j--) {
$aboveRight[] = $str_split[$i][$j];
};
この後は、先ほどのようにX
かO
が5つあるのか条件分岐を行います。
$aboveRightCount = array_count_values($aboveRight);
if($aboveRightCount["X"] === 5) {
echo "X";
return;
} elseif($aboveRightCount["O"] === 5) {
echo "O";
return;
}
おわり
解答コード例を見てみると、上記とは違う解法であったためとても勉強になりました。
繰り返し処理をうまく使えるようにしていこう!
追記
上記のコードより簡潔な書き方を教わったため以下に記載致します。
<?php
function judge($arr) {
$check = function($str) {
$str = implode(array_unique(str_split($str)));
return (strlen($str) === 1 && $str !== '.') ? $str : false;
};
$s1 = $s2 = '';
$size = count($arr);
for($i = 0; $i < $size; $i++) {
// 横判定
$str = $check($arr[$i]);
if($str) return $str;
// 縦判定
$str = implode(array_map(function($m) use($i) { return $m[$i];}, $arr));
$str = $check($str);
if($str) return $str;
// 斜め判定用
$s1 .= $arr[$i][$i];
$s2 .= $arr[$i][$size - $i - 1];
}
// 斜め判定
$str = $check($s1);
if($str) return $str;
$str = $check($s2);
if($str) return $str;
return 'D';
}
echo judge([
'XO.X.',
'XOXXO',
'OOOOX',
'XOXX.',
'OOXOO',
]); // O
echo judge([
'O..XX',
'OOXXO',
'OOXXX',
'XX.OO',
'XOOXO',
]); // X
こちら、コメントで@3267fさんより頂いたコードとなります。
本当にありがとうございます。
縦・斜めの値を取得
私が記述したやり方では、縦
斜め
の値を一度配列に置き直し、繰り返しを用いて文字のカウントをしていました。
が、以下のコードで簡潔に書くことができます。
$s1 = $s2 = '';
$size = count($arr);
for($i = 0; $i < $size; $i++) {
// 横判定
$str = $check($arr[$i]);
if($str) return $str;
// 縦判定
$str = implode(array_map(function($m) use($i) { return $m[$i];}, $arr));
$str = $check($str);
if($str) return $str;
// 斜め判定用
$s1 .= $arr[$i][$i];
$s2 .= $arr[$i][$size - $i - 1];
}
配列に格納せずとも上記のように配列から特定の値を直接指定して、文字数をカウントすることができます。
文字のカウント
文字のカウントは以下のようにarray_unique
で文字を絞り、残った文字を判定しています。
$check = function($str) {
$str = implode(array_unique(str_split($str)));
return (strlen($str) === 1 && $str !== '.') ? $str : false;
};
簡潔なコードになり、非常に勉強になりました!
改めてお礼申し上げます。
参考