我正在尝试编写一个正则表达式,以显示所有10个字符长的单词,并且所有字母都没有重复。
到目前为止,我已经
grep --colour -Eow '(\w{10})'
这是问题的第一部分。我将如何检查“唯一性”?除了需要使用反向引用之外,我真的没有任何线索。
我正在尝试编写一个正则表达式,以显示所有10个字符长的单词,并且所有字母都没有重复。
到目前为止,我已经
grep --colour -Eow '(\w{10})'
这是问题的第一部分。我将如何检查“唯一性”?除了需要使用反向引用之外,我真的没有任何线索。
Answers:
grep -Eow '\w{10}' | grep -v '\(.\).*\1'
排除具有两个相同字符的单词。
grep -Eow '\w{10}' | grep -v '\(.\)\1'
排除重复字符的字符。
POSIXly:
tr -cs '[:alnum:]_' '[\n*]' |
grep -xE '.{10}' |
grep -v '\(.\).*\1'
tr
通过将任何s非单词字符c序列(字母数字和下划线的补全)转换为换行符,将单词放在自己的行上。
或搭配一个grep
:
tr -cs '[:alnum:]_' '[\n*]' |
grep -ve '^.\{0,9\}$' -e '.\{11\}' -e '\(.\).*\1'
(不包括少于10个字符和多于10个字符的行,以及字符至少出现两次的行)。
grep
仅使用一个(具有PCRE支持的GNU grep或pcregrep
):
grep -Po '\b(?:(\w)(?!\w*\1)){10}\b'
也就是说,一个单词边界(\b
)后跟一个10个单词字符的序列(前提是每个单词后面都没有一个单词字符及其本身的序列,使用负数预读PCRE运算符(?!...)
)。
我们很幸运,它在这里能正常工作,因为在重复部分中,没有太多的正则表达式引擎与反向引用一起使用。
请注意(至少在我的GNU grep版本中)
grep -Pow '(?:(\w)(?!\w*\1)){10}'
不起作用,但是
grep -Pow '(?:(\w)(?!\w*\2)){10}'
确实(如echo aa | grep -Pw '(.)\2'
)听起来像个错误。
您可能想要:
grep -Po '(*UCP)\b(?:(\w)(?!\w*\1)){10}\b'
如果您希望\w
或\b
考虑将任何字母作为单词组成部分,而不仅仅是非ASCII语言环境中的ASCII字母。
另一种选择:
grep -Po '\b(?!\w*(\w)\w*\1)\w{10}\b'
这是一个单词边界(一个单词边界后没有一个重复的单词字符序列),后面是10个单词字符。
人们可能会想到的事情是:
Babylonish
例如将被匹配,因为所有字符都不同,即使存在两个B
s,一个小写和一个大写(用于-i
更改大小写)。-w
,\w
和\b
,一个字是(仅适用于GNU ASCII那些信grep
现在,在[:alpha:]
您所在区域的字符类,如果使用-P
和(*UCP)
),十位数字或下划线。c'est
(根据单词的法语定义两个单词)或it's
(根据单词的某些英语定义一个单词)或rendez-vous
(根据单词的法语定义一个单词)不被视为一个单词。(*UCP)
,也不会将Unicode组合字符视为单词组成部分,因此téléphone
($'t\u00e9le\u0301phone'
)被视为10个字符,其中之一为非字母。défavorisé
($'d\u00e9favorise\u0301'
)即使有两个也将被匹配,é
因为这是10个所有不同的字母字符,后跟一个组合的重音符号(非字母,因此在e
与其重音之间存在一个字边界)。\w
虽然不匹配-
。
好的...这是五个字符串的笨拙方式:
grep -P '^(.)(?!\1)(.)(?!\1|\2)(.)(?!\1|\2|\3)(.)(?!\1|\2|\3|\4).$'
由于您不能在字符类(例如[^\1|\2]
)中添加反向引用,因此必须使用否定的前瞻 - (?!foo)
。这是PCRE功能,因此您需要进行-P
切换。
当然,一个10字符串的模式将更长,但是有一个较短的方法,在前瞻中使用可变长度的任何匹配('。*'):
grep -P '^(.)(?!.*\1)(.)(?!.*\2)(.)(?!.*\3)(.)(?!.*\4)(.)(?!.*\5).$'
在阅读了斯蒂芬·夏泽拉斯的启发性答案后,我意识到可以通过grep的-v
开关使用类似的简单模式:
(.).*\1
由于检查一次执行一个字符,因此将检查是否有给定的字符后跟零个或多个字符(.*
),然后匹配后向引用。 -v
反转,仅打印与此模式不匹配的内容。这使得反向引用更加有用,因为它们不能与字符类相抵消,并且很明显:
grep -v '\(.\).*\1'
可以识别具有唯一字符的任意长度的字符串,而:
grep -P '(.)(?!.*\1)'
不会,因为它将匹配具有唯一字符的任何后缀(例如,abcabc
由于abc
结尾而匹配,并且aaaa
由于结尾而匹配a
-因此匹配任何字符串)。这是由于环视宽度为零(它们不消耗任何东西)引起的复杂情况。
(.)(?!.*\1)(.)(?!.*\2)(.)(?!.*\3)(.)(?!\4).
如果您不需要在regex中完成全部操作,则可以分两步进行:首先匹配所有10个字母的单词,然后过滤它们以确保唯一性。我知道如何执行此操作的最短方法是在Perl中:
perl -nle 'MATCH:while(/\W(\w{10})\W/g){
undef %seen;
for(split//,$1){next MATCH if ++$seen{$_} > 1}
print
}' your_file
请注意其他\W
锚点,以确保仅匹配长度恰好为10个字符的单词。
其他人建议,如果不对某些实际上不是正则的正则表达式系统进行各种扩展,就不可能做到这一点。但是,由于您要匹配的语言是有限的,因此很显然它是常规的。对于4个字母的3个字母,这很容易:
(abc|abd|acb|acd|bac|bad|bcd|bdc|cab|cad|cbd|cdb|dab|dac|dbc|dcb)
显然,这会急于增加更多的字母和更大的字母。:-)
GNU的选项--perl-regexp
(简称-P
)grep
使用更强大的正则表达式,其中包括前瞻模式。以下模式查找该字母在单词其余部分中未出现的每个字母:
grep -Pow '((\w)(?!\w*\g{-1})){10}'
但是,运行时行为非常糟糕,因为它\w*
可能具有几乎无限的长度。可以限制为\w{,8}
,但也可以检查10个字母以内的单词限制。因此,以下模式首先检查正确的字长:
grep -Pow '(?=\w{10}\b)((\w)(?!\w*\g{-1})){10}'
作为测试文件,我使用了一个≈500 MB的大文件:
更新:
对于非贪婪运算符(\w*?
)或所有格运算符((...){10}+
),我找不到运行时行为的重大变化。似乎稍微快了一点,替换了option -w
:
grep -Po '\b(?=\w{10}\b)((\w)(?!\w*\g{-1})){10}\b'
将grep从2.13版本更新到2.18更为有效。测试文件只花了大约6 s。
\w{,8}?
)可以帮助进行某种类型的输入(尽管不是很明显)。很好的\g{-1}
解决GNU grep错误。
\g{-1}
,因为它使模式更加独立于位置。以这种形式,它可以用作较大图案的一部分。
Perl解决方案:
perl -lne 'print if (!/(.)(?=$1)/g && /^\w{10}$/)' file
但它不起作用
perl -lne 'print if (!/(.)(?=\1)/g && /^\w{10}$/)' file
要么
perl -lne 'print if ( /(.)(?!$1)/g && /^\w{10}$/)' file
经过perl v5.14.2和v5.18.2测试