そこにeachがあるから

PHP 配列を回すならforかforeachか

foreach一択。

PHPに配列と連想配列の区別がない以上、イテレーションの現場にforの居場所はないと思われる。添字に数字以外が含まれるかもしれない配列をcount()と$i++で回すのはなんかリスキーだ。

ていうかメリットとして挙げられている「ループ内でデータが直感的に参照できる」てのはむしろ冗長にしか見えない。for()の中に初期化、続行/中断条件の定義、毎回実行する構文、と3つも決まりきったことを書かなきゃいけないのも面倒。

PHPにはブロックスコープとかの概念もないので、用済みのforeach用変数がループ終わっても参照できるとかどうしようもなく不細工な面もありますが、まあそれはそれで。あとforeach((array)$var as $v){echo $v;}とかするとWarningが出なくなってだいたいの場面では便利です。

各言語でのイテレーション

目的別に分けるとだいたい以下の3パターンだと思う。

  1. 配列の各要素が欲しい
  2. 連想配列の各要素と添字がペアで欲しい
  3. 決まった回数だけ何か処理をしたい

「配列の各要素と添字がペアで欲しい」は特殊な用途だと思うのでとりあえず無視。たぶん、たまたま添字が数字だけで出来てる連想配列じゃないかと思うので、2.と同じ方法で対処できるかもしれない。

「連想配列の添字だけ欲しい」は、連想配列の添字だけを配列にして(PHPだとarray_keys()、RubyでいうHash#keys)しまえば、1.と同じってことになる。

「連想配列の各要素だけが欲しい」もまずないと思われるので無視。あったとしても添字を捨てるか、array_values()相当して配列作れば同じこと。

「配列の決まった数だけ欲しい」はそもそもイテレーションじゃない。それをいうと3.もそうじゃないか、って疑念はわきますけど、まあ、その、特例ってことで許してください。なんとなくarray_slice()とかarray_filter()して配列を部分的に抜き取るのが王道じゃないかなあ。

というわけで、以下に書いた以外にもやりかたはあると思いますが、もっとも素朴な実装例ってことでひとつ。

Ruby

eachメソッドを呼べばいい。

決まった回数の反復には[0..n]な配列を作ってeachしてもいいけど、通常はNumberクラスのメソッドを使う。


arr=[1,2,3]

# 配列
arr.each{|v|
    puts v
}

# 連想配列
hash={1=>"a",2=>"b"}
hash.each{|k,v|
    puts "#{k} => #{v}"
}

# 特定回数
0.upto(9){|n|
    puts n
}

Array/Hashクラスのeachメソッドを呼ぶ、というスタイルなのでPHPのforeachとは主客逆転してる。

僕が知ってる範囲ではいちばんシンプルな解法だと思う。

Python

Pythonは配列(list)と連想配列(dict)で若干構文が変わるのでなんか嫌だった。


# 配列
arr=[1,2,3]
for v in arr:
    print v

# 連想配列
hash={1:"a",2:"b"}
for k,v in hash.iteritems():
    print "%s => %s" % (k,v)

# 特定回数
for n in range(10):
    print n

iteritems()がどうしても気持ち悪い。

名前こそ全部forだけど、特性はPHPのforeachに近い。特定回数の実行には[0..n]な配列を作ってforするみたい。

Pythonは3日くらいしかまともに触ってないのでまずいやりかたなのかもしれない。

JavaScript

問題児。

PHPは配列と連想配列を区別しないけど、JavaScriptは連想配列とObjectオブジェクトを区別しない。連想配列はキーと値のペアではなく、オブジェクトのプロパティとその値という扱い。

// 配列
var arr=[1,2,3];
for(var i=0,len=arr.length; i<len; i++){
    alert(arr[i]);
}

// 連想配列
var hash={1:"a",2:"b"};
for(var p in hash){
    if(hash.hasOwnProperty(p)){
        alert([p," => ",hash[p]].join(""));
    }
}

// 特定回数
for(var n=0; n<10; n++){
    alert(n);
}

hashはObjectオブジェクトなのでhasOwnPropertyでチェックしないと変なメソッドや不要なプロパティまで引っかかるかもしれない。あと配列走査に原始的なforしか手段がないのでイライラ。

もちろんArray.prototype.eachを定義してイテレーションする手もある。XPathResultとかNodeListとかも同様。しかしながら、連想配列=Objectであるかぎり、Object.prototype.eachにはちょっと抵抗がある(連想配列以外も含めたあらゆるオブジェクトが実質的にeachメソッドを持つことになる)。

Prototype.jsはそのためにHashクラスをつくって、連想配列とObjectを分離しようとした。Hash#toQueryStringとか、通常のJavaScriptでは心理的になかなかなしえない拡張をしまくってる。

jQueryは巨大なDSLというか、JavaScript自体を再定義してるような感じ。jQueryを使うならJavaScriptの流儀をだいぶ忘れてjQueryの流儀に染まる必要がある。

他はあんまり詳しくないけど、だいたいこういう事情があるので生のJavaScriptを触りたい人はライブラリの使用をためらう傾向にあるんだと思います。

まとめ

なんか自分用メモになってしまった。他言語での例に照らせばforeachのほうが自然だって言いたかったはずなのに。あれー。

php
名前

ほか