【问题标题】:Python best way to 'swap' words (multiple characters) in a string?Python在字符串中“交换”单词(多个字符)的最佳方法?
【发布时间】:2022-01-09 12:21:14
【问题描述】:

考虑以下示例:

string_now = 'apple and avocado'
stringthen = string_now.swap('apple', 'avocado') # stringthen = 'avocado and apple'

和:

string_now = 'fffffeeeeeddffee'
stringthen = string_now.swap('fffff', 'eeeee') # stringthen = 'eeeeefffffddffee'

Swap character of string in Python 中讨论的方法不起作用,因为那里使用的映射技术只考虑了一个字符。 Python 的内置str.maketrans() 也只支持单字符翻译,当我尝试做多字符时,它会抛出以下错误:

replace() 方法链不仅远非理想(因为我有很多替换要做,链接替换将是一大块代码),而且由于它的顺序性,它不能完美地翻译为:

string_now = 'apple and avocado'
stringthen = string_now.replace('apple','avocado').replace('avocado','apple')

给出'apple and apple' 而不是'avocado and apple'

实现这一目标的最佳方法是什么?

【问题讨论】:

  • 是否有保证不在字符串中的字符?例如\n?
  • 'applemon'.swap('apple', 'lemon') 应该生产什么?
  • @KellyBundy 这是一个有趣的案例,但对于我的案例来说,不会有这样的案例。它们重叠的地方。它可以是'applemon'.swap('apple', 'mon')'applemon'.swap('app', 'lemon')。但这肯定是一个非常有趣的案例。
  • 没有永远不会出现的角色。尤其是新行几乎总是出现在多句行中
  • python-ideas 上的旧消息 :-)

标签: python python-3.x string


【解决方案1】:

我设法使这个功能完全符合您的要求。

def swapwords(mystr, firstword, secondword):
    splitstr = mystr.split(" ")

    for i in range(len(splitstr)):
        if splitstr[i] == firstword:
            splitstr[i] = secondword
            i+=1
        if splitstr[i] == secondword:
            splitstr[i] = firstword
            i+=1

    newstr = " ".join(splitstr)

   return newstr

基本上,它的作用是接收你的字符串"Apples and Avacados",并用空格分割它。因此,每个单词都在数组splitstr[] 中被索引。使用它,我们可以使用 for 循环来交换单词。 i+=1 是为了确保单词不会被交换两次。最后,我使用newstr= " ".join(splitstr) 将字符串连接回来,它连接了由空格分隔的单词。

运行以下代码给我们: Avacados and Apples.

【讨论】:

  • 您应该使用if..elif 而不是两个单独的ifs。然后,您不需要i += 1for 循环在下一次迭代中自动获取可迭代的下一个元素。事实上,你可以使用i += 100 并不会产生任何影响。
  • 另外,您的代码与swapwords("apples avocados and avocados", "apples", "avocados") 中断。
【解决方案2】:

鉴于我们想要交换单词xy,并且我们不关心它们重叠的情况,我们可以:

  • 在出现x 时拆分字符串
  • 在每个部分中,将y 替换为x
  • 加入y

本质上,我们使用字符串中的分割点作为临时标记,以避免顺序替换的问题。

因此:

def swap_words(s, x, y):
    return y.join(part.replace(y, x) for part in s.split(x))

测试一下:

>>> swap_words('apples and avocados and avocados and apples', 'apples', 'avocados')
'avocados and apples and apples and avocados'
>>>

【讨论】:

  • 我认为这是最好的方法,因为它不会假设没有出现任何字符并且非常优雅地避免顺序替换问题
【解决方案3】:

两种正则表达式解决方案,另一种适用于确实具有无法出现的字符(毕竟有超过一百万种不同的可能字符)并且不喜欢replace链的其他人:-)

def swap_words_regex1(s, x, y):
    return re.sub(re.escape(x) + '|' + re.escape(y),
                  lambda m: (x if m[0] == y else y),
                  s)

def swap_words_regex2(s, x, y):
    return re.sub(f'({re.escape(x)})|{re.escape(y)}',
                  lambda m: x if m[1] is None else y,
                  s)

def swap_words_replaces(s, x, y):
    return s.replace(x, chr(0)).replace(y, x).replace(chr(0), y)

一些基准测试结果:

 3.7 ms  1966 kB  swap_words_split
10.7 ms  2121 kB  swap_words_regex1
17.8 ms  2121 kB  swap_words_regex2
 1.3 ms   890 kB  swap_words_replaces

完整代码(Try it online!):

from timeit import repeat
import re
import tracemalloc as tm

def swap_words_split(s, x, y):
    return y.join(part.replace(y, x) for part in s.split(x))

def swap_words_regex1(s, x, y):
    return re.sub(re.escape(x) + '|' + re.escape(y),
                  lambda m: (x if m[0] == y else y),
                  s)

def swap_words_regex2(s, x, y):
    return re.sub(f'({re.escape(x)})|{re.escape(y)}',
                  lambda m: x if m[1] is None else y,
                  s)

def swap_words_replaces(s, x, y):
    return s.replace(x, chr(0)).replace(y, x).replace(chr(0), y)

funcs = swap_words_split, swap_words_regex1, swap_words_regex2, swap_words_replaces

args = 'apples and avocados and bananas and oranges and ' * 10000, 'apples', 'avocados'

for _ in range(3):
    for func in funcs:
        t = min(repeat(lambda: func(*args), number=1))
        tm.start()
        func(*args)
        memory = tm.get_traced_memory()[1]
        tm.stop()
        print(f'{t * 1e3:4.1f} ms  {memory // 1000:4} kB  {func.__name__}')
    print()

【讨论】:

  • 为什么会有人不喜欢替换链? swap_words_replaces 是我认为最优雅的解决方案。
  • @FanchenBao 你得问问 OP,他们写的“远非理想”,但我不知道为什么。不过,我认为最优雅的是 split+join 解决方案。
  • 是的,当我有一两个替代品要做时,这很优雅。由于我最初有大量替换要做,拆分连接方法效果更好,因为我可以循环遍历它们,为每个操作应用函数,但是在链接替换时它将是一大块代码
  • @Hamza 嗯?为什么不能执行与拆分连接方法完全相同的循环?
【解决方案4】:

试试这个功能

def swap_custom(swap, word):
    swap_word = word.replace(swap[0], "0")
    swap_word = swap_word.replace(swap[1], "1")
    swap_word = swap_word.replace("0", swap[1])
    swap_word = swap_word.replace("1", swap[0])
    return swap_word

string_now = 'apple and avocado'
swap = ['apple', 'avocado']
stringthen = swap_custom(swap, string_now)

【讨论】:

  • 如果string_now = "I ate 0 apples and 1 avocado today"怎么办?
  • 这只是一个交换的想法,对于您的边缘情况,您可以使用任何其他字符串值而不是 0 和 1。例如 ##1##、##0##。
  • @TanmayShrivastava 使用 {0}{1} 而不是 01 作为占位符可以解决这个问题
【解决方案5】:

为什么不只使用一个永远不会在原始字符串中的临时字符串?

例如:

>>> a = 'apples and avocados and avocados and apples'
>>> b = a.replace('apples', '#IamYourFather#').replace('avocados', 'apples').replace('#IamYourFather#', 'avocados')
>>> print(b)
avocados and apples and apples and avocados

其中#IamYourFather# 是一个永远不会出现在原始字符串中的字符串。

【讨论】:

  • 因为我不确定哪些字符串永远不会出现,但这对于这种情况仍然很有用
  • @Hamaza md5(你的名字或生日)就够了。
【解决方案6】:

本方案使用str.format():

string_now = "apple and avocado"
stringthen = (  # "avocado and apple"
    string_now.replace("apple", "{apple}")
    .replace("avocado", "{avocado}")
    .format(apple="avocado", avocado="apple")
)

# Edit: as a function
def swap_words(s, x, y):
    return s.replace(x, "{" + x + "}")
            .replace(y, "{" + y + "}")
            .format(**{x: y, y: x})

它首先在关键字前后添加大括号,将它们变成占位符。然后str.format()用来替换占位符。

【讨论】:

  • 感谢@nikeros 让我思考这个解决方案。
猜你喜欢
  • 2011-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-12
  • 1970-01-01
  • 1970-01-01
  • 2019-03-03
  • 1970-01-01
相关资源
最近更新 更多