uniq()あれこれ

ふとこちらの記事を拝見して、頭に浮かんだことをメモってみる。

id:aroma_blackさんの

[y for x,y in enumerate(seq) if seq[:x+1].count(y) == 1]

や、id:cocoatomoさんの

[x for i, x in enumerate(a) if i == a.index(x)]

でも良いんだけど、あえて別解を探すと:

Python2.7/3なら

import collections
list(collections.OrderedDict((x,None) for x in seq).keys())

一番素直で速いのは

filter(lambda x,s=set():(x not in s, s.add(x))[0], seq)

かなぁ?

入力のリストの要素から、新たな一つのリストに還元すると考えれば、reduceを使う手もある。

reduce(
    lambda x, y, s=set():
        (x+[y], s.add(y))[0] if y not in s else x, 
    seq, [])

毎回新しいリストオブジェクト作って中身をコピーしてるから、O(N2)だ?気になるんだったらreduceは使えないねぇ。それともリスト内包で

[x for i, x in enumerate(seq) if x not in seq[:i]]

毎回リストオブジェクトを作るのは嫌?それなら

from itertools import repeat
[x for x, s in zip(seq, repeat(set())) if (x not in s) and (s.add(x) or True)]

かなぁ。

こっちの方が綺麗か

from itertools import repeat
[(s.add(x) or x) for x, s in zip(seq, repeat(set())) if x not in s]


でもまぁ、なんだかんだ言っても、素直に

def uniq(seq):
    seen = set()
    for i in seq:
        if i not in seen:
            yield i
            seen.add(i)

が一番。素直が一番だ。itertools.uniq() とかはあっても良い気はするけど、あんまりニーズないのかな?