Страницы

Поиск по вопросам

понедельник, 3 февраля 2020 г.

Удалить уникальные значения из list

#python #list #count


Подобная тема была, но ответ мне не ясен. Почему удаляет из списка не все значения?

data = [1, 2, 3, 4, 5, 6]
for i in data:
     if data.count(i) == 1:
           data.remove(i)
print data


Печатает [2,4,6]
    


Ответы

Ответ 1



Самое главное, что Вы должны запонить - никогда не изменяйте размер массива во время прохождения по нему. Давайте посмотрим, как изменение размера массива влияет на логику цикла: In [3]: l = list(range(6)) In [4]: for x in l: ...: print(x) ...: l.remove(x) ...: 0 2 4 Взглянем на это через призму замечательных ASCII рисунков: +---+---+---+---+---+---+---+ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | <- l +---+---+---+---+---+---+---+ ^ x Выведем на печать x и удалим его из списка: # print(0) +---+---+---+---+---+---+ | 1 | 2 | 3 | 4 | 5 | 6 | <- l +---+---+---+---+---+---+ ^ x Переместимся на следующий элемент, как завещал нам великий Guido van Rossum: +---+---+---+---+---+---+ | 1 | 2 | 3 | 4 | 5 | 6 | <- l +---+---+---+---+---+---+ ^ x Для закрепления, повторим действия: напечатаетаем, удалим и перейдем на следующую итерацию цикла: # print(2) +---+---+---+---+---+ | 1 | 3 | 4 | 5 | 6 | <- l +---+---+---+---+---+ ^ x (до перехода) +---+---+---+---+---+ | 1 | 3 | 4 | 5 | 6 | <- l +---+---+---+---+---+ ^ x (после перехода) Очевидно, что изменяя размер массива во время итерирования по нему, на свет рождается еще одно маленькое зло, которое может привести (и приводит) к ошибкам. Самый короткий рабочий эквивалент данного цикла представил @andreymal: data = [x for x in data if data.count(x) > 1] Но у представленных решений есть один общий недостаток - они имеют квадратичную сложность. In [9]: data = list(range(10000)) In [10]: %timeit [x for x in data if data.count(x) > 1] 1 loops, best of 3: 1.66 s per loop Стандартная библиотека Python предоставляет класс Counter, который подсчитает количество вхождений каждого элемента. Таким образом, скорость получится линейной (строго говоря, амортизированно линейной): In [11]: from collections import Counter In [12]: def f(xs): ....: counter = Counter(xs) ....: return [x for x in xs if counter[x] > 1] ....: In [13]: %timeit f(range(10000)) 100 loops, best of 3: 2.33 ms per loop

Ответ 2



Как писали в предыдущем ответе, массив меняется, а индекс не меняется, по сути получается лишнее смещение на следующий элемент при удалении другого элемента. Когда мне лень мудрить, а список чистить надо, я создаю копию массива: for i in tuple(data): if data.count(i) == 1: data.remove(i) (tuple вместо list, потому что он, говорят, производительнее) Когда мне мудрить не лень, я могу завести отдельный список под удаляемые элементы: rm = [] for i in data: if data.count(i) == 1: rm.append(i) for x in rm: data.remove(i) Когда я вспоминаю про существование генераторных выражений, я пишу вариант-однострочник: data = [x for x in data if data.count(x) > 1] Четвёртый известный мне вариант приведён в другом ответе.

Ответ 3



если добавить печать data = [1, 2, 3, 4, 5, 6] for i in data: print i if data.count(i) == 1: data.remove(i) print data получим 1 3 5 видимо в питоне, когда вы удаляете элемент, индекс остается, и вы шагаете через один. т.е. после удаления надо опять с тем же индексом проверять элемент. Проще всего запусть цикл по убыванию индекса вот так удалим всё (мой первый код на питоне :), наверняка можно красивее) data = [1, 2, 3, 4, 5, 6] i = len(data)-1 while i>=0 : if data.count(data[i]) == 1: data.remove(data[i]) i = i-1 print data

Комментариев нет:

Отправить комментарий