Индексирование списка с уникальным индексом
У меня есть список l = [10,10,20,15,10,20]
. Я хочу присвоить каждому уникальному значению определенный "индекс", чтобы получить [1,1,2,3,1,2]
.
Это мой код:
a = list(set(l))
res = [a.index(x) for x in l]
Который оказывается очень медленным.
l
имеет 1M элементов и 100K уникальных элементов. Я также попробовал карту с лямбдой и сортировкой, что не помогло. Каков идеальный способ сделать это?
Ответы
Ответ 1
Медленность вашего кода возникает из-за того, что a.index(x)
выполняет линейный поиск, и вы выполняете линейный поиск для каждого из элементов в l
. Таким образом, для каждого из элементов 1M вы выполняете (до) 100 тыс. Сравнений.
Самый быстрый способ преобразовать одно значение в другое - это посмотреть на карту. Вам нужно будет создать карту и заполнить взаимосвязь между исходными значениями и значениями, которые вы хотите. Затем извлеките значение из карты, когда вы встретите другое из того же значения в своем списке.
Вот пример, который делает один проход через l
. Там может быть место для дальнейшей оптимизации, чтобы исключить необходимость повторного перераспределения res
при добавлении к ней.
res = []
conversion = {}
i = 0
for x in l:
if x not in conversion:
value = conversion[x] = i
i += 1
else:
value = conversion[x]
res.append(value)
Ответ 2
Вы можете сделать это в O(N)
, используя defaultdict
и понимание списка:
>>> from itertools import count
>>> from collections import defaultdict
>>> lst = [10, 10, 20, 15, 10, 20]
>>> d = defaultdict(count(1).next)
>>> [d[k] for k in lst]
[1, 1, 2, 3, 1, 2]
В Python 3 используйте __next__
вместо next
.
Если вам интересно, как это работает?
default_factory
(т.е. count(1).next
в этом случае), переданный в defaultdict
, вызывается только тогда, когда Python встречает отсутствующий ключ, поэтому для 10 значение будет равным 1, а затем в течение следующих десяти отсутствующий ключ больше, поэтому используется ранее рассчитанный 1, теперь 20 снова является отсутствующим ключом, и Python снова вызовет default_factory
, чтобы получить его значение и т.д.
d
в конце будет выглядеть так:
>>> d
defaultdict(<method-wrapper 'next' of itertools.count object at 0x1057c83b0>,
{10: 1, 20: 2, 15: 3})
Ответ 3
Ваше решение медленное, потому что его сложность O(nm)
с m
является числом уникальных элементов в l
: a.index()
is O(m)
, и вы вызываете его для каждого элемента в l
.
Чтобы сделать это O(n)
, избавиться от index()
и сохранить индексы в словаре:
>>> idx, indexes = 1, {}
>>> for x in l:
... if x not in indexes:
... indexes[x] = idx
... idx += 1
...
>>> [indexes[x] for x in l]
[1, 1, 2, 3, 1, 2]
Если l
содержит только целые числа в известном диапазоне, вы также можете хранить индексы в списке вместо словаря для более быстрого поиска.
Ответ 4
Ну, я думаю, это зависит от того, хотите ли вы вернуть индексы в этом конкретном порядке или нет. Если вы хотите вернуть пример:
[1,1,2,3,1,2]
тогда вы можете посмотреть другие представленные ответы. Однако, если вы только заботитесь о создании уникального индекса для каждого уникального номера, то у меня есть быстрое решение для вас.
import numpy as np
l = [10,10,20,15,10,20]
a = np.array(l)
x,y = np.unique(a,return_inverse = True)
и для этого примера вывод y равен:
y = [0,0,2,1,0,2]
Я тестировал это для 1 000 000 записей, и это было сделано практически немедленно.
Ответ 5
Вы можете использовать collections.OrderedDict()
для сохранения уникальных элементов в порядке и, перейдя по перечислению этих упорядоченных уникальных элементов, чтобы получить диктовку элементов и те индексы (основанные на их порядке), затем передать этот словарь с основным списком operator.itemgetter()
, чтобы получить соответствующий индекс для каждого элемента:
>>> from collections import OrderedDict
>>> from operator import itemgetter
>>> itemgetter(*lst)({j:i for i,j in enumerate(OrderedDict.fromkeys(lst),1)})
(1, 1, 2, 3, 1, 2)
Ответ 6
Для полноты, вы также можете сделать это с нетерпением:
from itertools import count
wordid = dict(zip(set(list_), count(1)))
Это использует набор, чтобы получить уникальные слова в list_
, пары каждый из этих уникальных слов со следующим значением из count()
(который подсчитывается вверх) и строит словарь из результатов.
Оригинальный ответ, написанный nneonneo.