List.count () говорит об одном элементе в списке, когда есть два

Я написал код на 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. Я не понимаю, почему это неправильный ответ

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
4
0
752
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Ответ принят как подходящий

Попробуйте добавить в новый список, а не менять старый:

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)

Обратите внимание, что я добавил комментарий, потому что этот синтаксис может сбивать с толку некоторых людей.

Вы правы насчет детали list.pop. Но предлагаемая альтернатива по-прежнему изменяет список при повторении по нему, что может привести к безумным результатам.

wim 11.07.2018 21:26

@wim Я добавил повторяющийся список.

scharette 11.07.2018 21:34

Просто итерация по срезу данных: for i, item in enumerate(data[:]): тоже должен работать. Это был бы самый короткий код (не нужно объявлять копию), но я не уверен, что это будет рекомендованная практика.

michaPau 11.07.2018 21:58

Решение с использованием понимания списка

Я полагать мог бы быть более питоническим ответом, используя понимание списка:

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 работает лучше всего.

Согласен, что это наверное самый питонический

C.Nivs 11.07.2018 21:27

... если у вас нет списка из 1 миллиона номеров, и алгоритм должен записывать .count() на каждое отдельное число и просматривать весь список для каждого отдельного числа, чтобы получить его счет ....

Patrick Artner 11.07.2018 21:39

Я могу это проверить! Дайте 5 минут :) Еще добавлю встречный подход.

Luca Cappelletti 11.07.2018 21:40

@LucaCappelletti numpy все еще быстрее :)

rafaelc 11.07.2018 21:55

Добавление теста с вашей версией как можно скорее!

Luca Cappelletti 11.07.2018 21:56

вы также можете сравнить a = [random.randint(0, n) for i in range(n)] с b = random.choices(range(n+1), k = n); o)

Patrick Artner 11.07.2018 22:07

Конечно, но они не включены в то время, которое я пробежал. Вот результаты: imgur.com/a/8QWL68J

Luca Cappelletti 11.07.2018 22:11

Вы можете использовать понимание списка для создания нового списка:

[ x for x in data if data.count(x)>1 ]

Кроме того, метод pop() принимает в качестве аргумента индекс элемента, а не значение.

Если у вас длинный список, вы должны поместить все числа в 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)

Помните, что при умножении списка увеличивается указатель на объект.

Luca Cappelletti 11.07.2018 21:56

@LucaCappelletti Что? Как это здесь актуально?

rafaelc 11.07.2018 21:58

быстрее, чем Counter nice - уже получил +1. Есть идеи, почему быстрее? numpy используя C под капотом?

Patrick Artner 11.07.2018 22:05

Вы правы, в данном случае это не актуально, так как в моем примере список имеет чрезвычайно большую дисперсию, а в вашем нет синглтонов. Результаты здесь: imgur.com/a/3o3hq7R

Luca Cappelletti 11.07.2018 22:06

Использование 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]

Другие вопросы по теме