使用列表推导式替换字符串

6

能否使用列表推导式完成此示例:

a = ['test', 'smth']
b = ['test Lorem ipsum dolor sit amet',
     'consectetur adipiscing elit',
     'test Nulla lectus ligula',
     'imperdiet at porttitor quis',
     'smth commodo eget tortor', 
     'Orci varius natoque penatibus et magnis dis parturient montes']


for s in a:
    b = [el.replace(s,'') for el in b]

我希望能从一系列句子中删除特定单词。我可以使用循环来实现,但我认为可能有一行代码的解决方案。
我尝试了以下代码:
b = [[el.replace(s,'') for el in b] for s in a ]

但它出了问题。

我得到了很多高质量的答案,但现在我有一个更复杂的问题:如果我想使用词语组合怎么办?

a = ['test', 'smth commodo']

非常感谢您提供了这么多的答案!我对所有解决方案进行了速度测试,以下是结果: 我对100次计算取平均值(最后一个除外,因为等待时间太长)。

                      b=10 a=2   |  b=9000 a=2 | b=9000 a=100 | b=45k a=500
---------------------------------+-------------+--------------+---------------
COLDSPEED solution:   0.0000206  |  0.0311071  |  0.0943433   |  4.5012770
Jean Fabre solution:  0.0000871  |  0.1722340  |  0.2635452   |  5.2981001
Jpp solution:         0.0000212  |  0.0474531  |  0.0464369   |  0.2450547
Ajax solution:        0.0000334  |  0.0303891  |  0.5262040   | 11.6994496
Daniel solution:      0.0000167  |  0.0162156  |  0.1301132   |  6.9071504
Kasramvd solution:    0.0000120  |  0.0084146  |  0.1704623   |  7.5648351

我们可以看到Jpp解决方案是最快的,但我们不能使用它 - 它是所有其他解决方案中唯一不能在单词组合上工作的解决方案(我已经写信给他,希望他会改进他的答案!)。因此,看起来@cᴏʟᴅsᴘᴇᴇᴅ的解决方案是大数据集上最快的。


1
你为什么要把完全正常的代码重写成一行,让它更难读懂呢? - Aran-Fey
1
@Aran-Fey 我不确定 :) 我认为它更加“Pythonic”,也许会更快一些。 - Mikhail_Sam
7个回答

4

您所拥有的内容没有问题,但如果您想要使其更加清晰,并且性能不是很重要的话,您可以编译一个正则表达式模式并在循环中调用sub函数。

>>> import re
>>> p = re.compile(r'\b({})\b'.format('|'.join(a)))
>>> [p.sub('', text).strip() for text in b]

['Lorem ipsum dolor sit amet',
 'consectetur adipiscing elit',
 'Nulla lectus ligula',
 'imperdiet at porttitor quis',
 'commodo eget tortor',
 'Orci varius natoque penatibus et magnis dis parturient montes'
]

详细信息
您的模式应该是这个样子的:

\b    # word-boundary - remove if you also want to replace substrings
(
test  # word 1
|     # regex OR pipe
smth  # word 2 ... you get the picture
)
\b    # end with another word boundary - again, remove for substr replacement

这是已编译的正则表达式匹配器:

>>> p
re.compile(r'\b(test|smth)\b', re.UNICODE)

另一个需要考虑的问题是,你替换的字符串本身是否包含可能被正则表达式引擎解释为元字符的字符,而不是作为字面量处理。你可以在构建模式时使用re.escape来转义这些字符。

p = re.compile(r'\b({})\b'.format(
    '|'.join([re.escape(word) for word in a]))
)

当然,要记住,随着数据量和替换次数的增加,正则表达式和字符串替换都会变得繁琐。考虑使用适用于大型操作的其他工具,例如flashtext

1
为了安全起见,如果输入包含任何特殊的正则表达式字符,则您应该将join(a)更改为join(re.escape(word) for word in a) - Aran-Fey
@Aran-Fey 我考虑了一下...决定这只会让事情更加混乱。但你是对的,我会在即将到来的迭代中进行修改。 - cs95
@cᴏʟᴅsᴘᴇᴇᴅ 再次感谢您的快速回复!我理解使用 re 比循环慢,是吗? - Mikhail_Sam
@Mikhail_Sam 经验法则是,除非你真正需要正则表达式,否则不推荐使用。然而,最好的做法是测试一下。 - cs95
1
@Mikhail_Sam 我认为无论如何都应该可以工作,你能否尝试一下并让我知道它是否不起作用? - cs95
显示剩余2条评论

3
如果列表非常大,构建一个ORed正则表达式列表(例如"\btest\b|\bsmth\b")可能会非常冗长,如果需要删除的单词数量较多,则时间复杂度为O(n)。正则表达式测试第一个单词,然后是第二个...。
我建议您使用一个基于set的替换函数进行单词查找。如果未找到单词,则返回该单词本身,否则返回空以删除该单词:
a = {'test', 'smth'}
b = ['test Lorem ipsum dolor sit amet',
     'consectetur adipiscing elit',
     'test Nulla lectus ligula',
     'imperdiet at porttitor quis',
     'smth commodo eget tortor',
     'Orci varius natoque penatibus et magnis dis parturient montes']

import re

result = [re.sub(r"\b(\w+)\b", lambda m : "" if m.group(1) in a else m.group(1),c) for c in b]

print(result)

['Lorem ipsum dolor sit amet', 'consectetur adipiscing elit', 'Nulla lectus ligula', 'imperdiet at porttitor quis', 'commodo eget tortor', 'Orci varius natoque penatibus et magnis dis parturient montes']

如果您要替换的单词列表包含由两个单词组成的字符串,则此方法无法工作,因为\w不匹配空格。可以对由两个单词组成的“单词”列表进行第二次操作:

a = {'lectus ligula', 'porttitor quis'}

并将result注入类似过滤器,但采用显式的2个单词匹配:

result = [re.sub(r"\b(\w+ ?\w+)\b", lambda m : "" if m.group(1) in a else m.group(1),c) for c in result]

所以这需要2次遍历,但如果单词列表很大,仍然比详尽的正则表达式要快。


如何修改它以用于单词组合?我刚试过 a = {'test', 'smth commodo'} 但失败了。我猜这是因为正则表达式中的 \b ... \b,不是吗? - Mikhail_Sam
啊啊啊,因为你的列表里有单词,所以这变得更加困难了。低复杂度方法不那么容易实现。Coldspeed 的答案应该能够工作,即使在大型列表上不是特别高效。 - Jean-François Fabre
你的问题并没有要求这样做。你可以编辑它(添加一些额外的条件,而不修改原始内容)。我的方法可以通过第二次处理来实现。 - Jean-François Fabre
没错,它有效!谢谢。只是想测试一下,哪种方法更快! - Mikhail_Sam
我为所有解决方案添加了速度测试。您现在可以在问题中查看它!谢谢。 - Mikhail_Sam

2

这是另一种使用 setstr.joinstr.splitstr.strip 的方式。

a_set = set(a)

b = [[' '.join([word if word not in a_set else ''
                for word in item.split()]).strip()]
     for item in b]

# [['Lorem ipsum dolor sit amet'],
#  ['consectetur adipiscing elit'],
#  ['Nulla lectus ligula'],
#  ['imperdiet at porttitor quis'],
#  ['commodo eget tortor'],
#  ['Orci varius natoque penatibus et magnis dis parturient montes']]

有趣的解决方案!但我还需要执行另一个操作——将列表的列表转换为字符串列表,对吧? - Mikhail_Sam
@Mikhail_Sam,是的,我绝不意味着这是最有效的解决方案。如果性能很重要,请使用您的数据进行测试。如果不是,那就选择最易读的(可能不是这个)。 - jpp
1
这个答案的一个小变化是 [" ".join(filter(lambda x: x not in a, k.split())) for k in b] - Sohaib Farooqi
@jpp,它完美地运行了。我为这个问题增加了一些复杂性 - 请问你能否改进你的解决方案以在单词组合上工作? - Mikhail_Sam
@jpp 我为所有解决方案添加了速度测试。你现在可以在问题中查看它!看看你的结果 :) - Mikhail_Sam
@Mikhail_Sam,很抱歉,我的解决方案无法处理多个单词..正因为如此,这个解决方案才会快速。您需要选择另一个解决方案。 - jpp

1
你可以使用map和正则表达式。
import re
a = ['test', 'smth']
b = ['test Lorem ipsum dolor sit amet',
     'consectetur adipiscing elit',
     'test Nulla lectus ligula',
     'imperdiet at porttitor quis',
     'smth commodo eget tortor', 
     'Orci varius natoque penatibus et magnis dis parturient montes']

pattern=r'('+r'|'.join(a)+r')'
b=list(map(lambda x: re.sub(pattern,r'',x).strip(),b))

我为所有解决方案添加了速度测试。您现在可以在问题中查看它!谢谢。 - Mikhail_Sam

1
作为一种纯函数式方法(主要用于教育目的),可以利用functools模块中的partial和reduce函数以及map函数来将替换函数应用于字符串列表。请保留html标签。
In [48]: f = partial(reduce, lambda x, y: x.replace(y + ' ', ''), a)

In [49]: list(map(f, b))
Out[49]: 
['Lorem ipsum dolor sit amet',
 'consectetur adipiscing elit',
 'Nulla lectus ligula',
 'imperdiet at porttitor quis',
 'commodo eget tortor',
 'Orci varius natoque penatibus et magnis dis parturient montes']

此外,如果a中的项目数量不是很大,多次重复使用replace()并没有什么问题。在这种情况下,一个非常优化和直接的方法是使用两个如下的replace
In [54]: [line.replace(a[0] + ' ', '').replace(a[1] + ' ', '') for line in b]
Out[54]: 
['Lorem ipsum dolor sit amet',
 'consectetur adipiscing elit',
 'Nulla lectus ligula',
 'imperdiet at porttitor quis',
 'commodo eget tortor',
 'Orci varius natoque penatibus et magnis dis parturient montes']

使用 reduce 是一个有趣的解决方案!谢谢! - Mikhail_Sam
1
我为所有解决方案添加了速度测试。您现在可以在问题中查看它!谢谢。 - Mikhail_Sam

1
另一种可能性是将所有的单词组合在一起,然后将 \s 替换为 | 以供 re.sub 使用:
import re
b = ['test Lorem ipsum dolor sit amet',
 'consectetur adipiscing elit',
 'test Nulla lectus ligula',
 'imperdiet at porttitor quis',
 'smth commodo eget tortor', 
 'Orci varius natoque penatibus et magnis dis parturient montes']
a = ['test', 'smth commodo']
replaced_strings = [re.sub(re.sub('\s', '|', ' '.join(a)), '', i) for i in b]

输出:

[' Lorem ipsum dolor sit amet', 'consectetur adipiscing elit', ' Nulla lectus ligula', 'imperdiet at porttitor quis', '  eget tortor', 'Orci varius natoque penatibus et magnis dis parturient montes']

为了消除额外的空格,请进行一次额外的处理:
new_data = [re.sub('^\s+', '', i) for i in replaced_strings]

输出:

['Lorem ipsum dolor sit amet', 'consectetur adipiscing elit', 'Nulla lectus ligula', 'imperdiet at porttitor quis', 'eget tortor', 'Orci varius natoque penatibus et magnis dis parturient montes']

我为所有解决方案添加了速度测试。您现在可以在问题中查看它!谢谢。 - Mikhail_Sam

0
你可能正在寻找这个:
[el.replace(a[0],'').replace(a[1],'') for el in b]

如果你想同时移除空格,那么可以使用strip()函数。

[el.replace(a[0],'').replace(a[1],'').strip() for el in b]

希望这可以帮到你...

感谢您的解决方案!问题是a列表可能非常长(10-20个字符串)。 - Mikhail_Sam

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