アシアルブログ

アシアルの中の人が技術と想いのたけをつづるブログです

配列の奥底にある値をもとに配列をソートする

こんにちは。松田です。
書くことが思いつかなかったので今回は軽めのネタをひとつ。
php連想配列を扱っている時、配列の奥深くにある値をもと配列全体をソートしたい場合があります。
そんなときに使える方法の紹介です。

たとえばこんな配列。


<?php

$list = array(
  array('data' => array('data2' => array('value' => 111))),
  array('data' => array('data2' => array('value' => 444))),
  array('data' => array('data2' => array('value' => 333))),
  array('data' => array('data2' => array('value' => 222))),
  array('data' => array('data2' => array('value' => 555))),
);

?>

かなり極端に書いてますが、この配列を value の値でソートしたいとします。

完成後の理想型がこちら。


<?php

$list = array(
  array('data' => array('data2' => array('value' => 111))),
  array('data' => array('data2' => array('value' => 222))),
  array('data' => array('data2' => array('value' => 333))),
  array('data' => array('data2' => array('value' => 444))),
  array('data' => array('data2' => array('value' => 555))),
);

?>



こんな場合は、usortとcreate_functionで次のように書きましょう。



<?php

usort($list, create_function('$a,$b', 'return $a["data"]["data2"]["value"] > $b["data"]["data2"]["value"];'));

?>

実行後 print_r($list) はこんな感じになります。



Array
(
    [0] => Array
        (
            [data] => Array
                (
                    [data2] => Array
                        (
                            [value] => 111
                        )

                )

        )

    [1] => Array
        (
            [data] => Array
                (
                    [data2] => Array
                        (
                            [value] => 222
                        )

                )

        )

    [2] => Array
        (
            [data] => Array
                (
                    [data2] => Array
                        (
                            [value] => 333
                        )

                )

        )
長いので以下省略。。


みごとvalueの順に昇順にソートされてますね。


なんとなく予想がつくかもしれませんが、降順にソートする場合は判定式の不等号を逆にしちゃいましょう。



<?php

usort($list, create_function('$a,$b', 'return $a["data"]["data2"]["value"] > $b["data"]["data2"]["value"];'));

?>


実行結果。



Array
(
    [0] => Array
        (
            [data] => Array
                (
                    [data2] => Array
                        (
                            [value] => 555
                        )

                )

        )

    [1] => Array
        (
            [data] => Array
                (
                    [data2] => Array
                        (
                            [value] => 444
                        )

                )

        )
以下略。。



php5.3以降の場合は無名関数も使えるので、下記のようにかくこともできます。



<?php

$func = function($a, $b) { 
	return $a["data"]["data2"]["value"] > $b["data"]["data2"]["value"];
};
usort($list, $func);

?>


判定処理部分をすべて文字列で書かなければいけないcreate_functionよりはこちらのほうがカッコよく書けますね。
こんなふうにusortとcreate_functionは相性がよくなかなか使えます。


・・・とここまで書いたのですが、array_multisortという関数で同じようなことができるようです。。なんてこったい\(^o^)/
お好きな方をどうぞ。。

JavaScriptの連想配列(ハッシュ)はArrayオブジェクトでも作成できる!?

こんにちは、橋本です。

今日は、JavaScript連想配列(ハッシュ)に関するお話をしたいと思います。

基本的にJavaScript連想配列を作るときには、Objectオブジェクトを使用します。
こんな感じ。



var hoge = {
  hoge: 'hoge',
  fuga: 'fuga'
};


キーと値を取るときはこんな感じ。



for (var a in hoge) {
  //キー
  alert(a);
  // 値
  alert(hoge[a]);		
}


基本的に配列のキーに文字列を設定することは出来ません。

…と思い込み、今まではこのやり方に特に疑問を感じずにやってきました。

ところが、今日とあるコードにArrayオブジェクトで連想配列を定義している記述が。
「そ、そんな馬鹿な!!ちゃんと動いているはずが…」と思ったのですが、問題なく動いているようで。

ただ、配列のキーに文字列を指定した場合、lengthプロパティには反映されず、alertで配列の中身を表示することも出来ない模様。

これはどういうことなのだろうと思い、いろいろ試してみました。



//1. キー数字のみ
var hoge = new Array();
hoge[0] = 'hoge';
hoge[1] = 'fuga';

alert(hoge.length); // 2




//2. キー文字列のみ
var hoge = new Array();
hoge['hoge'] = 'hoge';
hoge['fuga'] = 'fuga';

alert(hoge.length); // 0




//3. キー数字&文字列
var hoge = new Array();
hoge['hoge'] = 'hoge';
hoge['fuga'] = 'fuga';
hoge[0] = 'puyo';
hoge[1] = 'aaaa';

alert(hoge.length); // 2


どうやら配列のキーに数字を指定した場合のみ、配列の値として認識されるようです。

これはなぜか。

結論から言うと、配列のキーに文字列を指定した場合には、配列の要素としてではなく、Arrayオブジェクトのプロパティとして値が定義されます。

つまり、



var hoge = new Array();
hoge['fuga'] = 'hoge';


とやるのは、



var hoge = new Array();
hoge.fuga = 'hoge';


とやるのと同じということです。
Objectオブジェクトで連想配列を定義した場合と同様に、プロパティとして定義されるんですね。

ちなみに、ECMAScriptの仕様書を読むと、lengthプロパティのカウントには、内部メソッドのputメソッドを使っているようです。
(参考: Under Translation of ECMA-262 3rd Edition 15.4 Array オブジェクト (Array Objects))

次のように書かれています。

Put (P, V)

Array オブジェクトは変化した Put メソッドを用いて他の Native ECMAScript オブジェクトのために使用される。
A を Array オブジェクト、 P を文字列と想定する。
A の Put メソッドが、プロパティ P と値 V で呼出されるとき、次のステップが取られる:

1. A の CanPut method を名前 P で呼出す。
2. Result(1) が false ならば、戻る。
3. A 名前 P のプロパティを持たないならば、ステップ 7 へ。
4. P が "length" ならば、ステップ 12 へ。
5. A のプロパティ P を V に設定する。
6. ステップ 8 ヘ。
7. 名前 P のプロパティを生成し、値を V に設定し空の属性を与える。
8. P が配列の添え字でなければ、戻る。
9. ToUint32(P) が A の length プロパティ未満ならば、戻る。
10. A の length プロパティを ToUint32(P)+1 に変更 (または設定) する。
11. 戻る。
12. ToUint32(V) を算出する。
13. Result(12) が ToNumber(V) と等しくなければ、例外 RangeError を投げる。
14. A の length プロパティ の値未満であり Result(12) 未満でない各整数 k について、 A 自身が ToString(k) という名前の (継承したプロパティではない) プロパティを持つならば、そのプロパティを削除する。
15. A のプロパティ P の値を Result(12) に設定する。
16. 戻る。

ざっくり言うと、キーが数字だったらlength変更するけど、そうじゃなかったら、プロパティとして登録するだけって感じみたいですね。

普段PHPを使っていると、JavaScriptの配列でも連想配列を使用することが出来ると思い込みがち。
Arrayオブジェクトを連想配列として使用することも出来ますが、上記のとおり、lengthプロパティに反映されないなど、いろいろ弊害があるかと思います。

ちなみに、さきほど記載した「キー数字&文字列」で作成した配列をfor~in文とlengthプロパティを使用したfor文で表示すると、それぞれ違った結果になります。



var hoge = new Array();
hoge['hoge'] = 'hoge';
hoge['fuga'] = 'fuga';
hoge[0] = 'puyo';
hoge[1] = 'aaaa';

// for~in文
for (var i in hoge) {
  alert(hoge[i]); //'hoge','fuga','puyo','aaaa'が表示される
}

// lengthプロパティを使用したfor文
for (var i = 0; i < hoge.length; i++) {
  alert(hoge[i]); //'puyo','aaaa'が表示される
}


紛らわしいので、やっぱり連想配列を使うときには、Objectオブジェクトを使った方がよさげですね。