はじめに
独学エンジニアというサービス内で出題された、テレビの視聴時間クイズについて、アウトプットもかねてメモを残します。※著作権の関係で一部改変しています。
問題
要件定義
たかし君は、受験勉強がはかどらない事に悩んでいます。テレビの視聴をやめれば学習時間が増えるのはわかっているのですが、なかなかやめることができません。そこで、たかし君は、一日のテレビの視聴分数を記録することから始めよう考え、プログラムを書くことにしました。
テレビを見るたびにチャンネルごとの視聴分数をメモしておき、一日の終わりに記録します。テレビの合計視聴時間と、チャンネルごとの視聴分数と視聴回数を出力してください。
条件
- 同じチャンネルを複数回見た時は、それぞれ分けて記録すること。
- チャンネル:数値を指定すること。1〜12の範囲とする(1ch〜12ch)
- 視聴分数:分数を指定すること。1〜1440の範囲とする
- チャンネルごとに視聴分数と視聴回数は一つにまとめて出力すること
- 視聴時間は、時間(h)単位かつ、小数点第一まで出力すること。
インプット
チャンネル 視聴時間 チャンネル 視聴時間 チャンネル 視聴時間 …アウトプット
テレビの視聴時間合計チャンネル 視聴分数 視聴回数
チャンネル 視聴分数 視聴回数
チャンネル 視聴分数 視聴回数
インプット・アウトプット例
インプット
1 30 5 25 2 30 1 15
アウトプット
1.7
1 45 2
5 25 5
2 30 1
その他
- PHP Ver 8.1.1
- Docker使用
- PHPUnit テストコード記述 ※最後に記述
コード
お見苦しいですが、最初に私が考えたコード、次に解答に近いコードを記載します。問題を解く手順
1. データ構造を決める2. データを処理しやすい形にする
3. 合計時間の算出
4. チャンネルごとの視聴分数と視聴回数を算出
5. 表示する
自分のコード
<?php
const HOUR = 60;
const DIVIDE_LENGTH = 2;
//表示だけなので returnはなし voidを記述する
function calculateViewingTime(array $argument): void
{
# データを処理しやすい形にする
$divideIntoTwo = array_chunk($argument, DIVIDE_LENGTH);
$channelTimes = sortChannelTimes($divideIntoTwo);
# 合計時間の算出と表示
calcTotalViewingTimes($channelTimes);
# チャンネルの視聴回数と視聴分数の算出と表示
calcTotalChannelViewingTimes($channelTimes);
}
function sortChannelTimes(array $divideIntoTwo): array
{
$channelTimes = [];
foreach ($divideIntoTwo as $element) {
if (array_key_exists($element[0], $channelTimes)) {
$channelTimes[$element[0]][] = $element[1];
break;
}
$channelTimes[$element[0]][] = $element[1];
}
return $channelTimes;
}
function calcTotalViewingTimes(array $channelTimes): void
{
$totalViewingTimes = 0;
foreach ($channelTimes as $times) {
$totalViewingTimes += array_sum($times);
}
echo round($totalViewingTimes / HOUR, 1) . PHP_EOL;
}
function calcTotalChannelViewingTimes(array $channelTimes): void
{
foreach ($channelTimes as $key => $times) {
$time = array_sum($times);
$count = count($times);
echo $key . ' ' . $time . ' ' . $count . PHP_EOL;
}
}
解説
1.データ構造を決める
はじめにデータ構造を決めます。
最終的に求めたいアウトプットは3つ。
- 全体の視聴時間の合計
- 各チャンネルごとの視聴時間の合計
- 各チャンネルごとの視聴回数
以上を踏まえて、求めやすく、見やすいデータ構造を作ります。
[
ch => [min, min],
ch => [min, min],
ch => [min, min],
ch => [min, min],
...
]
こんな感じで行きます。
2.データを処理しやすい形にする。
1で決めたデータの形に変更していきます。
//マジックナンバーを防ぐ
const DIVIDE_LENGTH = 2;
//array_chunkで[[ch, min], [ch, min]]に変更
$divideIntoTwo = array_chunk($argument, DIVIDE_LENGTH);
$channelTimes = sortChannelTimes($divideIntoTwo);
//[ch, min]を ch => [min, min]の形に変更
function sortChannelTimes(array $divideIntoTwo): array
{
$channelTimes = [];
foreach ($divideIntoTwo as $element) {
//同じKey(チャンネル)だった場合は、同じKeyに要素を追加するif文も記述する。
if (array_key_exists($element[0], $channelTimes)) {
$channelTimes[$element[0]][] = $element[1];
break;
}
$channelTimes[$element[0]][] = $element[1];
}
return $channelTimes;
}
この時、チャンネルが被った場合は、その要素に追加するように処理を記述します。
3.合計時間の算出
次に、合計時間を算出していきます。
今回の場合は、表示も同時に行いたいと思います。
const HOUR = 60;
//returnが必要ないので戻り値にvoidを指定する。
function calcTotalViewingTimes(array $channelTimes): void
{
$totalViewingTimes = 0;
//array_sumで各要素の合計数値を足していきます。
foreach ($channelTimes as $times) {
$totalViewingTimes += array_sum($times);
}
//最終的に求めたいのは「時間」なので「分」に変更するため60をかける。
echo round($totalViewingTimes / HOUR, 1) . PHP_EOL;
}
4.チャンネルごとの視聴分数と視聴回数を算出
チャンネル、視聴分数、視聴回数を算出・表示していきます。
calcTotalChannelViewingTimes($channelTimes);
function calcTotalChannelViewingTimes(array $channelTimes): void
{
//チャンネルはKey、視聴時間は各チャンネルの総合値、視聴回数は要素をカウント
foreach ($channelTimes as $key => $times) {
$time = array_sum($times);
$count = count($times);
echo $key . ' ' . $time . ' ' . $count . PHP_EOL;
}
}
解答に近いコード
それでは、解答コードです。
<?php
const SPLIT_LENGTH = 2;
function calcViewingTimesAndChannel(array $argument): void
{
$inputs = array_chunk($argument, SPLIT_LENGTH);
$channelGroupViewingPeriods = ChannelViewingPeriods($inputs);
display($channelGroupViewingPeriods);
}
function ChannelViewingPeriods(array $inputs): array
{
$channelGroupViewingPeriods = [];
foreach ($inputs as $input) {
$chan = $input[0];
$min = $input[1];
$mins = [$min];
if (array_key_exists($chan, $channelGroupViewingPeriods)) {
$mins = array_merge($channelGroupViewingPeriods[$chan], $mins);
}
$channelGroupViewingPeriods[$chan] = $mins;
}
return $channelGroupViewingPeriods;
}
function calculateTotalHour(array $channelGroupViewingPeriods): float
{
$viewingTimes = [];
foreach ($channelGroupViewingPeriods as $period) {
$viewingTimes = array_merge($viewingTimes, $period);
}
$totalMin = array_sum($viewingTimes);
return round($totalMin / 60, 1);
}
function display(array $channelGroupViewingPeriods): void
{
$totalHour = calculateTotalHour($channelGroupViewingPeriods);
echo $totalHour . PHP_EOL;
foreach ($channelGroupViewingPeriods as $chan => $mins) {
echo $chan . ' ' . array_sum($mins) . ' ' . count($mins) . PHP_EOL;
}
}
自分のコードをと比べて
パッと見ると、コードの視認性が全然違います。どこで、何をしているのか?がハッキリしています。
2.データを処理しやすい形にする。
//解答コード
function ChannelViewingPeriods(array $inputs): array
{
$channelGroupViewingPeriods = [];
foreach ($inputs as $input) {
$chan = $input[0];
$min = $input[1];
$mins = [$min];
if (array_key_exists($chan, $channelGroupViewingPeriods)) {
$mins = array_merge($channelGroupViewingPeriods[$chan], $mins);
}
$channelGroupViewingPeriods[$chan] = $mins;
}
return $channelGroupViewingPeriods;
}
//自分のコード
function sortChannelTimes(array $divideIntoTwo): array
{
$channelTimes = [];
foreach ($divideIntoTwo as $element) {
if (array_key_exists($element[0], $channelTimes)) {
$channelTimes[$element[0]][] = $element[1];
break;
}
$channelTimes[$element[0]][] = $element[1];
}
return $channelTimes;
}
解答コードでは、データ(配列)を変換する前に、変数(chanやmin)に一度格納することで、関数内の処理がとても見やすいです。
5.表示する
自分のコードでは表示用の関数は作成しませんでしたが、解答コードではfunction display()という関数が作成されています。
これによって、プログラムが明確に分割されているので視認性が上がっています。
テストコード PHPUnit
use PHPUnit\Framework\TestCase;
require_once(__DIR__ . "/../フォルダ名/ファイル名.php");
class hogehoge extends TestCase
{
public function 関数名()
{
$answer = <<< EOT
1.7
1 45 2
5 25 1
2 30 1
EOT;
$this->assertSame($answer, 関数名([1, 30, 5, 25, 2, 30, 1, 15]));
}
}