map_between をやってみる

面白そうなのを見つけたのでやってみた。

やってみた

とりあえずこんな感じにしてみた。Ruby の each_cons みたいなものがPythonにもあるのかも。

#!/usr/bin/env python
#-*- coding:utf8 -*-
from itertools import imap

test = [1, 2, 3, 4, 5]

def map_between(x, func):
    return list(imap(func, x, x[1:]))

print map_between(test, lambda x, y: x + y) #=> [3, 5, 7, 9]

class List(list):
    def map_between(self, func):
        return list(imap(func, self, self[1:]))

mylist = List(test)
print mylist.map_between(lambda x, y: x + y) #=> [3, 5, 7, 9]

追記(2011/01/05)

良い子の戦国大名(id:imagawa_yakata)からご指摘をいただきました。ありがとうございますm(_ _)m

1. 引数lstがスライシングに応答しないとエラーになってしまうので、それを気にする必要の無い書き方にしたい。
2. スライシングすると別のオブジェクトを作って返す。仮に巨大なリストを引数に渡したときにそのリストの全長-1の大きさのリストを複製するのは(大したコストじゃないんだろうけど)嫌だ。

出典: リストの隣接要素を次々に処理する - 今川館

1. に関しては、組み込みのmap関数もシーケンス型しかサポートしてない気がするので、まースライシングできんかったらエラーでもいいかもなぁと個人的には思いました。
2. に関してはあまりよろしくないですね。確かに巨大なリストを渡した時に id:imagawa_yakataさんの方が圧倒的に良い感じですね。ジェネレータが返ってくるので、結果リストの最初の二つしか利用しませんとかいうケースの場合にも余計な処理をしないので嬉しい感じ。

という事を考えてたら、リスト前提で考えるんだったら、imap使う必要ないんじゃね?と思ったので書いてみた。

#!/usr/bin/env python
#-*- coding:utf8 -*-

test = [1, 2, 3, 4, 5]

def map_between1(lst, func):
    return [func(lst[i], lst[i + 1]) for i in range(len(lst) - 1)] 

def map_between2(lst, func):
    return (func(lst[i], lst[i + 1]) for i in range(len(lst) - 1)) 

print map_between1(test, lambda x, y: x + y) #=> [3, 5, 7, 9]
print list(map_between2(test, lambda x, y: x + y)) #=> [3, 5, 7, 9]

「map_between1」はまるっとリストを返すバージョン、「map_between2」はジェネレータを返すバージョン。これなら2番目の指摘の件は大丈夫かと思ったけど、結局funcに渡す時にlst[i]とかはコピーと一緒だから大して変わらんのかな?

追記(2011/01/06)

結局の所、新しく書いた map_between1 と map_between2 は渡された引数lstに大して range(len(lst) -1) とかやってるから、コピーするのと一緒で駄目駄目だった。ベンチマークぽい事もやってみたので、そのうち書こう

余談

はてなブログってシンタックスハイライトされなくなったの?