Python:在列表中查找包含X的项的索引

时间:2009-12-29 17:40:02

标签: python

我有一个巨大的数据列表,超过1M的记录形式相似(虽然这是一个更简单的形式):

[
  {'name': 'Colby Karnopp', 'ids': [441, 231, 822]}, 
  {'name': 'Wilmer Lummus', 'ids': [438, 548, 469]},
  {'name': 'Hope Teschner', 'ids': [735, 747, 488]}, 
  {'name': 'Adolfo Fenrich', 'ids': [515, 213, 120]} 
  ... 
]

鉴于id为735,我想找到Hope Teschner的索引2,因为给定的id属于Hope的id列表。这样做的最佳(表现方式)是什么?

感谢您的任何提示。

修改

可能应该提到这一点,但id 可以不止一次出现。在特定id 出现多次的情况下,我想要给定id的最低索引。

列表中的数据会经常变化,所以我对构建字典犹豫不决,因为字典需要通过每次更新列表来修改/重建,因为索引是字典中的值 - 即。更改列表中一个项目的位置将要求更新字典中的每个值,其索引大于新更改的索引。

编辑编辑

我刚做了一些基准测试,看起来即使对于1M +记录,重建字典也非常快。我想我现在会追求这个解决方案。

6 个答案:

答案 0 :(得分:6)

第一个索引满足条件的最简单方法(在Python 2.6或更高版本中:

next((i for i, d in enumerate(hugelist) if 735 in d['ids']), None)

如果没有项目满足条件,则给出None;更一般地说,你可以把next内置的第二个参数放在你需要的任何内容中,或者省略第二个arg(在这种情况下你可以删除一组括号)如果你没有没有项目满足条件时获得StopIteration异常(例如,您知道这种情况是不可能的)。

如果您需要在更改hugelist或其内容之间进行此类操作的时间超过几次,那么,正如您在问题的第二次编辑中指出的那样,构建一个辅助字典(来自整数)包含它的第一个词典的索引是优选的。由于您需要第一个适用的索引,因此您希望向后迭代(因此更接近hugelist开头的匹配将覆盖更远的那些) - 例如:

auxdict = {}
L = len(hugelist) - 1
for i, d in enumerate(reversed(hugelist)):
  auxdict.update(dict.fromkeys(d['ids'], L-i))

[[你不能使用reversed(enumerate(...,因为enumerate返回迭代器而不是列表,而reversed被优化为仅处理序列参数 - 需要L-i 1}}]]。

您可以通过其他方式构建auxdict,包括不进行撤消,例如:

auxdict = {}
for i, d in enumerate(hugelist):
  for item in d['ids']:
    if item not in auxdict: auxdict[item] =i

但由于在内循环中执行的if数量很大,这可能会大大减慢。由于需要内部循环,直接dict构造函数(采用一系列键,值对)也可能会变慢:

L = len(hugelist) - 1
auxdict = dict((item, L-i) for i, d in enumerate(reversed(hugelist)) for item in d['ids'])

但是,这些仅仅是定性考虑因素 - 考虑在hugelist(在命令行提示符下使用timeit)的几个“典型/代表性”值示例中运行基准测试,因为我(通常建议)测量这些方法的相对速度(以及它们的运行时间与我在答案开始时显示的无辅助查找的运行时间的比较 - 这个比率,加上您希望在连续hugelist次更改之间执行的平均查找次数将帮助您选择整体策略。

答案 1 :(得分:3)

Performancewise,如果您有1M记录,则可能需要切换到数据库或不同的数据结构。使用给定的数据结构,这将是线性时间操作。如果您打算经常进行此查询,则可以创建一个ID来记录dict。

答案 2 :(得分:3)

最好的方法可能是从ID到名称设置反向dict()。

答案 3 :(得分:0)

两个或更多个dicts可以共享相同的ID吗?如果是这样,我认为你需要返回一个索引列表。

如果您想进行一次性搜索,那么您可以使用列表解析来执行此操作:

>>> x = [
...   {'name': 'Colby Karnopp', 'ids': [441, 231, 822]}, 
...   {'name': 'Wilmer Lummus', 'ids': [438, 548, 469]},
...   {'name': 'Hope Teschner', 'ids': [735, 747, 488]}, 
...   {'name': 'Adolfo Fenrich', 'ids': [515, 213, 120]},
      ...
...  ]

>>> print [idx for (idx, d) in enumerate(x) if 735 in d['ids']]
[2]

但是如果你想要做很多事情并且列表没有太大变化那么创建一个反向索引要好得多:

>>> indexes = dict((id, idx) for (idx,d) in enumerate(x) for id in d['ids'])
>>> indexes
{213: 3, 515: 3, 548: 1, 822: 0, 231: 0, 488: 2, 747: 2, 469: 1, 438: 1, 120: 3, 441: 0, 735: 2}
>>> indexes[735]
2

注意:上面的代码假定每个ID都是唯一的。如果有重复项,则用collections.defaultdict(list)替换dict。

NNB:上面的代码将索引返回到原始列表中,因为这就是您要求的内容。但是,除非您想使用索引从列表中删除它,否则最好返回实际的dict而不是索引。

答案 4 :(得分:0)

如果构建索引的频率很低:

在主列表中创建索引值的查找数组,例如

lookup = [-1,-1,-1...]

...
def addtolookup
...

mainlistindex =lookup[myvalue]
if mainlistindex!=-1:
 name=mainlist[mainlistindex].name

如果频率很高,请考虑排序方法(我认为这是Schwartzian Transform答案的意思)。如果您在源列表发生更改时重建树的性能问题比使用从制造的索引中获取数据的性能更多问题,那么这可能会很好;将数据插入到现有列表中(关键地)知道当前一个最佳匹配字符串停止与id关联时id的其他可能匹配将比在每个delta上从头开始构建列表更快。

修改

这假设您的ID是密集填充的整数。

为了提高访问排序列表的性能,可以将其划分为400-600个条目的块,以避免重复向前或向后移动整个列表中的一个或几个位置,并使用二进制算法进行搜索。

答案 5 :(得分:0)

似乎数据结构不适合其使用。更改列表代价很高 - 无论是更改本身(如果您执行任何插入/删除)以及由此产生的重建dict的需要,或者每次都进行线性扫描。

问题是: 是如何更改列表的?

也许不是使用索引(经常更改),而是使用对象,并使用指向对象本身的指针而不是担心索引?