所有10个字母词的正则表达式,带有唯一字母


23

我正在尝试编写一个正则表达式,以显示所有10个字符长的单词,并且所有字母都没有重复。

到目前为止,我已经

grep --colour -Eow '(\w{10})'

这是问题的第一部分。我将如何检查“唯一性”?除了需要使用反向引用之外,我真的没有任何线索。


1
这必须用正则表达式完成吗?
Hauke Laging

我正在练习正则表达式,所以最好是:)
Dylan Meeus 2014年

3
我不相信您可以使用计算机科学风格的正则表达式来做到这一点:您想要的东西需要“记忆”前​​面匹配的字符是什么,而正则表达式则没有。就是说,您也许可以使用反向引用和PCRE样式匹配可以完成的非正则表达式来完成此任务。
Bruce Ediger 2014年

3
@BruceEdiger只要在语言(26)和字符串(10)中有有限数量的字符,就可以做到。它的状态很多,但没有什么可以使它不成为常规语言。

1
您是说“所有英语单词...”吗?您是否要包括那些拼写有连字符和撇号的人(姻亲,不要)?您是要包括咖啡馆,朴素,立面之类的词吗?
hippietrail 2014年

Answers:


41
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例如将被匹配,因为所有字符都不同,即使存在两个Bs,一个小写和一个大写(用于-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与其重音之间存在一个字边界)。

1
太棒了 \w虽然不匹配-
Graeme

@Stephane您可以发布对后两个表达式的简短说明。
mkc 2014年

有时候,环顾四周似乎是解决所有过去用RE无法实现的事情的解决方案。
Barmar 2014年

1
@Barmar使用正则表达式仍然是不可能的。“正则表达式”是一种数学构造,它仅明确允许某些构造,即文字字符,字符类以及'|','(...)','?','+'和'*'运算符。任何使用不是上述之一的运算符的所谓“正则表达式”实际上不是正则表达式。
Jules 2014年

1
@Jules这是unix.stackexchange.com,而不是math.stackexchange.com。数学RE的都无关紧要在这种情况下,我们在谈论你使用grep,PCRE等使用各种RE的
Barmar

12

好的...这是五个字符串的笨拙方式:

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-因此匹配任何字符串)。这是由于环视宽度为零(它们不消耗任何东西)引起的复杂情况。


做得好!不过,这只能与Q中的一个结合使用。
Graeme 2014年

1
我相信,如果您的正则表达式引擎允许变长的负前瞻,您可以简化第一个:(.)(?!.*\1)(.)(?!.*\2)(.)(?!.*\3)(.)(?!\4).
Christopher Creutzig 2014年

@ChristopherCreutzig:绝对不错。我添加了它
。– goldilocks

6

如果您不需要在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个字符的单词。


谢谢,但我想将它作为正则表达式oneliner :)
Dylan Meeus

4

其他人建议,如果不对某些实际上不是正则的正则表达式系统进行各种扩展,就不可能做到这一点。但是,由于您要匹配的语言是有限的,因此很显然它是常规的。对于4个字母的3个字母,这很容易:

(abc|abd|acb|acd|bac|bad|bcd|bdc|cab|cad|cbd|cdb|dab|dac|dbc|dcb)

显然,这会急于增加更多的字母和更大的字母。:-)


我不得不对此表示反对,因为这实际上是一个可行的答案。虽然这实际上可能是任何人编写正则表达式的效率最低的方法:P
Dylan Meeus 2014年

4

GNU的选项--perl-regexp(简称-Pgrep使用更强大的正则表达式,其中包括前瞻模式。以下模式查找该字母在单词其余部分中未出现的每个字母:

grep -Pow '((\w)(?!\w*\g{-1})){10}'

但是,运行时行为非常糟糕,因为它\w*可能具有几乎无限的长度。可以限制为\w{,8},但也可以检查10个字母以内的单词限制。因此,以下模式首先检查正确的字长:

grep -Pow '(?=\w{10}\b)((\w)(?!\w*\g{-1})){10}'

作为测试文件,我使用了一个≈500 MB的大文件:

  • 第一种模式:≈43 s
  • 后期模式:≈15秒

更新:

对于非贪婪运算符(\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错误。
斯特凡Chazelas

@StephaneChazelas:感谢您的反馈。我还尝试了非贪婪和所有格运算符,但没有发现运行时行为发生重大变化(2.13版)。2.18版要快得多,我至少可以看到一点改进。两种版本均存在GNU grep错误。无论如何,我更喜欢相对引用\g{-1},因为它使模式更加独立于位置。以这种形式,它可以用作较大图案的一部分。
Heiko Oberdiek

0

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测试


第一个和第三个不执行任何操作,第二个输出不超过2个连续空格的10行或以上字符的任何行。pastebin.com/eEDcy02D
manatwork 2014年

它可能是perl版本。使用v5.14.2和v5.18.2进行了测试

我在Linux上的v5.14.1和Cygwin上的v5.14.2尝试了它们。两者的行为都类似于我之前链接的pastebin示例。
manatwork 2014年

第一行适用于我所提到的perl版本。后者应该工作,因为它们是相同的,但是没有。经常请注意,一些贪婪的表情是高度实验性的。

重新测试了您的最新更新。仅第二个正确输出。(但是,单词必须排成一行,而问题在于匹配单词,而不是整行。)
manatwork 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.