在Python列表中高效搜索部分字符串



正在寻找一种在Python(3.6+)列表中搜索部分字符串的有效方法。

我有两份清单。listA是路径名+唯一文件名的字符串列表:

['/pathname/uniquestring.ext', '/pathname/uniquestring.ext', '/pathname/uniquestring.ext' ...]

(使用glob()创建,文件名都已给定并且已经存在)

listB是字典的列表。每个字典都有相同的键集,但值是唯一的。

[{key1:value1, key2:value2}, {key1:value3, key2:value4}, ...]

(也已给出)

listB中每个字典中的一个键:值对将具有一个值,该值包含在listA中的一个唯一项中。

但是,值在listA的每个项目中的位置是不确定的。

我想要的是:对于listB中的每个项目,找到listA中包含与dict中的k:v对匹配的子字符串的项目,并创建一个新的dict(或元组列表)作为"查找表"(目标是更正一组图像文件中损坏的exif创建日期)。

示例:

listA = ['/pathname/abdce_654321.ext', '/pathname/a3b4c5_123456.ext', '/pathname/cbeebie_645321_abcde.ext', ...]
listB = [{"id": "123456", "create_date": "23/05/2014"}, ...]
new_dict = {"/pathname/a3b4c5_123456.ext": "23/05/2014, ...}

我从dict comp中得到了我想要的东西,如下所示:

{j:i['create_date'] for j in listA for i in listB  if i['id'] in j}

但是,即使是我的小文件(大约5500个项目),在我(诚然相当旧)的笔记本电脑上也需要12秒。

大概这是因为我必须使用我的方法在整个列表B上迭代5500次。

在Python中有更有效的方法吗?

(注意,我不是在寻求如何用python更正exif数据的建议;这是关于列表中字符串查找的一般问题)

更正&澄清

  1. 在我的例子中,我忽略了在值"123456"周围加引号,这当然意味着它是一个整数;在真实世界的数据中,它不是,我处理的实际数据中的任何等效值也不是
  2. 出现在列表中的"id"子字符串项几乎总是由下划线分隔,但并不总是出现在整个字符串的同一位置;因此,例如,对每个项目执行拆分('_')并不总是将'id'字符串放在位置[-1]、[-2]或[-3],尽管[-1]可以处理大约80%的情况
  3. 所有的id都是唯一的,它们在任何一个列表中都不会出现一次以上;listA中的每个文件名都是唯一的;每个"id"从不出现在多个字典中

感谢大家迄今为止对的兴趣

我可以看到这两条评论的意思。最大的问题是:我们是否需要使用in,因为只有当我们不知道id在路径字符串中的位置时,这才是必要的?如果它总是在一个特定的地方,我们可以提取它,并使用恒定的时间查找:

def extract_id(path):
# todo
ids = {item['id']: item['create_date'] for item in listB}
new_dict = {path: ids[extract_id(path)] for path in listA}

它只是CCD_ 2,而不是您当前的CCD_。

首先,这里有一些帮助测试的通用列表:

listA = ['/pathname/abdce_%s.ext' % str(x) for x in range(10000)]
listB = [{'id': str(number), "create_date": "23/05/2014"} for number in range(10000)]
hello = {j: i['create_date'] for j in listA for i in listB if i['id'] in j}

运行10000个值,我的机器平均耗时8.8秒。(如果我在之后打印字典,则为9.5秒)

现在,如果我们将该代码编译为Cython(一个在C上运行的python超集),对我来说,这段时间降到了4.4秒

参见下方的代码

cpdef dict main():
cdef int x
cdef int number
cdef char j
cdef dict i
listA = ['/pathname/abdce_%s.ext' % str(x) for x in range(10000)]
listB = [{'id': str(number), "create_date": "23/05/2014"} for number in range(10000)]
hello = {j: i['create_date'] for j in listA for i in listB if i['id'] in j}
return hello

我写了一个小测试台,它生成有点像你的随机数据,并尝试你的原始词典理解,以及一个具有优化功能的版本,例如在找到匹配项时提前发布和删除使用过的标记。

match(你的原件)和match2(我的)都打印出结果的数量,以确保它们同等工作。

结果很有说服力。。。希望这能有所帮助。

我的MBP上5000/10000个项目的编号:

  • 原件:1.771/7.391
  • 优化:0.054/0.203
  • 不删除使用过的标签(如果这不是可接受的业务规则):0.917/3.789

import random
import timeit
import string
random.seed(42)

def genrand(n):
return "".join(
random.choice(string.ascii_lowercase + string.digits) for x in range(n)
)

filenames = []
tags = []
for x in range(5000):
id = genrand(8)
filenames.append("/pathname/%s_%s.ext" % (genrand(6), id))
if random.random() < 0.95:
tags.append({"id": id, "date": "date for %s" % id})

def match():
x = {j: i["date"] for j in filenames for i in tags if i["id"] in j}
print(len(x))

def match2():
x = {}
available_tags = tags[:]
for filename in filenames:
for tag in available_tags:
if tag["id"] in filename:
x[filename] = tag
available_tags.remove(tag)  # we've used this tag, remove it
break
print(len(x))

print(timeit.timeit(match, number=1))
print(timeit.timeit(match2, number=1))

最新更新