python字典理解可以用来创建子字符串及其位置的字典吗?
给定一个字符串,我想创建一个包含该字符串中所有n个字符的子字符串的字典,其中字典键是子字符串,值是列表。列表的第一个元素是子字符串的出现次数,第二个元素是这些出现的起始位置的列表 例如,对于python字典理解可以用来创建子字符串及其位置的字典吗?,python,dictionary,dictionary-comprehension,Python,Dictionary,Dictionary Comprehension,给定一个字符串,我想创建一个包含该字符串中所有n个字符的子字符串的字典,其中字典键是子字符串,值是列表。列表的第一个元素是子字符串的出现次数,第二个元素是这些出现的起始位置的列表 例如,对于n=3,字符串'abcdabcxdabc'将导致此词典: {'abc': [3, [0, 4, 9]], 'cda': [1, [2]], 'dab': [2, [3, 8]], 'bcd': [1, [1]], 'cxd': [1, [6]], 'bcx': [1, [5]], 'x
n=3
,字符串'abcdabcxdabc'
将导致此词典:
{'abc': [3, [0, 4, 9]],
'cda': [1, [2]],
'dab': [2, [3, 8]],
'bcd': [1, [1]],
'cxd': [1, [6]],
'bcx': [1, [5]],
'xda': [1, [7]]}
下面的代码很有效,因为它只遍历字符串一次,但我想知道是否有一种更优雅和/或更通俗的方法来实现这一点,可能是使用字典理解。我对python非常陌生,仍然在试图弄清楚什么时候使用理解是有意义的(甚至是可能的),等等
text = 'abcdabcxdabc'
n = 3
d = {}
for i in range(len(text) - n + 1):
sub = text[i:i + n]
if sub in d:
d[sub][0] += 1
d[sub][1].append(i)
else:
d[sub] = [1, [i]]
print(d)
更新:感谢所有回复。他们通常证实了我的怀疑,即这太复杂了,无法在一个单一的理解中有效地实现(但多亏火山表明,如果不考虑效率的话,这是可能的)。感谢雷姆科格里奇和伊格纳西奥·巴斯克斯·艾布拉姆斯为我指点了defaultdict。我还得深入研究一下。我的计时结果表明,与dict相比,初始化defaultdict的开销稍大一些,但运行时间可能稍快一些,至少在本例中是这样。(计时结果在下面的单独评论中发布。)现在我想知道是否有任何情况下dict比defaultdict更受欢迎。另外,感谢Narcolei为我指出了它的工作时间 问题在于
v[0]
取决于长度或v[1]
,这意味着生成v[1]
的操作必须运行两次,或者必须迭代字典才能填充v[0]
以替换第一次包含的伪值
另一个问题是dict理解要求整个键和值立即可用,这意味着您必须运行列表理解才能获得字符的所有索引,这意味着整个操作变为O(n2)
我要做的唯一优化就是替换d
的创建,这样就不需要检查密钥包含
d = collections.defaultdict(lambda: [0, []])
这很可怕,但(我只添加了偏移量,从偏移量列表中可以得到的出现次数)。是的,可以做到
In [83]: my_str = 'abcdabcxdabc'
In [84]: n=3
In [85]: {substr: [my_str.replace(substr, ' '*n, c).index(substr)
for c in xrange(my_str.count(substr))]
....: for substr in set(my_str[idx:idx+n] for idx in xrange(len(my_str)-n))}
Out[85]:
{'abc': [0, 4, 9],
'bcd': [1],
'bcx': [5],
'cda': [2],
'cxd': [6],
'dab': [3, 8],
'xda': [7]}
正如@Ignacio所说,任何试图解决这个问题的理解都会有一个二次运行时性能,如@volcano的回答所示。解决此问题的最干净方法如下:
def substrings(text, n):
d = collections.defaultdict(list)
for i in xrange(len(text)-n+1):
d[text[i:i+n]].append(i)
return d
请注意,您不需要存储子字符串的数量,因为只需执行len(d['abc'])
即可获得abc
的出现次数
以下是这种方法与压缩的一些时间安排:
>>> import collections
>>>
>>> def substrings(text, n):
>>> d = collections.defaultdict(list)
>>> for i in xrange(len(text)-n+1):
>>> d[text[i:i+n]].append(i)
>>> return d
>>>
>>> def substrings2(text, n):
>>> return {substr: [my_str.replace(substr, ' '*n, c).index(substr) for c in xrange(my_str.count(substr))] for substr in set(my_str[idx:idx+n] for idx in xrange(len(my_str)-n))}
>>>
>>> text = 'abcdabcxdabc'
>>>
>>> %timeit substrings(text, 3)
100000 loops, best of 3: 9.51 µs per loop
>>> %timeit substrings2(text, 3)
10000 loops, best of 3: 26.3 µs per loop
>>> text = text * 100
>>> %timeit substrings(text, 3)
1000 loops, best of 3: 440 µs per loop
>>> %timeit substrings2(text, 3)
100 loops, best of 3: 8.68 ms per loop
请注意,理解的时间是如何增加1000倍的,而输入的大小是如何增加100倍的。我使用defaultdict和火山理解的一部分实现了几个变体,并运行了一些计时测试 原始版本(test1): 第一种变化(测试2): 第二种变化(测试3): 第三种变化(测试4): 以下是计时结果(显示每个循环的执行时间):
原始版本和前两个版本似乎是O(n),而第三个版本更接近O(n*n)。我想我会选择第二个版本,因为它是O(n)版本中最紧凑的一个。为了记录在案,另一个版本是:
>>> n, s = 3, 'abcdabcxdabc'
>>> L=[(s[i:i+n], i) for i in range(len(s)-n+1)]
>>> L
[('abc', 0), ('bcd', 1), ('cda', 2), ('dab', 3), ('abc', 4), ('bcx', 5), ('cxd', 6), ('xda', 7), ('dab', 8), ('abc', 9)]
>>> d={t:[i for u, i in L if u == t] for t, _ in L}
>>> d
{'abc': [0, 4, 9], 'bcd': [1], 'cda': [2], 'dab': [3, 8], 'bcx': [5], 'cxd': [6], 'xda': [7]}
>>> {k:(len(v), v) for k, v in d.items()}
{'abc': (3, [0, 4, 9]), 'bcd': (1, [1]), 'cda': (1, [2]), 'dab': (2, [3, 8]), 'bcx': (1, [5]), 'cxd': (1, [6]), 'xda': (1, [7])}
一行:
>>> {k:(len(v), v) for L in ([(s[i:i+n], i) for i in range(len(s)-n+1)],) for k, v in ((t, [i for u, i in L if u == t]) for t, _ in L)}
{'abc': (3, [0, 4, 9]), 'bcd': (1, [1]), 'cda': (1, [2]), 'dab': (2, [3, 8]), 'bcx': (1, [5]), 'cxd': (1, [6]), 'xda': (1, [7])}
在“现实世界”中我会怎么做:
“真实世界”的版本在一点上与一行不同:dict是O(n)vs O(n^2)个人而言,我不会对此进行理解。我更喜欢对集合中的每个元素(可能是经过过滤的元素)进行理解。你要做的是对集合的每个部分进行计数,这会降低集合的可读性。为什么你要在
abc
中计算c
两次,然后再计算cda
?这不应该是abc
,dab
,cxd
,abc
?使用defaultdict(from collections import defaultdict;d=defaultdict(lambda:[0,[])
),而不是普通的dict,这样您就不必一直检查键的存在。
d = defaultdict(lambda: [0, []])
for i, sub in [(i, text[i:i + n]) for i in range (len(text) - n + 1)]:
d[sub][0] += 1
d[sub][1].append(i)
d = {sub: [text.replace(sub, ' '*n, c).index(sub) for c in range(text.count(sub))]
for sub in set(text[i:i + n] for i in range(len(text) - n + 1))}
text = "abcdabcxdabc":
10000 loops, best of 3, function test1: 7.37486786334e-06
10000 loops, best of 3, function test2: 1.02725863892e-05
10000 loops, best of 3, function test3: 1.16522984082e-05
10000 loops, best of 3, function test4: 1.98546753287e-05
text = "abcdabcxdabc"*10:
10000 loops, best of 3, function test1: 7.16965834334e-05
10000 loops, best of 3, function test2: 6.8394193171e-05
10000 loops, best of 3, function test3: 7.63521044367e-05
10000 loops, best of 3, function test4: 0.00016625460554
text = "abcdabcxdabc"*100:
1000 loops, best of 3, function test1: 0.000708709217238
1000 loops, best of 3, function test2: 0.000623426932274
1000 loops, best of 3, function test3: 0.000695915822531
1000 loops, best of 3, function test4: 0.00419154787196
text = "abcdabcxdabc"*1000:
1000 loops, best of 3, function test1: 0.00700270379835
1000 loops, best of 3, function test2: 0.00615744327104
1000 loops, best of 3, function test3: 0.00712429980398
1000 loops, best of 3, function test4: 0.296075626815
>>> n, s = 3, 'abcdabcxdabc'
>>> L=[(s[i:i+n], i) for i in range(len(s)-n+1)]
>>> L
[('abc', 0), ('bcd', 1), ('cda', 2), ('dab', 3), ('abc', 4), ('bcx', 5), ('cxd', 6), ('xda', 7), ('dab', 8), ('abc', 9)]
>>> d={t:[i for u, i in L if u == t] for t, _ in L}
>>> d
{'abc': [0, 4, 9], 'bcd': [1], 'cda': [2], 'dab': [3, 8], 'bcx': [5], 'cxd': [6], 'xda': [7]}
>>> {k:(len(v), v) for k, v in d.items()}
{'abc': (3, [0, 4, 9]), 'bcd': (1, [1]), 'cda': (1, [2]), 'dab': (2, [3, 8]), 'bcx': (1, [5]), 'cxd': (1, [6]), 'xda': (1, [7])}
>>> {k:(len(v), v) for L in ([(s[i:i+n], i) for i in range(len(s)-n+1)],) for k, v in ((t, [i for u, i in L if u == t]) for t, _ in L)}
{'abc': (3, [0, 4, 9]), 'bcd': (1, [1]), 'cda': (1, [2]), 'dab': (2, [3, 8]), 'bcx': (1, [5]), 'cxd': (1, [6]), 'xda': (1, [7])}
>>> def substrings(s, n):
... d = {}
... tis = ((s[i:i+n], i) for i in range(len(s)-n+1))
... for t, i in tis:
... d.setdefault(t, []).append(i)
... return {k:(len(v), v) for k, v in d.items()}
...
>>> substrings(s, n)
{'abc': (3, [0, 4, 9]), 'bcd': (1, [1]), 'cda': (1, [2]), 'dab': (2, [3, 8]), 'bcx': (1, [5]), 'cxd': (1, [6]), 'xda': (1, [7])}