エンジニア

2015.02.10

【PHP】つらい配列操作と距離を置けるLINQライクなライブラリ、Ginqを使ってみた

配列のつらさ

PHPで、以下の様な配列の詰め直しのようなコード書いたりしてると、
クラシック過ぎてあー……という気持ちになります。

<?php
// 表形式テキストが入ったファイルを開いて、ある条件を満たす行のみ抽出し、
// さらに何らかの加工処理をして別の配列に格納する。
$fileName = "some_list";
/** @var array $file */
$file          = file($fileName);
$newLinesArray = [];
foreach($file as $line) {
    /** @var string $line */
    // ... は抽出する行であるかどうかを真偽値で返す式
    if (...) {
        // 各行毎の処理
        // 処理後の行の中身を$processedLineに格納して、新しい配列に詰める。
        /** @var string $processedLine */
        array_push($newLinesArray, $processedLine);
    }
}

例えばPythonだと、リスト内包表記というのがあり、こういう処理がもうちょっと格好良く書けます。
上記の例より単純な算数っぽい例で以下に記します。

# 1から100までの連続した自然数の入ったlist
src = range(1,101)
# 7の倍数のみを抽出する
dst = [n for n in src if n % 7 is 0 ]
# [7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98] が得られる

C#など.NET Framework環境だと、LINQというさらに格好良い構文があります。
LINQが良いのは記法だけじゃないので、ちょっと本質から外れますが。
MacやLinux系 環境でも、Monoをインストールしていれば、csharpコマンドでC#のREPLが起動して、簡単に試せます。

var dst = from x in System.Linq.Enumerable.Range(1, 100)
where x % 7 == 0
select x;

ずるい!!

というわけで、PHPで配列(及びIteratorが実装されているオブジェクト)を格好良く処理できる以下のライブラリを使ってみました。
akanehara/ginq · GitHub

Ginqのインストール

ReadMe にあるとおり、各自のComposerでライブラリが管理されているPHP実行環境にて、
composer.jsonに以下の記述を追加し、

{
    "require": {
        "ginq/ginq": "~0.2.3"
    }
}

以下でインストールできます。

php composer.phar update

Ginqを使ったコード例

この記事の冒頭のPHPでの処理は、Ginqを使って以下の等価な処理に書き換えられます。
ついでにSplFileObject を使ってファイル操作をしてみます。

<?php
// 表形式テキストが入ったファイルを開いて、ある条件を満たす行のみ抽出し、
// さらに何らかの加工処理をして別の配列に格納する。
$newLinesArray =
    Ginq::from(new SplFileObject("some_list"))
        ->select(function ($line) {
            // ... は抽出する行であるかどうかを真偽値で返す式
            return (...);
        })
        ->map(function ($line) {
            // 各行毎の処理
            return $processedLine;
        })
        ->toArray();

(->がエスケープされて読みづらい……)
制御構文が減った分、だいぶ印象が違います。
LINQと一緒か?と問われると、無名関数をゴリゴリ書いている分記述量が多いですが、
O/Rマッパー位のノリで配列処理が出来ました。
このくらいの処理だと、foreach でもいいじゃん感があるのは否めませんがw

一応、上記のselectとmapの様なことは、array_filter(), array_map() を作用させても得られるのですが、

<?php
$fileName = "some_list";
/** @var array $file */
$file          = file($fileName);
$newLinesArray =
    array_map(function ($line) {
        // 各行毎の処理
        return $processedLine;
    }, array_filter($file, function ($line) {
        // ... は抽出する行であるかどうかを真偽値で返す式
        return (...);
    }));

array_filter() で抽出した配列の返り値をそのままarray_map() に作用させると、好みの問題ですが、なかなか渋い感じです。
さらに、
array_map() : 第1引数が無名関数で第2引数が配列
array_filter() : 第1引数が配列で第2引数が無名関数
と地味にインターフェースが異なっているのが個人的にはうざいです。
これ以上作用させたらだいぶだるい感じがします。

まとめ

他にもjoin,zip,take,takeWhile,memoize などのメソッドがあるので、色々試してみます。
細かなドキュメントはありませんが、挙動は test/GinqTest.php にあるテストを読めば分かりそうです。
データ構造が配列くらいしかない分(SPLをまともに使ってる人いますか?) 、
PHPは配列と仲良くすることから避けられないので、配列操作のつらさを軽減していきたいですね。
便利な構文が言語仕様に入っていってくれるといいですねー。

一覧に戻る