正则表达式至少包含两个唯一字符

时间:2011-03-17 15:37:30

标签: .net regex

我需要一个正则表达式来验证密码。

您可以假设输入仅包含小写字母a-z。 限制是必须至少有两个独特的字母。

请注意,我的意思是独特的字符;不只是两个不同的特征。 (如果这有道理?)

例如,这些都可以:

abc
abbc
aabc

这些应该失败:

aabb    //There are no unique letters.  The 'a' appears twice.
aabbcc  //There are no unique letters
abab    //There are no unique letters
abb     //There is only one unique letter

我知道只是通过字母循环将是一个更容易的解决方案,但不幸的是我需要这个作为正则表达式。

我一直在尝试各种前瞻等组合但到目前为止没有运气。

修改

我取得了一些进展。我现在可以检查一个独特的字母,同时使用负面外观和负面前瞻。像这样:

(.)(?<!\1.+)(?!.*\1)

我预计我可以把它放两次,但它不起作用。类似于:

(.)(?<!\1.+)(?!.*\1)(.)(?<!\2.+)(?!.*\2)

6 个答案:

答案 0 :(得分:9)

这个似乎来做这个伎俩:

using System;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      string[] values = { "abc", "abbc", "aabc", "aabb", "aabbcc", "abab", "abb" };
      string pattern = @"(?:(.)(?<=^(?:(?!\1).)*\1)(?=(?:(?!\1).)*$).*?){2,}";
      foreach (string value in values) {
         if (Regex.IsMatch(value, pattern)) {
            Console.WriteLine("{0} valid", value);
         }
         else {   
            Console.WriteLine("{0} invalid", value);
         }
      }
   }
}

产生输出:

abc valid
abbc valid
aabc valid
aabb invalid
aabbcc invalid
abab invalid
abb invalid

可以在Ideone上看到:http://ideone.com/oU7a0

但正则表达式是一件可怕的事情!

如果你愿意,我稍后会解释(我现在必须去)。


修改

好的,这是对这个monstorisity的解释(我希望!):

(?:                # start non-capture group 1
  (.)              #   capture any character, and store it in match group 1
  (?<=             #   start posisitve look-behind
    ^              #     match the start of the input string
    (?:(?!\1).)    #     if what is captured in match group 1 cannot be seen ahead, match the character
    *              #     repeat the previous zero or more times 
    \1             #     this is the `(.)` we're looking at
  )                #   end posisitve look-behind
  (?=              #   start posisitve look-ahead
    (?:(?!\1).)    #     if what is captured in match group 1 cannot be seen ahead, match the character
    *              #     repeat the previous zero or more times 
    $              #     match the end of the input string
  )                #   emd posisitve look-ahead
  .*?              #   match zero or more characters, un-greedy
)                  # end non-capture group 1
{2,}               # match non-capture group 1 at least 2 times

用简单的英语,有点像这样:

+---                                                      # (
| match and group any character `C` at position `P`,      # (.)
|                                                         #
| and look from the start of the input all that way       #
| to `P` where there can't be any character like `C`      # (?<=^(?:(?!\1).)*\1)
| in between.                                             #
|                                                         #
| Also look from position `P` all the way to the end      #
| of the input where there can't be any character `C`     # (?=(?:(?!\1).)*$)
| in bewteen.                                             #
+---                                                      #
| if the previous isn't matched, consume any character    #
| un-greedy zero or more times (but the previous block    # .*?
| is always tried before this part matched the character) #
+---                                                      # )
|                                                         #
|                                                         # 
+----> repeat this at least 2 times                       # {2,}

编辑II

假设Kobi(K)位于字符串"abcYbacZa"的顶部,包含9个字符:

              K
+---+---+---+---+---+---+---+---+---+ 
| a | b | c | Y | b | a | c | Z | a |
+---+---+---+---+---+---+---+---+---+

^   ^   ^   ^   ^   ^   ^   ^   ^
|   |   |   |   |   |   |   |   |
p0  p1  p2  p3  p4  p5  p6  p7  p8

和Kobi想知道索引4中的字符Y在整个字符串中是否唯一。 Kobi和他信任的奴才一起旅行,让我们打电话给他的仆人Bart(B),他从Kobi获得以下任务:

第1步

Kobi:Bart,回到输入的开头:regex: (?<=^ ... )(Bart将从位置0开始:p0,这是第一个a之前的空字符串) ;

B             K
+---+---+---+---+---+---+---+---+---+ 
| a | b | c | Y | b | a | c | Z | a |
+---+---+---+---+---+---+---+---+---+

^   ^   ^   ^   ^   ^   ^   ^   ^   ^
|   |   |   |   |   |   |   |   |   |
p0  p1  p2  p3  p4  p5  p6  p7  p8  p9

第2步

然后向前看并确定你是否看到我在比赛组1 regex: (.)中记住的字符,即Y字符。所以在位置p0,Bart执行regex: (?!\1)。对于p0,这是正确的:Bart会看到字符a,因此Y仍然是唯一的。 Bart前进到下一个位置p1,在那里他看到了角色b:一切都很好,他又向p2迈出了一步,依此类推:regex: (?:(?!\1).)*

第3步

巴特现在处于p3位置:

            B K
+---+---+---+---+---+---+---+---+---+ 
| a | b | c | Y | b | a | c | Z | a |
+---+---+---+---+---+---+---+---+---+

^   ^   ^   ^   ^   ^   ^   ^   ^   ^
|   |   |   |   |   |   |   |   |   |
p0  p1  p2  p3  p4  p5  p6  p7  p8  p9

当他现在向前看时,他确实看到了字符Y,所以regex: (?!\1)失败了。但是Kobi仍在使用的那个角色被\1中的最后一个regex: (?:(?!\1).)*\1使用。p3。所以在Y之后,巴特自豪地告诉Kobi:<是>,是的,p4在我们身后看时确实是独一无二的!“。 “好”,Kobi说,“现在做同样的事情,但不要向后看,向前看,一直到我们站在这个字符串的末尾,然后做它很快!“

第4步

巴特抱怨一些难以理解的东西,但是在 K B +---+---+---+---+---+---+---+---+---+ | a | b | c | Y | b | a | c | Z | a | +---+---+---+---+---+---+---+---+---+ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ | | | | | | | | | | p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 开始他的旅程:

b

当他向前看时,他会看到角色regex: (?!\1),因此b成立,regex: .消耗角色regex: (?:(?!\1).)*。巴特重复这个零次或多次regex: (?:(?!\1).)*$一直到输入Y的末尾。他再次回到Kobi并告诉他:“是的,展望未来时,(.)(?<=^(?:(?!\1).)*\1)(?=(?:(?!\1).)*$) 仍然是独一无二的!”

所以,正则表达式:

regex: .*?

将匹配字符串中唯一的单个字符。

第5步

上面的正则表达式不能简单地重复2次或更多次,因为那只能匹配2个连续的唯一字符。因此,我们会在其后添加(.)(?<=^(?:(?!\1).)*\1)(?=(?:(?!\1).)*$).*? ^^^

b
在这种情况下,

将消耗字符ac(?:(.)(?<=^(?:(?!\1).)*\1)(?=(?:(?!\1).)*$).*?){2,} ^^^ ^^^^^ ,并重复 正则表达式两次或更多次:

"YbacZ"

所以最后,子串"abcYbacZa"Y匹配,因为Z和{{1}}是唯一的。

你有它,就像馅饼一样简单,对吗? :)

答案 1 :(得分:6)

这是另一种方法 - 这个想法与Bart的方法相反 - 我匹配重复的字符(这有点容易),并计算其余的:

^
(?>                 # Possessive group - do not backtrack!
    (.)             # Match a duplicated character
       (?:
         (?=.*\1)       # It can have a duplicate after itself
         |
         (?<=\1.+)      # Or it already had one
       )
    |               # Or, it isn't a duplicated character at all
    (?<Unique>.)    # Capture it as a unique character.
)+
$
(?<-Unique>){2}     # After we're done, check there were at least
                    # two unique characters

这里的主要技巧是使用possessive group - 它确保重复的字符永远不会回溯,所以我知道下一个点只会捕获一个非重复的字符。

在.Net中,每个组的捕获都会添加到堆栈中。 (?<-Unique>)从堆栈中弹出捕获,如果它为空则失败。它提供了一种很好的方法来计算我们拥有多少捕获量。

答案 2 :(得分:2)

没有正则表达式...... LINQ!

"aabbcc".Distinct().Count()

好的,Linq也没有。但是这(纠正,现在我理解“不同”和“独特”之间的区别)会起作用:

bool hasAtLeastOneUniqueChar = 
    (from c in "aabbc"
    group c by c into grp
    where grp.Count() == 1
    select grp.Key).Any()

答案 3 :(得分:2)

如果字母表是有限的(让我们称之为LETTERS),可以用这个伪代码构造一个正则表达式:

R := "";
for each (X in LETTERS) for each (Y in LETTERS) if (X!=Y)
    R += "|(^(?=[^X]*X[^X]*$)([^Y]*Y[^Y]*$))"
        .replaceAll("X",X).replaceAll("Y",Y);
R := R.substring(1);

R仅使用常规构造和正向前瞻(相当于常规语言的交集,并且仍然是常规语言),证明所讨论的语言确实是常规的

基本上,这为每对X和Y创建了包含“只包含一个X和正好一个Y”的常规语言的联合。

答案 4 :(得分:2)

我在这里提出这个非.NET答案,因为我整个上午都在考虑它......

可变长度回顾对于其他解决方案至关重要,但.NET似乎很少支持它(到目前为止)。那么如何只用前瞻来做呢?这似乎适用于Perl 5.12:

/(?x)
    ^ (?&BALANCED) (?&RIGHT1) (?&BALANCED) (?&RIGHT1)
    (?(DEFINE)
            (?<RIGHT1>  (.) (?! .* \g{-1} ) )
            (?<LEFT2>  (.) (?= .* \g{-1}) (?! .* \g{-1} .* \g{-1} ) )
            (?<OTHER>   (.) (?= .* \g{-1} .* \g{-1} ) )
            (?<BALANCED> (?&OTHER)* (?> (?&LEFT2) (?&BALANCED) (?&RIGHT1) (?&OTHER)* )* )
    )
/

基本思想是将字符的出现次数分为最后一个(RIGHT1),倒数第二个(LEFT2)等。唯一字符只有最后一个出现,非唯一字符也有倒数第二个出现 - 因此我们要确保至少有两个“最后出现次数”多于“倒数第二次出现”。

根据定义,倒数第二次出现在最后一次出现之前,因此我们可以假设倒数第二次和最后一次出现分别是左括号和右括号,其他出现次数作为填充。然后我们有一个字符串,其左右括号至少与右括号一样多,我们可以确定每个左括号都匹配(因为它和每个左括号各自'带来'右括号甚至更远到右)。

因此我们使用标准匹配的括号识别器,使用递归原子匹配,并查找两个不匹配的右括号,也就是最后一次出现。

请注意,给定的倒数第二次出现不一定与同一个字符的最后一次出现次数匹配。实际上,它可能匹配唯一字符的最后和唯一出现,并且不匹配的最后一次出现可能会从非唯一字符“遗留” - 正则表达式无法为您找到唯一字符,匹配只是保证它们存在。

答案 5 :(得分:2)

根据大众的要求,我提出了我的工作答案。

(.)(?<!\1.+)(?!.*\1).*(.)(?<!\2.+)(?!.*\2)

解释一下:

(.)                    # match any character...
(?<!\1.+)              # ...which does not already exist in the input...
(?!.*\1)               # ..and does not exist later on in the input
                       # We have now found one unique character.

.*                     # allow for any number of random characters in the middle

(.)(?<!\2.+)(?!.*\2)   # Find a second unique character, 
                       # using the same technique.

- 非常感谢Kobi在评论中为我完成此事。