如何迭代替换字符串中的字符?

3
这应该是一个非常简单的问题,但我卡住了。我想要迭代地将字符串“PEPTIDE”中的每个字符替换为“A”,结果为:“AEPTIDE”,“PAPTIDE”,“PEATIDE”等。我知道在Python中字符串是不可变的,所以我首先将字符串转换为列表,然后设置计数器从0到len(str),然后用“A”替换第i个字符并重新将字符串连接起来。由于某些原因,我的代码没有逐步替换A,而只是在每个位置添加了A:
pepSeq = list("PEPTIDE")
x = range(len(pepSeq))
for i in x:
    pepSeq2 = pepSeq
    pepSeq2[i] = 'A'
    print("".join(pepSeq2))

结果: AEPTIDE AAPTIDE AAATIDE AAAAIDE AAAAADE AAAAAAE AAAAAAA
有什么建议吗?似乎问题出现在我试图创建一个新变量pepSeq2来避免替换pepSeq中的所有字符为A。

1
你并没有复制列表,而是创建了一个新的引用。 - Samwise
2
尝试使用 pepSeq2 = pepSeq[:] 来复制列表,而不是引用它。 - Nick
5个回答

1
最简单和最符合Python风格的方法是使用切片——将原始字符串的部分与需要替换的字符组合起来,再加上剩余的字符串,如下所示:
s = "PEPTIDE"
for i in range(len(s)):
    print(s[:i] + "A" + s[i+1:])

或者使用列表推导式
s = "PEPTIDE"
variants = [s[:i] + "A" + s[i+1:] for i in range(len(s))]

现在让我们比较这些解决方案的速度,以防求助者想要在更长的字符串上运行。 by_splice 是我的,by_copy 是求助者尝试的方法,by_enum 是 @smci 和 @Samwise 建议的方法。
def by_splice():
  s = "PEPTIDE" * 1000
  return [s[:i] + "A" + s[i+1:] for i in range(len(s))]

def by_copy():
  variants = []
  s = list("PEPTIDE" * 1000)
  for i in range(len(s)):
    ss = s[:]
    ss[i] = 'A'
    variants.append("".join(ss))
  return variants

def by_enum():
  s = "PEPTIDE" * 1000
  return [''.join([(c,'A')[j==i] for (j,c) in enumerate(s)]) for i in range(len(s))]


%time _ = by_splice()
%time _ = by_copy()
%time _ = by_enum()

CPU times: user 21.5 ms, sys: 15 ms, total: 36.6 ms
Wall time: 36.5 ms
CPU times: user 530 ms, sys: 27.4 ms, total: 557 ms
Wall time: 558 ms
CPU times: user 6.72 s, sys: 48.4 ms, total: 6.77 s
Wall time: 6.79 s

我的解决方案(通过splice)是最快的,而基于复制的解决方案则慢大约15倍,基于枚举的解决方案则慢大约150倍


0
一个简单的避免这个问题的方法是每次构建一个新列表:
>>> for i in range(len("PEPTIDE")):
...     print(''.join("A" if i == j else c for j, c in enumerate("PEPTIDE")))
...
AEPTIDE
PAPTIDE
PEATIDE
PEPAIDE
PEPTADE
PEPTIAE
PEPTIDA

1
这个解决方案是O(n^2) => 即使只有 len(s)==1000 的输入,也需要几秒钟。 - Ronald Luc
“PEPTIDE” 只有 7 个字母,这是个好事情!(如果您知道输入的大小永远不会改变,所有解决方案都有效地是 O(1)。) - Samwise

0

你不需要重复复制整个列表。每次迭代只需要保存和恢复一个字符即可。

我的意思是:

pepSeq = list("PEPTIDE")

for i in range(len(pepSeq)):
    saved, pepSeq[i] = pepSeq[i], 'A'
    print("".join(pepSeq))
    pepSeq[i] = saved

输出:

AEPTIDE
PAPTIDE
PEATIDE
PEPAIDE
PEPTADE
PEPTIAE
PEPTIDA

这不是提问者想要的,期望的输出是“AEPTIDE”,“PAPTIDE”,“PEATIDE”等。 - Jan Pokorný
@Jan:糟糕...已修复。谢谢。 - martineau

0

一行嵌套列表推导式 的做法:

[''.join([(c,'A')[j==i] for (j,c) in enumerate(s)]) for i in range(len(s))]

['AEPTIDE', 'PAPTIDE', 'PEATIDE', 'PEPAIDE', 'PEPTADE', 'PEPTIAE', 'PEPTIDA']

这是如何工作的?
使用列表理解字符串连接 ''.join([...]) 生成变化比迭代覆盖第 i 个元素更符合 Python 风格。
外部列表推导式通过 i in range(len(s)) 扫描每个可能的字母索引:i
内部列表推导式遍历字符串...
...使用 for (j, c) in enumerate(s) 作为 Pythonic 语法习惯来生成每个字符 c 及其索引 j 在每个索引处,我们可以选择 c'A',并使用表达式 j==i 作为数字(0/1)索引。这依赖于布尔 True/False 被隐式转换为整数 0/1。
此外,这里还提供了老派的 迭代构造 方法,它不太优雅和惯用,但可能更高效。
for i in range(len(s)):
    sout = s[:] # Make a copy
    sout[i] = 'A'
    print(sout)

[s[:i] + "A" + s[i+1:] for i in range(len(s))],为什么要过度复杂化呢? - Jan Pokorný
@JanPokorný:因为那只解决了这个特定的情况,但是嵌套的列表推导式向你展示了如何解决整个类别的问题(例如,如果您必须替换两个不同的字母)。 - smci
您可以重复使用splice来替换多个字符,这样做仍然会更简单和更快。 - Jan Pokorný

0
pepSeq = list("PEPTIDE")

for i in range(len(pepSeq)):
    a = pepSeq[i]
    pepSeq[i] = "A"
    print("".join(pepSeq),end = ' ')
    pepSeq[i] = a

输出结果为:AEPTIDE PAPTIDE PEATIDE PEPAIDE PEPTADE PEPTIAE PEPTIDA


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接