PHPを使うなら理解しておきたいアルゴリズム - 抽出・ソート・結合・集計

PHPでのアルゴリズムの書き方の覚書です。

説明に使用するデータ構造

アルゴリズムの説明のために、以下のような配列の配列を使います。

$countries = array();

$countries= array( name => "日本",             currency => "JPY", population => 127156000 );
$countries= array( name => "フランス",         currency => "EUR", population => 65073482 );
$countries= array( name => "スペイン",         currency => "EUR", population => 44904000 );
$countries= array( name => "ロシア",           currency => "RUB", population => 141903979 );
$countries= array( name => "ベトナム",         currency => "VND", population => 84238000 );
$countries= array( name => "カンボジア",       currency => "KHR", population => 14805000 );
$countries[]= array( name => "コートジボワール", currency => "XOF", population => 44904000 );

この配列の配列の内容は、以下のようなコードでブラウザに表示できます。

print( "<table border='1'>" );

// テーブルヘッダを出力する。
print( "<tr>" );
print( "<th>国名</th>" );
print( "<th>通貨</th>" );
print( "<th>人口</th>" );
print( "</tr>" );

for ( $i = 0; $i < count( $countries ); ++$i ) {
        $country = $countries[$i];

        // テーブルデータを出力する。
        print( "<tr>" );
        print( "<td>" . $country["name"] . "</td>" );
        print( "<td>" . $country["currency"] . "</td>" );
        print( "<td>" . $country["population"] . "</td>" );
        print( "</tr>" );
}

print( "</table>" );

このコードの実行結果は以下の通りです。

国名通貨人口
日本JPY127156000
フランスEUR65073482
スペインEUR44904000
ロシアRUB141903979
ベトナムVND84238000
カンボジアKHR14805000
コートジボワールXOF44904000

■抽出

複数のデータの中から、特定の条件を満たすデータを抽出するアルゴリズムです。
例として、商品データproductsから特定の条件を満たすデータを抽出します。

$products = array();

$products = array( product_id => "1010", product_name => "えんぴつ",   price => 80 );
$products = array( product_id => "1020", product_name => "ボールペン", price => 100 );
$products = array( product_id => "1030", product_name => "消しゴム",   price => 100 );
$products = array( product_id => "2010", product_name => "定規",       price => 140 );
$products = array( product_id => "2020", product_name => "コンパス",   price => 300 );
$products = array( product_id => "3010", product_name => "のり",       price => 200 );

商品データproductsをブラウザに表示すると、以下のようになります。

商品ID商品名価格
1010えんぴつ80
1020ボールペン100
1030消しゴム100
2010定規140
2020コンパス300
3010のり200


以下のコードは、商品データproductsから「price > 100」を満たすデータを抽出します。

// 抽出結果を格納する配列
$results = array();

// すべての商品データについて繰り返す。
for ( $i = 0; $i < count( $products ); ++$i ) {
        $product = $products[$i];

        // 商品データのpriceが100より大きいことを確認する。
        if ( $product["price"] > 100 ) {
                // 商品データを抽出結果に追加する。
                $results[]= $product;
        }
}

抽出結果は、変数resultsに格納されます。抽出結果resultをブラウザに表示すると、以下のようになります。

商品ID商品名価格
2010定規140
2020コンパス300
3010のり200

■ソート

複数のデータを、特定の条件でソートするアルゴリズムです

PHPでの配列のソート

ソートの例の前に、PHPでの配列のソートについて説明します。
配列をソートするには、usort()関数を使用します。

$array = array( 13, 43, 22, 234 );

usort( $array, "comp" );
function comp( $a, $b ) {
        // 引数1と引数2はそれぞれ配列中の値。

        // 引き算により引数の大小を比較する。
        // ・引数2の方が大きければ、戻り値は正の値: 引数2→引数1の順に並ぶ。
        // ・引数同士が一致すれば、戻り値は0: 引数1と引数2は同列。
        // ・引数1の方が大きければ、戻り値は負の値: 引数1→引数2の順に並ぶ。
        return $b - $a;
}

ソートの並び順は、usort()メソッドの引数に渡す比較関数で決めます。比較関数は通常のユーザ定義関数と同様に定義し、その比較関数の名前をusort()メソッドの引数に渡します。


比較関数には引数として配列の要素が2つずつ渡されます。引数2→引数1の順に並べたい場合は正の値を、引数1と引数2を同列にしたい場合は0を、引数1→引数2の順に並べたい場合は負の値を、比較関数の戻り値として返します。

なお、上記のコードは配列の要素を「数値の降順」にソートします。

ソートの例(五十音順)

それでは、ソートの例を挙げます。
例として、売上データsalesを特定の項目の「五十音順」にソートします。

$sales = array();

$sales = array( sales_date => "20090702" , sales_no => "00001" , product_id => "1020" , amount => 10 );
$sales = array( sales_date => "20090702" , sales_no => "00002" , product_id => "1010" , amount => 5 );
$sales = array( sales_date => "20090702" , sales_no => "00003" , product_id => "3010" , amount => 20 );
$sales = array( sales_date => "20090703" , sales_no => "00001" , product_id => "1030" , amount => 10 );
$sales = array( sales_date => "20090703" , sales_no => "00002" , product_id => "1020" , amount => 15 );
$sales = array( sales_date => "20090704" , sales_no => "00001" , product_id => "2020" , amount => 30 );
$sales[] = array( sales_date => "20090704" , sales_no => "00002" , product_id => "3010" , amount => 20 );

売上データsalesをブラウザに表示すると、以下のようになります。

売上日売上番号商品ID数量
2009070200001102010
200907020000210105
2009070200003301020
2009070300001103010
2009070300002102015
2009070400001202030
2009070400002301020


以下のコードは、売上データsalesを「五十音順でsales_noの昇順」にソートします。

usort( $sales, "comp" );
function comp( $sale1, $sale2 ) {
        // 引数1と引数2はそれぞれ売上データ。

        // 売上データからsales_noを取得する。
        $sales_no1 = $sale1["sales_no"];
        $sales_no2 = $sale2["sales_no"];

        ////////////////////////////////////////////
        // sales_noを比較する。
        ////////////////////////////////////////////

        if ( $sales_no1 === $sales_no2 ) {
                // sales_noが一致する場合: 引数1と引数2は同列。
                return 0;
        }

        if ( $sales_no1 > $sales_no2 ) {
                // 引数1のsales_noの方が大きい: 引数2→引数1の順に並べる
                return 1;
        } else {
                // 引数1のsales_noの方が小さい: 引数1→引数2の順に並べる
                return -1;
        }
}

ソート後の売上データsalesをブラウザに表示すると、以下のようになります。

売上日売上番号商品ID数量
2009070400001202030
2009070200001102010
2009070300001103010
2009070400002301020
200907020000210105
2009070300002102015
2009070200003301020

ソートの例(数値順)

例をもう一つ挙げます。商品データproductsを特定の項目の「数値順」にソートします。

$products = array();

$products= array( product_id => "2010", product_name => "定規",       price => 140 );
$products= array( product_id => "2020", product_name => "コンパス",   price => 300 );
$products= array( product_id => "3010", product_name => "のり",       price => 200 );
$products= array( product_id => "1030", product_name => "消しゴム",   price => 100 );
$products= array( product_id => "1020", product_name => "ボールペン", price => 100 );
$products= array( product_id => "1010", product_name => "えんぴつ",   price => 80 );

商品データproductsをブラウザに表示すると、以下のようになります。

商品ID商品名価格
2010定規140
2020コンパス300
3010のり200
1030消しゴム100
1020ボールペン100
1010えんぴつ80


以下のコードは、商品データproductsを「数値順でproduct_idの降順」にソートします。

usort( $products, "comp" );
function comp( $product1, $product2 ) {
        // 引数1と引数2はそれぞれ商品データ。

        // 商品データのproduct_idを取得する。
        $product_id1 = $product1["product_id"];
        $product_id2 = $product2["product_id"];

        ////////////////////////////////////////////
        // product_idを比較する。
        ////////////////////////////////////////////

        // 引き算によりproduct_idの大小を比較する。
        // ・引数2のproduct_idの方が大きければ、戻り値は正の値: 引数2→引数1の順に並ぶ。
        // ・product_idが一致すれば、戻り値は0: 引数1と引数2の同列。
        // ・引数1のproduct_idの方が大きければ、戻り値は負の値: 引数1→引数2の順に並ぶ。
        return $product_id2 - $product_id1;
}

ソート後の商品データproductsをブラウザに表示すると、以下のようになります。

商品ID商品名価格
3010のり200
2020コンパス300
2010定規140
1030消しゴム100
1020ボールペン100
1010えんぴつ80

■結合

2種類のデータを、特定の条件で結合するアルゴリズムです。
例として、商品データproductsと売上データsalesを結合します。

$products = array();

$products= array( product_id => "1010", product_name => "えんぴつ",   price => 80 );
$products= array( product_id => "1020", product_name => "ボールペン", price => 100 );
$products= array( product_id => "1030", product_name => "消しゴム",   price => 100 );
$products= array( product_id => "2010", product_name => "定規",       price => 140 );
$products= array( product_id => "2020", product_name => "コンパス",   price => 300 );
$products= array( product_id => "3010", product_name => "のり",       price => 200 );

$sales = array();

$sales= array( sales_date => "20090702", sales_no => "00001", product_id => "1020", amount => 10 );
$sales= array( sales_date => "20090702", sales_no => "00002", product_id => "1010", amount => 5 );
$sales= array( sales_date => "20090702", sales_no => "00003", product_id => "3010", amount => 20 );
$sales= array( sales_date => "20090703", sales_no => "00001", product_id => "1030", amount => 10 );
$sales= array( sales_date => "20090703", sales_no => "00002", product_id => "1020", amount => 15 );
$sales= array( sales_date => "20090704", sales_no => "00001", product_id => "2020", amount => 30 );
$sales[]= array( sales_date => "20090704", sales_no => "00002", product_id => "3010", amount => 20 );

商品データproductsをブラウザに表示すると以下のようになります。

商品ID商品名価格
1010えんぴつ80
1020ボールペン100
1030消しゴム100
2010定規140
2020コンパス300
3010のり200
また、売上データsalesをブラウザに表示すると以下のようになります。
売上日売上番号商品ID数量
2009070200001102010
200907020000210105
2009070200003301020
2009070300001103010
2009070300002102015
2009070400001202030
2009070400002301020


以下のコードは、商品データと売上データをproduct_idをキーとして結合します。

/////////////////////////////////////////////////////////////////////////////////
// ハッシュテーブルを作成し、 product_idをキーとして商品データを取得できるようにする。
/////////////////////////////////////////////////////////////////////////////////

// ハッシュテーブル
$hash_table = array();

// すべての商品データについて繰り返す。
for ( $i = 0; $i < count( $products ); ++$i ) {
        $product = $products[$i];

        // 商品データのproduct_idを取得する。
        $key = $product["product_id"];

        // product_idをキーとして、商品データをハッシュテーブルに格納する。
        $hash_table[$key] = $product;
}

/////////////////////////////////////////////////////////////////////////////////
// 売上データと商品データを結合する。
/////////////////////////////////////////////////////////////////////////////////

// すべての売上データについて繰り返す。
for ( $i = 0; $i < count( $sales ); ++$i ) {
        $sale = $sales[$i];

        // 売上データのproduct_idを取得する。
        $product_id = $sale["product_id"];

        // product_idをキーとして、商品データをハッシュテーブルから取得する。
        $product = $hash_table[$product_id];
        if ( $product !== undefined ) {
                // 商品データを取得できた場合:

                // 商品データの値を売上データに格納する。
                $sale["product_name"] = $product["product_name"];
                $sale["price"] = $product["price"];

                $sales[$i] = $sale;
        }
}

結合後の売上データsalsesをブラウザに表示すると、以下のようになります。

売上日売上番号商品ID商品名数量価格
20090702000011020ボールペン10100
20090702000021010えんぴつ580
20090702000033010のり20200
20090703000011030消しゴム10100
20090703000021020ボールペン15100
20090704000012020コンパス30300
20090704000023010のり20200

■集計

複数のデータを特定の条件で集計するアルゴリズムです。
例として、売上データsalesを集計します。

$sales = array();

$sales= array( sales_date => "20090702", sales_no => "00001", product_name => "ボールペン", price => 100, amount => 10 );
$sales= array( sales_date => "20090702", sales_no => "00002", product_name => "えんぴつ",   price => 80,  amount => 5 );
$sales= array( sales_date => "20090702", sales_no => "00003", product_name => "のり",       price => 200, amount => 20 );
$sales= array( sales_date => "20090703", sales_no => "00001", product_name => "消しゴム",   price => 100, amount => 10 );
$sales= array( sales_date => "20090703", sales_no => "00002", product_name => "ボールペン", price => 100, amount => 15 );
$sales= array( sales_date => "20090704", sales_no => "00001", product_name => "コンパス",   price => 300, amount => 30 );
$sales[]= array( sales_date => "20090704", sales_no => "00002", product_name => "のり",       price => 200, amount => 20 );

売上データsalesをブラウザに表示すると以下のようになります。

売上日売上番号商品名価格数量
2009070200001ボールペン10010
2009070200002えんぴつ805
2009070200003のり20020
2009070300001消しゴム10010
2009070300002ボールペン10015
2009070400001コンパス30030
2009070400002のり20020


以下のコードは、salesを集計してsales_date毎の合計数量と合計売上高を求めます。

///////////////////////////////////////////////////////
// 集計結果の初期化
///////////////////////////////////////////////////////

// 集計結果を一時的に格納するマップ配列
$resultsTmp = array();

// すべての売上データについて繰り返す。
for ( $i = 0; $i < count( $sales ); ++$i ) {
        $sale = $sales[$i];

        // 売上データのsales_dateを取得する。
        $sales_date = $sale["sales_date"];

        if ( ! array_key_exists( $sales_date, $resultsTmp ) ) {
                // オブジェクトにキーsales_dateが含まれていない:

                // 集計結果の初期値を生成する。
                $result = array( "sales_date" => $sales_date, "amount" => 0, "total" => 0 );

                // 生成した初期値をキーsales_dateに関連付けてオブジェクトに格納する。
                $resultsTmp[$sales_date] = $result;
        }
}

///////////////////////////////////////////////////////
// salesを集計
///////////////////////////////////////////////////////

// すべての売上データについて繰り返す。
for ( $i = 0; $i < count( $sales ); ++$i ) {
        $sale = $sales[$i];

        // 売上データのsales_dateを取得する。
        $sales_date = $sale["sales_date"];

        // キーsales_dateに関連付く集計結果をオブジェクトから取得する。
        $result = $resultsTmp[$sales_date];

        // 売上データのamountを集計結果に足し合わせる。
        $result["amount"] += $sale["amount"];
        // 売上データのpriceとamountとを掛けた値を集計結果に足し合わせる。
        $result["total"] += $sale["price"] * $sale["amount"];

        $resultsTmp[$sales_date] = $result;
}

///////////////////////////////////////////////////////
// 集計結果を通常の配列に移し替え
///////////////////////////////////////////////////////

$results = array();

// 一時オブジェクトの全てのデータについて繰り返す。
foreach ( $resultsTmp as $result ) {
        // データを配列に追加する
        $results[]= $result;
}

集計結果は変数resultsに格納されます。集計結果resultsをブラウザに表示すると以下のようになります。

売上日合計数量合計売上高
20090702355400
20090703252500
200907045013000