Заимствование рекурсивной идеи, используемой при определении функции 10000 * для списков в Haskell, это будет рекурсивный подход:
def unique(lst):
return [] if lst==[] else [lst[0]] + unique(filter(lambda x: x!= lst[0], lst[1:]))
например:.
In [118]: unique([1,5,1,1,4,3,4])
Out[118]: [1, 5, 4, 3]
Я попробовал это для растущих размеров данных и увидел сублинейную сложность времени (не является окончательной, но предполагает, что это должно быть хорошо для нормальных данных).
In [122]: %timeit unique(np.random.randint(5, size=(1)))
10000 loops, best of 3: 25.3 us per loop
In [123]: %timeit unique(np.random.randint(5, size=(10)))
10000 loops, best of 3: 42.9 us per loop
In [124]: %timeit unique(np.random.randint(5, size=(100)))
10000 loops, best of 3: 132 us per loop
In [125]: %timeit unique(np.random.randint(5, size=(1000)))
1000 loops, best of 3: 1.05 ms per loop
In [126]: %timeit unique(np.random.randint(5, size=(10000)))
100 loops, best of 3: 11 ms per loop
Мне также кажется интересным, что это может быть легко обобщено другими операциями. Как это:
import operator
def unique(lst, cmp_op=operator.ne):
return [] if lst==[] else [lst[0]] + unique(filter(lambda x: cmp_op(x, lst[0]), lst[1:]), cmp_op)
Например, вы можете передать функцию, которая использует понятие округления до того же целого числа, как если бы оно было «равенством» в целях уникальности, например:
def test_round(x,y):
return round(x) != round(y)
тогда уникальный (some_list, test_round) предоставил бы уникальные элементы списка, где уникальность больше не означала традиционное равенство (что подразумевается при использовании любого вида подхода на основе множеств или основанного на дикт-ключах к этой проблеме), но вместо этого означает, что для каждого возможного целого числа K, к которому элементы могут округляться, берется только первый элемент, округляемый до K, например:
In [6]: unique([1.2, 5, 1.9, 1.1, 4.2, 3, 4.8], test_round)
Out[6]: [1.2, 5, 1.9, 4.2, 3]