正则表达式游戏 - 用可变数量的字符替换除特定单词之外的每个单词

时间:2017-05-04 19:08:09

标签: regex

嘿,你们是正则表达的恋人!

我在Regex中,这些时候都有一个纯粹的理论问题。简单来说,我会把它作为游戏呈现。

游戏:
假设你有一个用空格分隔的单词列表 我称之为单词是因为它们是由正则表达式定义的:[a-zA-Z_0-9]+(这里没有空字)
清单示例:
Horse Banana Joker RoXx0r A_Long_Word Joker 1337

我希望你做的是将 Joker 之外的每个单词替换为等于匹配单词的字符数的 $
$$$$$ $$$$$$ Joker $$$$$$ $$$$$$$$$$$ Joker $$$$

用更少的单词: 我想要一个正则表达式匹配每个不属于单词“Joker”的字符(在字符串中,我的意思是,不是那个组成单词Joker )

虽然这并不容易,但这并非不可能(我有自己的正则表达式)。这就是为什么我会制定一些规则。

规则:

  • 必须只使用1个正则表达式
  • 我不接受任何仅适用于特定语言的正则表达式
  • 我仍会接受最常见的功能,如条件语,外观等......即使某些语言无法读取它们
  • 不允许递归(但是如果你有一个递归的递归,发布它,只是为了正则表达式的美丽^^)
  • 必须针对性能优化正则表达式
  • 如果您的正则表达式匹配(得到它?;))这些规则但不满足我,我会随意添加更多规则

添加规则:



为了帮助你,这里有正则表达式必须工作的一些字符串:
Horse Banana Joker RoXx0r A_Long_Word Joker 1337 Joke Poker Joker Jokers
更换后必须返回:
$$$$$ $$$$$$ Joker $$$$$$ $$$$$$$$$$$ Joker $$$$ $$$$ $$$$$ Joker $$$$$$

Joker Joker Joker
更换后必须返回:
Joker Joker Joker

再次,解决问题不是这里的目标,我希望看到不同的解决方案,更重要的是我希望看到最好的解决方案!

解决方案:

一个非常优雅的Casimir et Hippolyte
(?:\G(?!^)|(?<!\S)(?!Joker(?:\s|$)))\S(替换:$
See the post
然而,\ G取出了问题的乐趣并且不能用于所有语言,所以我不能接受它,除非可以创建相当于\ G

的自定义分隔符 Casimir et Hippolyte也几乎接受了答案:
((?:\s+|\bJoker\b)*)\S((?:\s+Joker)*\s*$)?(替换:$1$$2
See the post
当字符串中只有小丑词时,不起作用 ClasG的类似解决方案:
(\bJoker[^\w]+)\w|\w([^\w]+Joker\b)|\w(替换:$1$$2
See the post
当字符串中只有小丑词时,不起作用 另一个ClasG
[^Joker\s]|(?<!\b)J|J(?!oker\b)|(?<!\bJ)o|o(?!ker\b)|(?<!\bJo)k|k(?!er\b)|(?<!\bJok)e|e(?!r\b)|(?<!\bJoke)r|r(?!\b)(替换:$
See the post
但是效率不是很高,但它是看待事物的另一种方式;)

在阅读下面的Rahul评论后,我想出了一个类似的正则表达式:
(?(?<=\b|\bJ|\bJo|\bJok|\bJoke|\bJoker)(?!(?:Joke|oke|ke|e|)r\b)\w|\w)(替换$
Regex101
这也是低效的,但使用相同的外观列表的东西:)

这是我的第一个解决方案:
我使用的技巧可能被视为作弊,但我不这样做,因为它不会改变你用来替换字符的功能。您只需在字符串末尾添加'$',然后将字符替换为字符串 因此,而不是像: string = replace(string, regex, '$1$2')
我们会: string = replace(string+'$', regex, '$1$2')

所以这是正则表达式:
(\bJoker\b)|.$|\w(?=.*(\$))(替换:$1$2
Regex 101
这应该适用于所有语言,除了那些不支持前瞻的语言(它们相当罕见)

如果找到新的正则表达式,请继续发布新的正则表达式,我希望看到更多方法! :)

3 个答案:

答案 0 :(得分:4)

对于PCRE / Perl / Ruby / Java / .net

查找

(?:\G(?!^)|(?<!\S)(?!Joker(?!\S)))\S

取代:

$

demo

模式细节:

(?:
    \G (?!^) # contigous to a previous match (but not at the start of the string)
  |        # OR
    (?<!\S)  # not preceded by a non white-space
    (?!Joker(?!\S)) # not followed by the forbidden word
)
\S   # a non-whitespace character

如果您的单词仅由单词字符组成,则可以简化使用单词和非单词边界播放的模式:(?:\G\B|\b(?!Joker\b))\w

其他方式(PCRE / Perl):没有\G功能和回溯控制动词(*SKIP)(需要更少的步骤):

\s*(?:Joker(?:\s+|$))*(*SKIP)\K.

要清楚(*SKIP)仅在字符串以禁用词或空格结尾时才有用。您也可以将其替换为(*COMMIT)

demo

或:

\bJoker\b(*SKIP)(*F)|\S

并使用pypi python正则表达式模块(开头有一个单词边界,一个单词结尾有一个单词边界):

\mJoker\M(*SKIP)(*F)|\S

适用于Javascript 的一个(如果只有要替换的东西)

查找

((?:\s+|\bJoker\b)*)\S((?:\s+Joker)*\s*$)?

替换:(反向引用group1,转义$,反向引用group2)

$1$$$2 

demo

使用y标志的另一个Javascript版本(强制匹配的对象),但遗憾的是,除了Firefox移动版之外,Internet Explorer,Safari和移动浏览器都不支持此标记:

var strs = ['Horse Banana Joker RoXx0r A_Long_Word Joker 1337 Joke Poker Joker', 'Joker Joker Joker'];

strs.forEach(function (s) {
    console.log(s.replace(/(?=((?:\s+|\bJoker\b)*))\1./gy, '$1$$'));
});

(?=(...))\1模拟一个原子组(禁止回溯)。

答案 1 :(得分:2)

好的,我们再来一次;)这次有一个完整的解决方案,应该适用于大多数正则表达式(JS除外)。它不是很灵活,但它有效:

[^Joker\s]|(?<!\b)J|J(?!oker\b)|(?<!\bJ)o|o(?!ker\b)|(?<!\bJo)k|k(?!er\b)|(?<!\bJok)e|e(?!r\b)|(?<!\bJoke)r|r(?!\b)

或更具可读性

[^Joker\s] # Test for any character not belonging to the word Joker
|
(?<!\b)J|J(?!oker\b) # Test for J not belonging to the word Joker
|
(?<!\bJ)o|o(?!ker\b) # Test for o not belonging to the word Joker
|
(?<!\bJo)k|k(?!er\b) # Test for k not belonging to the word Joker
|
(?<!\bJok)e|e(?!r\b) # Test for e not belonging to the word Joker
|
(?<!\bJoke)r|r(?!\b) # Test for r not belonging to the word Joker

它分别匹配单词Joker中的字符,确保他们使用后视和前瞻功能在该单词中。或者,它匹配所有不在单词中的字母。

$替换匹配工作。

Here it is at regex101

修改

更改了测试的顺序,使其更加高效。 (从&gt; 1600到~1100步。)

答案 2 :(得分:1)

不能真正说出原因,但我想看看能否在没有环顾四周的情况下做到这一点。这就是我最终的结果:

(\bJoker[^\w]+)\w|\w([^\w]+Joker\b)|\w

$1$$2替换它应该可以解决问题。

它有一个限制(我想到)。它不会将Joker作为一行中的单词处理:(。那是因为它背后的逻辑是......

它在两个替换中与单词Joker匹配 - 在其后面跟一个字母或在它之前。在两种情况下,通过非字母(空格)将单词与字母分开。还有第三种选择 - 一个字母。如果两个第一个匹配都没有,这将找到非与Joker相关的字母。 在前两种情况下,单词加相邻空格(非字母)被捕获到一个组(Joker - 空间)中。第二种选择也是如此,但顺序相反(空格 - Joker)。第三种选择不会捕获任何东西。它只是匹配一封信。

将完整匹配替换为$1$$2(请注意中间的文字$)或者插入单词Joker加空格(如果第一个替换匹配),后跟{{1 }}。 如果第一个匹配,但第二个匹配,则插入的替换将是$加上捕获的空格,后跟$。 如果两者中没有一个匹配,则不会捕获任何内容,并且唯一插入的内容将是唯一的Joker,替换匹配的任何字母。

See it here at regex101.

修改

注意到Casimir et Hippolyte的版本与我的相似。但它们并不相同,所以我现在暂时留下我的答案;)