Python中查找转换的算法
我想实现一个算法,获取字母变化的索引。 我有下面的列表,在这里我想找到每个字母的开头变化,并把结果列表除了第一个。因为,对于第一个,我们应该得到它发生的最后一个索引。让我举个例子:Python中查找转换的算法,python,algorithm,Python,Algorithm,我想实现一个算法,获取字母变化的索引。 我有下面的列表,在这里我想找到每个字母的开头变化,并把结果列表除了第一个。因为,对于第一个,我们应该得到它发生的最后一个索引。让我举个例子: 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'
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').
结果:(不是OP想要的结果,抱歉我一定是误解了。请看JSutton的ans)
这实际上是一封信在更改或列表结束之前的最后一个实例的索引。你想要(或者,你不想要,正如你最后解释的-请参阅我的其他答案):
这将为您提供每个字母运行的最后一个索引,而不是Xs。如果需要相关字母后的第一个索引,请将-1切换为0
scanl
是一个reduce,返回中间结果
一般来说,先过滤还是后过滤都是有意义的,除非由于某种原因过滤成本很高,或者过滤可以在不增加复杂性的情况下轻松完成
此外,您的代码相对较难阅读和理解,因为您是按索引进行迭代的。这在python中是不寻常的,除非对索引进行数字操作。如果您访问每个项目,通常直接迭代
还有,你为什么想要这种特殊的格式?通常将格式设置为
(唯一项、数据)
,因为这可以很容易地放在目录中您的问题有点令人困惑,但此代码应该满足您的要求
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]))
借助字典使运行时间在输入数量上保持线性,下面是一个解决方案:
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)时间,这是不好的 既然您已经解释过,您希望每个字母的第一个索引都位于第一个之后,下面是一行:
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作为第一个项目。因为你没有说你在做什么,或者为什么会有Xs,但是忽略了,我不知道这是否是正确的行为。如果不是,那么可能使用我的整个其他配方(在我的其他答案中),然后从中选择第一项 只需对代码进行最小的更改,并遵循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'))
这是我的建议。它有三个步骤
首先,找到每个字母序列的所有起始索引
将第一次非X运行中的索引替换为其运行结束时的索引,该索引将比下一次运行开始时的索引小一个
过滤掉所有的X运行
守则:
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"]
这不是最好的修复方法,但您可以在循环之前的列表开头添加一个“X”。这可以解决问题。是的,当你在第一个i-1上时,你不能访问i-1。所以它不会附加在那里。相反,尝试做i+1==i和range(len(字母)-1)运算,我认为您期望的结果并不一致。最后的B是20而不是18。18是带有@sihrc的Xagree。我正在努力理解期望的结果是什么。为什么(16,'D')在结果中,而不是(14,'C')?此外,OP应该列出一个更清晰易读的列表(例如,为什么重复“a”两次以上?。@CoKoder如果你不想要Xs,为什么不在一开始就过滤掉它们?这不是我想做的。它统计每个字母出现的次数。但是,我想得到字母变化的索引。@CoKoder您现在可以看看这个。通过添加一行,它将为您提供累积值(即索引)。对不起,我不想导入我还没有的模块,因此这是我想到的第一个解决方案。首先,我想看看您的计时代码;其次,编写易于理解的代码比2倍的加速要重要得多,除非你正在进行大量的处理。很抱歉,不使用现有库作为编写更多代码的理由是一个糟糕的想法。我也发现我的代码更易于理解,虽然我知道这是基于个人偏好和知识。这符合OP的期望结果+字母=['A'、'A'、'A'、'A'、'A'、'A'、'A'、'A'、'A'、'A'、'A'、'X'、'C'、'X'、'D'、'X'、'B'、'B'、'A'、'A'、'A'、'A']。它打印[(12,'X'),(13,'C'),(16,'D'),(18,'B'),(20,'A')],这是错误的,因为(12,'X')应该是(11,'A')。@CoKoder如果你不得不对每一个答案抱怨,那么问题就是你的问题。事实上,这与您最初要求的输出相匹配。@Marcin我不是在抱怨它。我想说的是,答案应该满足其他情况。它不应该只针对某个特定的列表运行,对吗?您还没有提交任何其他案例,@CoKoder。您只给出了一个案例,这个答案满足。这个打印:[(11,'A'),
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]))
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'))
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"]