all()とany()は意外と使える子かもしれない。

組み込み関数の中の使えない子

pythonの組み込み関数の中に以下の3つがあります。

  • reduce()
  • all()
  • any()

これらの3つを組み込み関数のわりに使いどころが少ない。使えない子たちだと認識してました。

例えばall()、any()は、各シーケンス*1内の真偽値について、名前から推測できなそうな処理を行い、結果を真偽値で返すのですが

all([True,True,True]) # => True
all([True,True,False]) # => False
any([True,True,False]) # => True
any([False,False]) # => False

比較関数を引数として渡すことができません。sortedやsortなどの関数は、比較関数を引数として取れるのに不思議なものです。リスト内の全ての要素に対して評価を与えてその結果を真偽値として欲しい場合、たいてい比較関数も渡せるようにした形で使いたいのです*2

しかしこれができないany(),all()ははっきり言って使えない子だと思ってました。
(reduceはpythonのlambdaがとても貧弱なので省略)

rubyではany(),all()は比較関数を取ることができる。

よくpythonと比較されることが多いrubyにもany(),all()に対応するメソッドは存在していますが、こちらは比較関数(ブロックを)取ることができます。

 [1,2,3].any?{|x| x % 2 == 0} # => true
 [1,3,5].any?{|x| x % 2 == 0} # => false
 [1,3,5].all?{|x| x % 2 == 0} # => false
 [2,4,6].all?{|x| x % 2 == 0} # => true

このことも相まって,自分の中でpythonのall(),any()は使えない子だという認識は強まったのでした。

勘違い。

しかし、勘違いしてました。pythonにはリスト内方表記/(ジェネレータ式)が構文糖として用意されています。例えば、リスト(配列)の各要素を2乗したいとき、普通のrubyプログラマが以下のように書くところを

(1..10).map{|x| x * x}

普通のpythonプログラマはmapを使って以下のように書かず、

map(lambda x: x  * x, range(1,11))

リスト内包表記を使い以下のように書くでしょう。*3

[i*i for i in range(1,11)]

普通のpythonプログラマであれば、mapを使わず上のような内包表記で書くことを考えます。これを元に考えてみましょう。表にしてみます。

言語 リストの各要素を2乗したいとき anyを使いたい時 allを使いたい時
ruby xs.map{...} xs.any?{...} xs.all?{...}
python [... for x in xs]

pythonにおいて内包表記を元に考えるのが自然だということを前提として受け入れて、all()などを使ってみることにしましょう。以下のように書けることが分かります。

# 全て偶数かどうか調べる。
all([x % 2 == 0 for x in [1,3,5]])
all([x % 2 == 0 for x in [2,4,6]])

これらの式を評価する再、中間状態として、内包表記を評価した結果の以下のようなリストが得られます。

[False, False, False]
[True, True, True]

これらに対してall()またはany()がかかるので結果として、rubyで行おうとした比較関数(ブロック)を取ることと同じことができてます。

また、リスト内包表記とは異なり結果のリストを生成しないジェネレータ式を使うこともできます。巨大なシークエンスに対して評価しようとするときにはこの方が良いかもしれません。

このことを使うと先のrubyの例がpythonでも以下のように書けます。

any((x % 2 == 0 for x in [1,2,3])) # => True
any((x % 2 == 0 for x in [1,3,5])) # => False
all((x % 2 == 0 for x in [1,3,5])) # => False
all((x % 2 == 0 for x in [2,4,6])) # => True

rubyでのそれと同じことができるようになりました。実はany()もall()もrubyのそれと同じだけの表現力を持っていたのです。any()やall()が使えない子という認識は誤っていました。ごめんなさい。

最後に

reduceは依然として使えない子という認識は変わって無いです。

*1:iterableなものe.g. list

*2:一番単純な例では、シーケンス内の全ての要素が偶数かどうかなど

*3:あるいはリスト内包表記を使わずforループを使って書く