Подтвердить что ты не робот

Индексирование списка с уникальным индексом

У меня есть список 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 уникальных элементов. Я также попробовал карту с лямбдой и сортировкой, что не помогло. Каков идеальный способ сделать это?

4b9b3361

Ответ 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.