Python: удалить много элементов из списка

Я нахожусь в финальной части проекта, над которым я работаю. Все работает гладко, но у меня есть узкое место, с которым мне трудно работать.

У меня есть список кортежей. Список имеет длину от 40 000 до 1 000 000 записей. Теперь у меня есть словарь, где каждый (значение, ключ) является кортежем в списке.

Итак, я мог бы

myList = [(20000, 11), (16000, 4), (14000, 9)...]
myDict = {11:20000, 9:14000, ...}

Я хочу удалить каждый (v, k) кортеж из списка.

В настоящее время я делаю:

for k, v in myDict.iteritems():
    myList.remove((v, k))

Удаление 838 кортежей из списка, содержащего 20 000 кортежей, занимает от 3 до 4 секунд. Скорее всего, я удалю более 10 000 кортежей из списка из 1 000 000, поэтому мне нужно, чтобы это было быстрее.

Есть ли лучший способ сделать это?

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

Ответ 1

Вам придется измерять, но я могу представить, что это будет более результативным:

myList = filter(lambda x: myDict.get(x[1], None) != x[0], myList)

потому что поиск происходит в dict, что больше подходит для такого рода вещей. Обратите внимание, что это приведет к созданию нового списка перед удалением старого; так что есть обмен памяти. Если это проблема, пересмотр вашего типа контейнера как предложения jkp может быть в порядке.

Изменить: будьте осторожны, если None находится в вашем списке - вам придется использовать другой "placeholder".

Ответ 2

Чтобы удалить около 10000 кортежей из списка около 1 000 000, если значения хешируются, самый быстрый подход должен быть:

totoss = set((v,k) for (k,v) in myDict.iteritems())
myList[:] = [x for x in myList if x not in totoss]

Подготовка набора - это небольшая разовая стоимость, которая позволяет многократно распаковывать и переупаковывать кортеж, или индексировать кортеж. Назначение myList[:] вместо назначения myList также семантически важно (в случае, если есть какие-либо другие ссылки на myList вокруг, этого недостаточно, чтобы перепрограммировать только имя - вы действительно хотите переконфигурировать содержимое!).

У меня нет тестовых данных, чтобы сделать измерение времени самостоятельно, увы!, но, дайте мне знать, как он играет в наши тестовые данные!

Если значения не являются хешируемыми (например, они являются подписями, например), наиболее быстрая, вероятно, следующая:

sentinel = object()
myList[:] = [x for x in myList if myDict.get(x[0], sentinel) != x[1]]

или, может быть (не должно иметь большого значения в любом случае, но я подозреваю, что предыдущий лучше - индексирование дешевле, чем распаковка и переупаковка):

sentinel = object()
myList[:] = [(a,b) for (a,b) in myList if myDict.get(a, sentinel) != b]

В этих двух вариантах дозорная идиома используется для защиты от значений None (что не является проблемой для предпочтительного подхода на основе набора - если значения хешируются!), поскольку это будет дешевле, чем if a not in myDict or myDict[a] != b (который требует двух индексов в myDict).

Ответ 3

Каждый раз, когда вы вызываете myList.remove, Python должен сканировать весь список для поиска этого элемента и его удаления. В худшем случае каждый элемент, который вы ищете, будет в конце списка каждый раз.

Вы пытались выполнить операцию "обратного":

newMyList = [(v,k) for (v,k) in myList if not k in myDict]

Но я действительно не уверен, насколько хорошо это будет масштабироваться, так как вы делаете копию исходного списка - потенциально может быть много использования памяти.

Наверное, лучшей альтернативой здесь будет ждать, когда Алекс Мартелли опубликует разумный, интуитивный, простой и эффективный подход.

Ответ 4

Проблема заключается в том, что вы используете list в качестве контейнера, который вы пытаетесь удалить, и это абсолютно неупорядоченный тип. Таким образом, чтобы найти каждый элемент в списке, это линейная операция (O (n)), она должна перебирать весь список, пока не найдет матч.

Если вы могли бы поменять list на какой-нибудь другой контейнер (set?), который использует hash() каждого элемента для их заказа, тогда каждое совпадение может выполняться намного быстрее.

Следующий код показывает, как вы могли это сделать, используя комбинацию идей, предложенных мной и Ником в этой теме:

list_set = set(original_list)
dict_set = set(zip(original_dict.values(), original_dict.keys()))
difference_set = list(list_set - dict_set)
final_list = []
for item in original_list:
    if item in difference_set:
        final_list.append(item)

Ответ 5

[(i, j) for i, j in myList if myDict.get(j) != i]

Ответ 6

Попробуйте что-то вроде этого:

myListSet = set(myList)
myDictSet = set(zip(myDict.values(), myDict.keys()))
myList = list(myListSet - myDictSet)

Это преобразует myList в набор, заменит ключи/значения в myDict и поместит их в набор, а затем найдет разницу, вернет его обратно в список и вернет его в myList,:)

Ответ 7

[i for i in myList if i not in list(zip(myDict.values(), myDict.keys()))]

Ответ 8

Список, содержащий миллион 2-х кортежей, невелик на большинстве машин под управлением Python. Однако, если вы абсолютно должны сделать удаление на месте, вот чистый способ сделать это правильно:

def filter_by_dict(my_list, my_dict):
    sentinel = object()
    for i in xrange(len(my_list) - 1, -1, -1):
        key = my_list[i][1]
        if my_dict.get(key, sentinel) is not sentinel:
            del my_list[i]

Обновить. На самом деле каждый del стоит O (n), перетасовывая указатели списка вниз, используя C memmove(), поэтому, если есть d dels, O(n*d) not O(n**2). Заметим, что (1) OP предполагает, что d approx == 0.01 * n и (2) усилие O(n*d) копирует один указатель в другое место в памяти... поэтому этот метод может быть на самом деле несколько быстрее, чем быстрый взгляд будет указывать. Тесты, кто-нибудь?

Что вы собираетесь делать со списком после, вы удалили элементы, находящиеся в dict? Возможно ли переписать диктовку на следующий шаг?