2015-04-24 3 views
3

Я ищу, чтобы запустить список идентификаторов и вернуть список всех идентификаторов, которые произошли более одного раза. Это было то, что я установил, что работает:Удалить уникальные значения из списка и сохранить только дубликаты

singles = list(ids) 
duplicates = [] 
while len(singles) > 0: 
    elem = singles.pop() 
    if elem in singles: 
     duplicates.append(elem) 

Но список идентификаторов, вероятно, получить довольно долго, и я реально не хочу время цикла определяющегося дорогой лены вызов, если я могу избежать его. (Я мог бы пойти неэлегантным путем и позвонить len один раз, а затем просто уменьшить его на каждую итерацию, но я бы предпочел избежать этого, если бы мог).

ответ

12

Умный способ сделать это, чтобы использовать структуру данных, которая позволяет легко и эффективно, как Counter:

>>> ids = [random.randrange(100) for _ in range(200)] 
>>> from collections import Counter 
>>> counts = Counter(ids) 
>>> dupids = [id for id in ids if counts[id] > 1] 

Построение Counter принимает O (N) время, в отличие от O (N log N) времени для сортировки, или O (N^2) для подсчета каждого элемента с нуля каждый раз.


В качестве примечания:

But the ids list is likely to get quite long, and I realistically don't want a while loop predicated on an expensive len call if I can avoid it.

len не дорого. Это постоянное время, и (по крайней мере, в списке встроенных типов list), это примерно так же быстро, как функция может попасть на Python, не делая ничего вообще.

Часть вашего дорогого кода вызывает в цикле elem in singles, что означает, что для каждого элемента вы должны сравнивать его с потенциально любым другим элементом, что означает квадратичное время.

+0

Я думаю, что это было бы более эффективно с помощью 'itertools.ifilter'. (я писал ответ с 'Counter' и' ifilter', но вы делаете это быстрее;)) – Kasramvd

+0

@ Kasra: Почему? Использование 'filter' /' ifilter' вместо выражения выражения expression/list означает, что вы должны обернуть тест внутри функции, что добавляет дополнительную стоимость. (Если бы вы просто ссылались на не создание списка, если нам это не нужно, тем проще сделать это, просто изменить listcomp на ген xpr, но поскольку он специально говорит, что ему нужно вернуть список, t думаю, что вы можете избежать создания списка.) – abarnert

+0

@Kasra: Первый список просто создает тестовые данные. Вам нужно получить входные данные откуда-то. – abarnert

5

Вы могли бы сделать так,

>>> ids = [1,2,3,2,3,5] 
>>> set(i for i in ids if ids.count(i) > 1) 
{2, 3} 
+0

Просто небольшое дополнение, если вы хотите закончить со списком: дублей = список (множеств (я для г в идентификаторах, если ids.count (I)> 1)) – Nemelis

+0

'списка (набор (I для я в ids, если ids.count (i)> 1)) ' –

+0

Это еще медленнее, чем его существующий код (хотя только с постоянным коэффициентом 2-ish). Он удаляет вызов «len», но эта часть не имеет значения; заменив «elem in singles» на 'ids.count' означает, что вы теперь каждый раз выполняете поиск по каждому элементу, вместо поиска только для первого совпадения только в дубликатах. – abarnert

-1

Если вы не заботитесь о том порядке, в котором извлекаются эти идентификаторы, эффективный подход будет состоять в шаге сортировки (который является O (N журнал (N))), за которым следуют иды, за которыми следуют сами (это O (N)). Таким образом, этот подход является общим O (N log (N)).

+1

Но сама сортировка - O (N log N), поэтому шаг O (N) не имеет значения. Кроме того, это разрушает порядок, и мы не знаем, приемлемо ли это, поэтому вы не можете просто предположить, что это так. – abarnert

+0

Да, это не работает, если результат должен быть в том же порядке. – Arkanosis

1

Я полагаю, что это будет работать быстрее:

occasions = {} 
for id in ids: 
    try: 
     occasions[id] += 1 
    except KeyError: 
     occasions[id] = 0 
result = [id for id in ids if occasions[id] > 1] 
+0

Да, это просто простая версия «Counter», реализованная вручную, так что это будет так же быстро. (Или, по крайней мере, очень близко к нему, 'Counter' может быть немного быстрее, используя' __missing__' вместо 'except KeyError:', но это только сделает небольшую постоянную разницу.) Я думаю, что 'caseions = Counter (ids) 'легче читать и сложнее ошибаться, но это тоже хорошо - тем более, что может быть понят новичок, который не привык к мысли в терминах dicts, почему это помогает. – abarnert

+0

Хорошо, я полностью согласен с тобой. Спасибо за ваш ответ, я не знал о такой возможности. – ka2m

Смежные вопросы