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

27
Yfiua 16 Дек 2015 в 16:48

6 ответов

Лучший ответ

Медлительность вашего кода возникает из-за того, что a.index(x) выполняет линейный поиск, и вы выполняете этот линейный поиск для каждого из элементов l. Таким образом, для каждого из 1М элементов вы выполняете (до) 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)
22
dsh 16 Дек 2015 в 14:01

Ну, я думаю, это зависит от того, хотите ли вы вернуть индексы в указанном порядке или нет. Если вы хотите, чтобы пример вернулся:

    [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 = [0,0,2,1,0,2]

Я проверил это на 1000000 записей, и это было сделано практически сразу.

6
jfish003 16 Дек 2015 в 14:37

Ваше решение медленное, потому что его сложность O(nm), где m является числом уникальных элементов в l: a.index() равно 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 содержит только целые числа в известном диапазоне, вы также можете хранить индексы в списке вместо словаря для более быстрого поиска.

6
Eugene Yarmash 16 Дек 2015 в 23:22

Вы можете использовать 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)
2
Kasramvd 10 Июл 2016 в 18:18

Вы можете сделать это за O(N) время с помощью {{X1} } и понимание списка:

>>> 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})
38
Ashwini Chaudhary 16 Дек 2015 в 14:11

Для полноты вы также можете сделать это с нетерпением:

from itertools import count

wordid = dict(zip(set(list_), count(1)))

Это использует набор для получения уникальных слов в парах list_ каждое из этих уникальных слов со следующим значением из count() (которое считает вверх), и строит словарь из результатов.

Оригинальный ответ, написанный nneonneo.

1
Community 23 Май 2017 в 11:53