[英]An algorithm to find transitions in Python
我想实现一种获取字母变化索引的算法。 我有下面的列表,在这里我想找到每个字母更改的开头,并放入除第一个字母之外的结果列表。 因为对于第一个,我们应该获得它出现的最后索引。 让我给你举个例子:
letters=['A','A','A','A','A','A','A','A','A','A','A','A','B','C','C','X','D','X','B','B','A','A','A','A']
过渡:
'A','A','A','A','A','A','A','A','A','A','A','A'-->'B'-->'C','C'-->'X'-->'D'-->'X'-->'B','B'-->'A','A','A','A'
在这里,A字母结束后,B开始,我们应该将最后一个A的索引和第一个B的索引放入,依此类推,但是我们不应该在结果列表中包括X字母。
所需结果:
[(11, 'A'), (12, 'B'), (13, 'C'), (16, 'D'), (18, 'B'), (20, 'A')]
到目前为止,我已经完成了这段代码,它将查找除(11,'A')以外的其他项目。 如何修改代码以获得所需的结果?
for i in range(len(letters)):
if letters[i]!='X' and letters[i]!=letters[i-1]:
result.append((i,(letters[i])))
我的结果:
[(12, 'B'), (13, 'C'), (16, 'D'), (18, 'B'), (20, 'A')] ---> missing (11, 'A').
现在,您已经解释了您想要每个字母的第一个索引在第一个字母之后的情况,这是一个单行代码:
letters=['A','A','A','A','A','A','A','A','A','A','A','A','B','C','C','X','D','X','B','B','A','A','A','A']
[(n+1, b) for (n, (a,b)) in enumerate(zip(letters,letters[1:])) if a!=b and b!='X']
#=> [(12, 'B'), (13, 'C'), (16, 'D'), (18, 'B'), (20, 'A')]
现在,您的第一个条目是不同的。 为此,您需要使用一个配方来查找每个项目的最后一个索引:
import itertools
grouped = [(len(list(g))-1,k) for k,g in (itertools.groupby(letters))]
weird_transitions = [grouped[0]] + [(n+1, b) for (n, (a,b)) in enumerate(zip(letters,letters[1:])) if a!=b and b!='X']
#=> [(11, 'A'), (12, 'B'), (13, 'C'), (16, 'D'), (18, 'B'), (20, 'A')]
当然,您可以避免创建grouped
的整个列表,因为您只使用过groupby中的第一项。 我把它留给读者作为练习。
如果X是第一个(一组)项目,这也将给您X作为第一个项目。 因为您没有说自己在做什么,也没有说X为何在这里,但被忽略了,所以我不知道这是否是正确的行为。 如果不是,则可能使用我的整个其他食谱(在我的其他答案中),然后从中取出第一项。
您想要的(或者,您不想要的,如您最后解释的那样-请参阅我的其他答案):
import itertools
import functional # get it from pypi
letters=['A','A','A','A','A','A','A','A','A','A','A','A','B','C','C','X','D','X','B','B','A','A','A','A']
grouped = [(len(list(g)),k) for k,g in (itertools.groupby(letters))]
#=> [(12, 'A'), (1, 'B'), (2, 'C'), (1, 'D'), (2, 'B'), (4, 'A')]
#-1 to take this from counts to indices
filter(lambda (a,b): b!='X',functional.scanl(lambda (a,b),(c,d): (a+c,d), (-1,'X'), grouped))
#=> [(11, 'A'), (12, 'B'), (14, 'C'), (16, 'D'), (19, 'B'), (23, 'A')]
除了Xs,这为您提供了每个字母运行的最后一个索引。 如果要在相关字母后的第一个索引,则将-1切换为0。
scanl
是reduce,它返回中间结果。
通常,首先过滤还是最后过滤是有意义的,除非出于某种原因这很昂贵,否则可以很容易地完成过滤而不增加复杂性。
而且,您的代码相对难以阅读和理解,因为您要按索引进行迭代。 在python中,这是不寻常的,除非以数字方式处理索引。 如果您要访问的每个项目,通常直接进行迭代。
另外,为什么要这种特殊格式? 通常将格式设置为(unique item,data)
因为可以轻松地将其放在dict
。
您的问题有点令人困惑,但是这段代码应该可以实现您想要的。
firstChangeFound = False
for i in range(len(letters)):
if letters[i]!='X' and letters[i]!=letters[i-1]:
if not firstChangeFound:
result.append((i-1, letters[i-1])) #Grab the last occurrence of the first character
result.append((i, letters[i]))
firstChangeFound = True
else:
result.append((i, letters[i]))
只需对代码进行最少的更改,即可遵循Josh Caswell的建议:
for i, letter in enumerate(letters[1:], 1):
if letter != 'X' and letters[i] != letters[i-1]:
result.append((i, letter))
first_change = result[0][0]
first_stretch = ''.join(letters[:first_change]).rstrip('X')
if first_stretch:
result.insert(0, (len(first_stretch) - 1, first_stretch[-1]))
这是一个使用groupby
生成单个序列的方法,可以从中提取第一个索引和最后一个索引。
import itertools
import functools
letters = ['A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'B', 'C', 'C', 'X', 'D', 'X', 'B', 'B', 'A', 'A', 'A', 'A']
groupbysecond = functools.partial(itertools.groupby,key=operator.itemgetter(1))
def transitions(letters):
#segregate transition and non-transition indices
grouped = groupbysecond(enumerate(zip(letters,letters[1:])))
# extract first such entry from each group
firsts = (next(l) for k,l in grouped)
# group those entries together - where multiple, there are first and last
# indices of the run of letters
regrouped = groupbysecond((n,a) for n,(a,b) in firsts)
# special case for first entry, which wants last index of first letter
kfirst,lfirst = next(regrouped)
firstitem = (tuple(lfirst)[-1],) if kfirst != 'X' else ()
#return first item, and first index for all other letters
return itertools.chain(firstitem,(next(l) for k,l in regrouped if k != 'X'))
letters=['A','A','A','A','A','A','A','A','A','A','A','A','B','C','C','X','D','X','B','B','A','A','A','A']
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
prev = letters[0]
result = []
for i in range(len(letters)):
if prev!=letters[i]:
result.append((i-1,prev))
if letters[i]!='X':
prev = letters[i]
else:
prev = letters[i+1]
result.append((len(letters)-1,letters[-1]))
print result
结果:(不是OP的理想结果,对不起,我必须误会了。请参阅JSutton的答案)
[(11,'A'), (12,'B'), (14,'C'), (16,'D'), (19,'B'), (23,'A')]
它实际上是字母更改或列表结束之前的最后一个字母实例的索引。
借助字典,可以使运行时间在输入数量上保持线性,这是一个解决方案:
letters=['A','A','A','A','A','A','A','A','A','A','A','A','B','C','C','X','D','X','B','B','A','A','A','A']
def f(letters):
result = []
added = {}
for i in range(len(letters)):
if (i+1 == len(letters)):
break
if letters[i+1]!='X' and letters[i+1]!=letters[i]:
if(i not in added and letters[i]!='X'):
result.append((i, letters[i]))
added[i] = letters[i]
if(i+1 not in added):
result.append((i+1, letters[i+1]))
added[i+1] = letters[i+1]
return result
基本上,我的解决方案始终尝试在发生更改的地方添加两个索引。 但是字典(具有恒定时间查找功能的字典会告诉我们是否已经添加了元素,或者不排除重复元素)。 这需要添加第一个元素。 否则,您可以使用if语句来指示将只运行一次的第一轮。 但是,我认为该解决方案具有相同的运行时间。 只要您不通过查找列表本身来检查是否添加了元素(因为在最坏的情况下这是线性时间查找),这将导致O(n ^ 2)时间不好!
这是我的建议。 它包括三个步骤。
编码:
def letter_runs(letters):
prev = None
results = []
for index, letter in enumerate(letters):
if letter != prev:
prev = letter
results.append((index, letter))
if results[0][1] != "X":
results[0] = (results[1][0]-1, results[0][1])
else: # if first run is "X" second must be something else!
results[1] = (results[2][0]-1, results[1][1])
return [(index, letter) for index, letter in results if letter != "X"]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.