使用唯一索引索引列表
Posted
技术标签:
【中文标题】使用唯一索引索引列表【英文标题】:Indexing a list with an unique index 【发布时间】:2016-03-22 16:36:29 【问题描述】:我有一个列表说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 个唯一元素。我也尝试过使用 lambda 和排序的 map,但没有帮助。理想的方法是什么?
【问题讨论】:
你关心空间复杂度还是只关心时间复杂度? 【参考方案1】:您可以使用defaultdict
和列表理解在O(N)
时间内完成此操作:
>>> 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
。
如果您想知道它是如何工作的?
传递给defaultdict
的default_factory
(即在这种情况下为count(1).next
)仅在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)
【讨论】:
【参考方案2】:您的代码运行缓慢是因为a.index(x)
执行线性搜索,而您对l
中的每个元素执行该线性搜索。因此,对于 100 万个项目中的每一个,您执行(最多)10 万次比较。
将一个值转换为另一个值的最快方法是在地图中查找它。您需要创建地图并填写原始值和所需值之间的关系。然后,当您在列表中遇到另一个相同的值时,从映射中检索该值。
这是一个单次通过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)
【讨论】:
我会这样做。我相信这个答案将是 OP 最容易理解的。如果可以的话,有几个问题,假设我们有 1b 条记录,1m 唯一,那么conversion
的大小将是 1m,我们有没有办法减少它?还有你将如何优化res
追加操作
for each of the 1M items you perform (up to) 100K comparisons
— 为什么是 100K?我猜应该是 1M x 1M。
感谢您的回答。因此,使用您的代码,我可以获得一个字典,其中的键和值都没有重复的数字。通过使用逆向字典inv_map = v: k for k, v in conversion.items()
,我可以得到带有索引值的原始值。
@eugeney 因为a = list(set(l))
,len(a)
将是 100K。转换为集合会将大小缩小为唯一值。
@taesu res
可以创建得足够大,例如res = [None] * len(l)
。然后,您需要通过循环跟踪索引并将占位符 None 值替换为所需的数字。这将避免在超出底层数组容量时出现在 append() 中的 malloc 和 memcpy。【参考方案3】:
您的解决方案很慢,因为它的复杂性是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
仅包含已知范围内的整数,您还可以将索引存储在列表中而不是字典中以加快查找速度。
【讨论】:
【参考方案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 个条目进行了测试,基本上是立即完成的。
【讨论】:
它需要 numpy,这对于这样的任务来说是一个相当大的依赖。由于 numpy 在 C 或 Fortran 中实现其算法,因此它显然会很快。 该问题要求最快的方式,但没有指定任何依赖限制。正如我暗示的那样,如果这条路线不合适,还有其他很好的答案可供选择 我知道,我不认为您的回答不好,但您的帖子并没有明确说明它需要大量的第三方依赖。 这个答案应该是最好的。非常简单干净!真的很棒!【参考方案5】:为了完整性,你也可以热切地做:
from itertools import count wordid = dict(zip(set(list_), count(1)))
这使用一个集合来获取
list_
中的唯一词,对 这些唯一单词中的每一个都具有来自count()
的下一个值(其中 向上计数),并根据结果构造字典。
Original answer,由 nneonneo 编写。
【讨论】:
集合是无序的,因此索引可能没有按正确的顺序分配。【参考方案6】:您可以使用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)
【讨论】:
给读者的提示:此方法使用OrderedDict
作为设置保留顺序。以上是关于使用唯一索引索引列表的主要内容,如果未能解决你的问题,请参考以下文章