Я написал код на python для удаления уникальных чисел из списка с учетом ввода:
[1,2,3,2,1]
Он должен вернуться
[1,2,2,1]
Но моя программа возвращается
[1,2,1]
Мой код:
for i in data:
if data.count(i) == 1:
data.pop(i)
Я обнаружил, что ошибка возникает на if data.count(i) == 1:. Он говорит data.count(2) == 1, когда в списке явно 2 раза встречается цифра 2. Я не понимаю, почему это неправильный ответ






Попробуйте добавить в новый список, а не менять старый:
res = []
data = [1,2,3,2,1]
for i in data:
if data.count(i) > 1:
res.append(i)
Менять размер списка во время итерации - плохая практика, и pop сделает это. Это возвращает res = [1, 2, 2, 1]
Это рекурсивная проблема. Вы неправильно поняли list.pop(). Требуется индекс, а не конкретный элемент. Следовательно, вы не удаляете то, что ожидаете.
здесь нужно использовать enumerate,
data = [1,2,3,2,1]
#You could use dup_list = data[:] for python 3.2 and below
dup_list = data.copy()
for index,item in enumerate(dup_list):
if dup_list.count(item) == 1:
data.pop(index)
таким образом вы помещаете элемент в правильный индекс.
РЕДАКТИРОВАТЬ
Я редактировал, спасибо @wim за его комментарий. Теперь я перебираю копию (dup_list) исходного списка, чтобы не перебирать и не изменять исходный список одновременно.
Кроме того, я создал копию для объяснения. Но вы можете использовать более короткую версию кода,
data = [1,2,3,2,1]
#using data[:] to iterate over a copy
for index,item in enumerate(data[:]):
if data.count(item) == 1:
data.pop(index)
Обратите внимание, что я добавил комментарий, потому что этот синтаксис может сбивать с толку некоторых людей.
@wim Я добавил повторяющийся список.
Просто итерация по срезу данных: for i, item in enumerate(data[:]): тоже должен работать. Это был бы самый короткий код (не нужно объявлять копию), но я не уверен, что это будет рекомендованная практика.
Я полагать мог бы быть более питоническим ответом, используя понимание списка:
result = [x for x in data if data.count(x) > 1]
Я переместил ответы К. Нивис и Патрик Артнер внутри функции, чтобы легче было запускать timeit.
Чтобы учесть время, необходимое для вызова функции, я также заключил понимание списка в вызов функции.
def remove_singletons(data):
"""Return list with no singleton using for loops."""
res = []
for i in data:
if data.count(i) > 1:
res.append(i)
return res
def remove_singletons_lc(data):
"""Return list with no singleton using for list comprehension."""
return [x for x in data if data.count(x)>1]
from collections import Counter
def remove_singletons_counter(data):
c = Counter(data)
return [x for x in data if c[x] > 1]
import numpy as np
def remove_singletons_numpy(data):
a = np.array(data)
_, ids, counts = np.unique(a, return_counts=True, return_inverse=True)
return a[counts[ids] != 1]
l = [1,2,3,2,1]
%timeit remove_singletons(l)
>>> 1.42 µs ± 46.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit remove_singletons_lc(l)
>>> 1.2 µs ± 17.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Counter%timeit remove_singletons_counter(l)
>>> 6.55 µs ± 143 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
numpy.unique%timeit remove_singletons_numpy(l)
>>> 53.8 µs ± 3.07 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Похоже, что понимание списка - это немного, но последовательно быстрее, чем цикл, и намного быстрее, чем Counter с небольшими списками. Numpy для небольших списков более медленный.
Предположим, что у нас есть большой список случайных элементов п из [0, n]
import random
n = 10000
l = [random.randint(0, n) for i in range(n)]
%timeit remove_singletons(l)
>>> 1.5 s ± 64.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit remove_singletons_lc(l)
>>> 1.51 s ± 33.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Counter%timeit remove_singletons_counter(l)
>>> 2.65 ms ± 228 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
numpy.unique%timeit remove_singletons_numpy(l)
>>> 1.75 ms ± 38.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
В больших списках бесспорным победителем является numpy.unique, за которым следует Counter.
Для небольших списков понимание списка, кажется, помогает, но для больших списков подход numpy.unique работает лучше всего.
Согласен, что это наверное самый питонический
... если у вас нет списка из 1 миллиона номеров, и алгоритм должен записывать .count() на каждое отдельное число и просматривать весь список для каждого отдельного числа, чтобы получить его счет ....
Я могу это проверить! Дайте 5 минут :) Еще добавлю встречный подход.
@LucaCappelletti numpy все еще быстрее :)
Добавление теста с вашей версией как можно скорее!
вы также можете сравнить a = [random.randint(0, n) for i in range(n)] с b = random.choices(range(n+1), k = n); o)
Конечно, но они не включены в то время, которое я пробежал. Вот результаты: imgur.com/a/8QWL68J
Если у вас длинный список, вы должны поместить все числа в Counter (iterable) - словарь.
from collections import Counter
data = [1,2,3,2,1]
c = Counter(data)
cleaned = [x for x in data if c[x] > 1]
print(cleaned)
Это будет подсчитывать все вхождения за один проход вашего списка (O(n)), а поиск того, как часто это происходит внутри созданного словаря, будет O(1). Вместе это намного быстрее, чем использовать понимание списка, например
result = [x for x in data if data.count(x) > 1]
для списка из 100 значений он будет перебирать ваши 100 значений 100 раз, чтобы подсчитать каждое из них, которое составляет O (n ^ 2) - плохая вещь.
Выход:
[1,2,2,1]
Не модифицировать список во время итерации по нему. Поведение, скорее всего, будет нежелательным.
numpy.unique с return_counts=TrueЕще один вариант - использовать numpy.
a = np.array([1,2,2,3,2,1])
_, ids, counts = np.unique(a, return_counts=True, return_inverse=True)
a[counts[ids] != 1]
Для больших массивов это намного быстрее, чем понимание списка и Counter
a = np.array([1,2,2,3,2,1]*1000) #numpy array
b = list(a) # list
потом
%timeit _, ids, c = np.unique(a, return_counts=True, return_inverse=True);a[c[ids] != 1]
225 µs ± 11.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit [x for x in a if b.count(x) > 1]
885 ms ± 23.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit [x for x in a if c[x] > 1]
1.53 ms ± 58.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Помните, что при умножении списка увеличивается указатель на объект.
@LucaCappelletti Что? Как это здесь актуально?
быстрее, чем Counter nice - уже получил +1. Есть идеи, почему быстрее? numpy используя C под капотом?
Вы правы, в данном случае это не актуально, так как в моем примере список имеет чрезвычайно большую дисперсию, а в вашем нет синглтонов. Результаты здесь: imgur.com/a/3o3hq7R
Использование del вместо pop
data = [1,2,3,2,1]
for i in data:
if data.count(i)== 1:
index = data. index(i)
del data[index]
print(data)
производит,
[1, 2, 2, 1]
[Program finished]
Вы правы насчет детали
list.pop. Но предлагаемая альтернатива по-прежнему изменяет список при повторении по нему, что может привести к безумным результатам.