嘿,你们是正则表达的恋人!
我在Regex中,这些时候都有一个纯粹的理论问题。简单来说,我会把它作为游戏呈现。
游戏:
假设你有一个用空格分隔的单词列表
我称之为单词是因为它们是由正则表达式定义的:[a-zA-Z_0-9]+
(这里没有空字)
清单示例:
Horse Banana Joker RoXx0r A_Long_Word Joker 1337
我希望你做的是将 Joker 之外的每个单词替换为等于匹配单词的字符数的 $ 。
$$$$$ $$$$$$ Joker $$$$$$ $$$$$$$$$$$ Joker $$$$
用更少的单词: 我想要一个正则表达式匹配每个不属于单词“Joker”的字符(在字符串中,我的意思是,不是那个组成单词Joker )
虽然这并不容易,但这并非不可能(我有自己的正则表达式)。这就是为什么我会制定一些规则。
规则:
添加规则:
为了帮助你,这里有正则表达式必须工作的一些字符串:
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
这应该适用于所有语言,除了那些不支持前瞻的语言(它们相当罕见)
如果找到新的正则表达式,请继续发布新的正则表达式,我希望看到更多方法! :)
答案 0 :(得分:4)
对于PCRE / Perl / Ruby / Java / .net
查找
(?:\G(?!^)|(?<!\S)(?!Joker(?!\S)))\S
取代:
$
模式细节:
(?:
\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)
。
或:
\bJoker\b(*SKIP)(*F)|\S
并使用pypi python正则表达式模块(开头有一个单词边界,一个单词结尾有一个单词边界):
\mJoker\M(*SKIP)(*F)|\S
适用于Javascript 的一个(如果只有要替换的东西):
查找
((?:\s+|\bJoker\b)*)\S((?:\s+Joker)*\s*$)?
替换:(反向引用group1,转义$,反向引用group2)
$1$$$2
使用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
中的字符,确保他们使用后视和前瞻功能在该单词中不。或者,它匹配所有不在单词中的字母。
用$
替换匹配工作。
修改强>
更改了测试的顺序,使其更加高效。 (从&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
,替换匹配的任何字母。
修改强>
注意到Casimir et Hippolyte的版本与我的相似。但它们并不相同,所以我现在暂时留下我的答案;)