takeやdrop

k.inabaさんのheadやdropPythonでどう書けるか検討してみた。

from itertools import dropwhile, takewhile
# Pythonで                                   # Rubyの妄想で                  # 結果
[1,2,3,4,5][0]                               # [1,2,3,4,5].head              1
[1,2,3,4,5][:1]                              # [1,2,3,4,5].head(1)           [1]
[1,2,3,4,5][:2]                              # [1,2,3,4,5].head(2)           [1, 2]
list(takewhile(lambda x: x<3, [1,2,3,4,5]))  # [1,2,3,5,5].head{|x|x<3}      [1, 2]
[1,2,3,4,5][1:]                              # [1,2,3,4,5].drophead          [2, 3, 4, 5]
[1,2,3,4,5][1:]                              # [1,2,3,4,5].drophead(1)       [2, 3, 4, 5]
[1,2,3,4,5][2:]                              # [1,2,3,4,5].drophead(2)       [3, 4, 5]
list(dropwhile(lambda x: x<3, [1,2,3,4,5]))  # [1,2,3,4,5].drophead{|x|x<3}  [3, 4, 5]

[1,2,3,4,5][-1]                              # [1,2,3,4,5].tail              5
[1,2,3,4,5][-1:]                             # [1,2,3,4,5].tail(1)           [5]
[1,2,3,4,5][-2:]                             # [1,2,3,4,5].tail(2)           [4, 5]
sorted(takewhile(lambda x: x>3,              # [1,2,3,4,5].tail{|x|>3}       [4, 5]
  reversed([1,2,3,4,5])),
  key=[1,2,3,4,5].index)                     
[1,2,3,4,5][:-1]                             # [1,2,3,4,5].droptail          [1, 2, 3, 4]
[1,2,3,4,5][:-1]                             # [1,2,3,4,5].droptail(1)       [1, 2, 3, 4]
[1,2,3,4,5][:-2]                             # [1,2,3,4,5].droptail(2)       [1, 2, 3]
sorted(dropwhile(lambda x: x>3,              # [1,2,3,4,5].droptail{|x|x>3}  [1, 2, 3]
  reversed([1,2,3,4,5])),
  key=[1,2,3,4,5].index)
[1,2,3,4,5].pop()                            # [1,2,3,4,5].pop               5
不可能                                       # [1,2,3,4,5].pop(1)            [5]
不可能                                       # [1,2,3,4,5].pop(2)            [4, 5]
不可能                                       # [1,2,3,4,5].pop{|x|x>3}       [4, 5]

Pythonに関してまとめると、

  • リストとイテレータに分けて考えるべき
  • リストもイテレータも後ろから slice、take、drop するのは苦手 ⇒ ダイレクトに実現するメソッドがない
  • 後ろから slice、take、drop する場合
    • reversed を2回組み合わせると受け付けてくれずエラーになる ⇒ reversed()がislice、takewhile、dropwhileオブジェクトを引数に受け付けてくれない
    • リストの場合、reversed と sorted 組み合わせれば何とかなる
    • イテレータの場合、恐らく不可能
    • Haskellは知らないが、そもそも返値が元のリストの順にソートされているのがおかしいと思われる。逆順にとるのだから返値も以下のように逆順になるべき。それなら1回の手順で可能。返値を次の1手で逆順にすれば良い。
>>> from itertools import dropwhile
>>> L = xrange(1,6)
>>> list( dropwhile(lambda x: x>3, reversed(L)) )
[3, 2, 1]

Pythoniteratorに関しての追求点

  • 逆順の逆順を何とか1手で実現できないだろうか?
  • 逆順にしなくても、もっと上手に後ろからとる方法は別にあるのだろうか?


RubyPythonの読みやすさの比較として

  • sliceはPythonの方が記号的で直感的に読みやすい
  • Pythonはsliceが演算子で、take、dropが関数なので統一感がない。Rubyは全てメソッドで統一感がある
  • Pythoniteratorを受け付けるがRubyは恐らく受け付けない


LL魂でもMatzさんが言っていたがRubyは早く遅延評価版のコレクションを導入すべきだよなあ。そこらへんはPythonも組み合わせにより受け付けてくれない場合があるのでもうちょっとしっかりプロトコルを作るべき。というか実装の問題?


追記(2007/11/18):
reversed([1,2,3,4,5])よりも、[1,2,3,4,5][::-1]の方が若干簡単だが、本質的なところで違いはない。イテレータにもスライシングが使えたら便利だが難しいのだろうか…。