php(別ファイルで書かれたリストから必要な要素を抜き出す)

ichikawaです。phpについて書きます。

今日のお題は、国語、算数、英語のテストをした各都道府県の受験者100人の書かれたリストから、東京都の受験者の算数の得点の平均点を出す、というものです。(実際のデータではなく、オリジナルです)

分かりやすいコードで書いてみよう、というもの。
そこで今回使う関数は、

  • file
  • explode

です。

file
file(’読み込みたいファイル名’)と指定することで、そのファイルの内容を1行ごと、配列として返します。
explode
explode(’,’ , $指定する変数名)とすることで、「,(カンマ)」区切りで、指定の変数の文字列を分割し、配列にして返します。説明が下手です。ごめんなさい。例えば以下の例ですと



$list = ("りんご,ばなな,メロン,すいか");

$fluits = explode(',' , $list);

/*
$fluits[0] => "りんご"
       [1] => "ばなな"
       [2] => "メロン"
       [3] => "すいか"
*/

$fluitsにはこのような配列で返ってきます。

さて、これらを使ってTRYです。受験者のリストが入っているファイル「受験者リスト.csv」

受験番号,都道府県,国語,算数,英語
1,京都府,77,72,66
2,東京都,95,68,94
3,神奈川県,79,93,74
4,大阪府,73,100,87
5,東京都,77,77,72
6,埼玉県,87,88,71
7,埼玉県,82,99,87
8,大阪府,70,65,78
9,大阪府,78,85,90
10,千葉県,93,84,82
11,千葉県,76,97,98
12,兵庫県,96,84,91
13,千葉県,61,62,92
14,兵庫県,90,63,67
15,千葉県,62,94,96
16,千葉県,100,73,61
17,千葉県,99,92,89
18,兵庫県,75,70,63
19,兵庫県,67,60,80
20,東京都,85,70,76
21,大阪府,73,68,75
22,東京都,72,82,75
23,千葉県,75,71,88
24,千葉県,85,89,91
25,埼玉県,81,79,74
26,京都府,90,77,87
27,京都府,78,66,60
28,東京都,76,76,89
29,兵庫県,85,63,93
30,京都府,85,68,71
31,東京都,79,99,74
32,東京都,88,65,87
33,神奈川県,85,60,64
34,千葉県,78,91,71
35,京都府,97,71,98
36,千葉県,88,86,62
37,千葉県,90,95,69
38,千葉県,63,81,74
39,埼玉県,80,89,86
40,神奈川県,94,73,77
41,埼玉県,73,81,93
42,兵庫県,72,64,86
43,神奈川県,76,84,82
44,東京都,69,84,76
45,京都府,79,86,73
46,埼玉県,66,88,65
47,大阪府,76,92,94
48,神奈川県,64,70,88
49,埼玉県,91,81,68
50,東京都,85,94,71
51,東京都,77,94,64
52,大阪府,77,81,86
53,京都府,66,99,79
54,千葉県,87,84,99
55,東京都,75,92,73
56,埼玉県,62,61,97
57,兵庫県,82,64,97
58,神奈川県,99,68,67
59,千葉県,61,72,62
60,埼玉県,93,89,75
61,京都府,88,94,72
62,千葉県,78,70,77
63,兵庫県,62,91,73
64,東京都,93,69,99
65,千葉県,74,95,82
66,千葉県,62,90,89
67,東京都,61,92,83
68,兵庫県,80,98,94
69,神奈川県,92,65,82
70,神奈川県,75,100,63
71,埼玉県,90,76,83
72,埼玉県,86,81,97
73,京都府,75,79,72
74,埼玉県,68,61,82
75,神奈川県,94,64,64
76,千葉県,61,98,81
77,兵庫県,62,62,62
78,埼玉県,61,66,96
79,兵庫県,82,78,74
80,神奈川県,99,71,67
81,千葉県,90,80,92
82,京都府,82,73,68
83,千葉県,78,72,88
84,埼玉県,69,69,72
85,千葉県,72,75,90
86,千葉県,81,86,65
87,東京都,63,80,71
88,東京都,91,78,77
89,埼玉県,99,69,78
90,埼玉県,82,86,95
91,京都府,98,83,79
92,神奈川県,93,92,79
93,東京都,66,68,79
94,大阪府,94,85,92
95,京都府,64,62,60
96,京都府,80,77,74
97,埼玉県,86,93,99
98,神奈川県,78,93,68
99,千葉県,76,87,83
100,神奈川県,78,61,73

長いですね。これらの文を list関数 でとりあえず分けてみましょう。



$lines = file('受験者リスト.csv');

/*
$lines[0] => 受験番号,都道府県,国語,算数,英語
        [1] => 1,京都府,77,72,66
このようになっている
*/

次に $line を explode関数 で情報を分けます



for ($i = 0;$i < count($lines);$i ++){
$list[$i] = explode("," , $lines[$i]);
}

$listに細かい分割ができました。
あとは都道府県を定義するキー[1]が東京であるものだけ、算数を定義するキー[3]の値を取り出し、平均を出せば完了です。

そうしてできた私のコードがこれです。



<?php
//ファイルから読み込む
$lines = file('coding.csv');
//分割するために配列にする
for ($i = 0;$i < count($lines);$i ++){
$list[$i] = explode("," , $lines[$i]);
}

/*
$list配列のキーの定義
  [0]受験番号
  [1]都道府県
  [2]国語
  [3]算数
  [4]英語
*/

//東京都の人の算数の点を抽出
for ($i = 0;$i < count($list);$i ++){
  if ($list[$i][1] == "東京都"){
    $Tokyo[] = $list[$i][3];
  }
}

$total = 0;

//算数の総合得点を計算
foreach ($Tokyo as $point){
  $total += $point;
}

//平均点
echo "平均点は".($total / count($Tokyo))."点ですn";

?>

どうですか。かなり見にくいですね。働いてくれますが、読みづらいですね。
制限時間が短かったのもあって、とりあえず動くことを優先して書きました。
しかしもう少し早く書けるようになりたいです。

問題点

  • 配列の使い過ぎ。どの配列が何を示しているのか意味がとれにくい。
  • 平均点を計算と同時に一気に出している。これは一旦変数に入れてから出力したほうが読みやすい。
  • 東京都の受験者がいなかった場合の何らかの処理がない
  • array_sum使ったほうがいいね

ほかにもたくさんありそうです。問題のある部分の指摘コメントお待ちしております。
講師の先生が書いたコード例が以下です。

●indexファイル

<?php
require_once 'functions.php';
$list = load();
$result = average($list);
?>

<html>
<body>
<p>東京都の算数の平均点は <?php echo $result; ?> です。</p>
</body>
</html>

●関数ファイル


<?phpfunction load()
{
    return file('result.csv');
}

function average($lines)
{
   $total = 0;
   $count = 0;
   $skip = 1;

   foreach ($lines as $line) {
     if ($skip == 1) {
            $skip = 0;
            continue;
     }

     $parts = explode(',', $line);
     if (!isset($parts[1]) || !isset($parts[3])) {
            continue;
     }

     $pref = $parts[1];
     $math = $parts[3];
     if ($pref != '東京都') {
            continue;
     }
     $total += $math;
     $count++;
   }
   if ($count > 0) {
        return $total / $count;
   } else {
        return 0;
   }
}
?>

このような感じです。これをたった数分で作っていらしたので驚きです。
感想は、continue を使うことで「東京都であるかないか」の部分でフィルターをかけているのはもちろんですが、いろいろな面でエラーを想定して書いているのがよく分かります。
条件に合う部分だけが最終的に下位に行く流れが見やすいです。
インデントがきれいです。
私との大きな違いは、全体の流れを関数として分けているかいないかの点です。
index ファイルには結果を表示する程度しか書かれていません。
こうしたほうが理解しやすいのだなと、自分のと比べることで実感しました。

—-
(2008/06/10 18:37 hirafuji追記)
動画をご覧下さい〜!

5 件のコメント so far »

  1. by takahashi, on 06.09.08 @ 7:37 PM

     

    今回の場合、私とichikawa君のコードのどっちが見やすいかというと・・・微妙かもですね。
    ささっと適当に書いたコードなので、正直いけてないです。

    まぁ、私の戦略は複雑なデータ構造は使わなくてもいいんだよ程度に
    読んでみてください。

    さて、次回は今回みんなが書いたコードをベースに拡張していくので
    自分が書いたコードがどれくらい柔軟かが試されるかもです。

  2. by ゆどうふ, on 06.10.08 @ 2:09 PM

     

    自分のことを棚に上げて突っ込みいれるひどいとうふが来ましたよ(`・ω・´)

    http://jp.php.net/file
    > ファイルを配列に入れて返します。 配列の各要素はファイルの各行に対応します。改行記号はついたままとなります。

    データが壊れていない事が保障されているとしても、file関数使うならexplodeする前でも後でもいいからtrim的なことをした方がいいと思います。csvをfile関数で扱うなら必須かも。

    最後にnl2brなんてかましたらウホッって感じになっちゃいます。
    trimとかnl2brとか教わってない場合は華麗にスルー。

    あっ、石とか投げないでっ><

  3. by ゆどうふ, on 06.10.08 @ 2:31 PM

     

    つきみやさんに講師のコードも質問していいって言われたので、気になる点を聞いてみます><

    > $parts = explode(’,', $line);
    > if (!isset($parts[1]) || !isset($parts[3])) {

    ここはissetの目的はなんだろう。。。と気になります。

    1.explode関数そのものを信頼していないのか?

    とも思いましたが、これは、そもそもキーの1とか3とかを見ている時点で、explodeを信頼するしないという次元ではないはず。。。

    2.データ数を信頼していないのか?

    データが4分割されない!という可能性を見ているのかな。。。と思いましたが、その場合はcount()を使うべきだし、1と3だけissetで見る意図が見えませんでした。

    3.explodeかissetを勘違いしてるのか?

    explodeでCSVをパースする場合、空データ部分はnullではなく空白文字列になります。
    また、issetは(最近はてブでも挙がっていましたが)、nullもしくは未定義を判定します。
    そのため、explodeした配列に対して1か3のデータをissetしても、配列要素が4つ存在することが保証されているとしたら、どちらも必ずtrueになるはずです。

    # 僕がexplodeの要素がnullになるパターンを知らない/見落としている可能性は否めませんが。

    個人的には、パターン3の可能性を色濃く感じているのですが、どうなのでしょう。emptyは使えないので
    !(isset($hoge) && $hoge === ”)
    とするところなのかなぁ?

  4. by takahashi, on 06.11.08 @ 1:59 PM

     

    ゆどうふさん、コメントありがとうございます。

    > > $parts = explode(’,’, $line);
    > > if (!isset($parts[1]) || !isset($parts[3])) {
    >
    > ここはissetの目的はなんだろう。。。と気になります。
    >
    その行をデリミタで分割した結果4つ結果がない場合を想定してます。
    確かにcountを使う方が正しいアプローチですね。

    > $pref = $parts[1];
    > $math = $parts[3];
    >
    上記のように利用する前に存在チェックをしないと気が済まないというのが
    結論です。Notice抑制コードってことですね。

    #本当はPHP4.3以前のように存在しない要素を指したときに
    #なにもいわずにnullを返してくれた方がLLらしいと思うんですよね・・・
    #そうであればこんなことをチェックしないですむのに。

  5. by スタートアップ研修記 » php ファイル管理と実習, on 06.18.08 @ 5:39 PM

     

    [...] uchiumiです。 今日のPHPは、ファイルの置き場所についてや、前回のソースを拡張していったりしました。 [...]

Comment RSS · TrackBack URI

コメントをどうぞ

名前: (Required)

eMail: (Required)

Website:

Comment:

Spam Protection by WP-SpamFree