使用唯一索引索引列表

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


如果您想知道它是如何工作的?

传递给defaultdictdefault_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),其中ml 中唯一元素的数量: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 作为设置保留顺序。

以上是关于使用唯一索引索引列表的主要内容,如果未能解决你的问题,请参考以下文章

如何查找列表中所有唯一元素的所有索引[重复]

数据库怎样创建一个唯一聚集索引

为啥存在唯一索引时 MySQL Innodb “创建排序索引”?

逐对序列分析-查找唯一组合的索引

索引匹配具有多个条件的唯一值?

Mysql索引基本概念及案例总结(含索引的使用注意事项)