【问题标题】:Combining these two Regular Expressions into one将这两个正则表达式合二为一
【发布时间】:2010-01-27 09:19:49
【问题描述】:

我在 C# 中有以下内容:

public static bool IsAlphaAndNumeric(string s)
{
    return Regex.IsMatch(s, @"[a-zA-Z]+") 
        && Regex.IsMatch(s, @"\d+");
}

我想检查参数s是否包含至少一个字母字符一个数字,我编写了上述方法。

但是有没有一种方法可以将两个正则表达式("[a-zA-Z]+""\d+")合并为一个?

【问题讨论】:

  • 如果您只想验证其中至少有 1 个存在,请不要使用 + 运算符来匹配不必要的较长字符串。
  • 我认为原始版本比大多数答案更优雅和可读。
  • 在我看来这个方法应该被称为 HasAlphaAndNumeric。您只是在检查它是否包含其中之一;其余的角色可以是任何东西,也可以什么都不是。例如,A1!@#1%^&A()_ 都通过了——这是您的意图吗?
  • @Alan Moore:是的,你是对的;你建议的方法名比我的好。

标签: c# regex


【解决方案1】:

对于带有 LINQ 的 C#:

return s.Any(Char.IsDigit) && s.Any(Char.IsLetter);

【讨论】:

  • 在更糟糕的情况下,这将需要字符串字符的两次完整迭代。
  • @affan - 在最坏的情况下,您必须检查每个字符两次;这适用于所有可能的解决方案。它是否发生在一个或两个循环中没有区别,除了创建另一个字符迭代器 - 对于内存中的字符串,这最多是一个很小的开销。
  • @affan - 请在投票前阅读说明,并检查原始功能的作用。它说“至少一个字母字符和一个数字”。正如@gnarf 向您解释的那样,您是错误代码的人。
  • 如果 OP 不致力于使用正则表达式,这可能是最好的建议。
  • +1 虽然这是一种非常简洁明了的方式,但我无法接受,因为我要求的是正则表达式。由于展示了 LINQ 替代方案,我仍然给了你一个 +1。
【解决方案2】:
@"^(?=.*[a-zA-Z])(?=.*\d)"

 ^  # From the begining of the string
 (?=.*[a-zA-Z]) # look forward for any number of chars followed by a letter, don't advance pointer
 (?=.*\d) # look forward for any number of chars followed by a digit)

使用两个positive lookaheads 以确保在成功之前找到一个字母和一个数字。您添加^ 只尝试从字符串的开头向前看一次。否则,正则表达式引擎将尝试匹配字符串中的每个点。

【讨论】:

    【解决方案3】:

    您可以使用[a-zA-Z].*[0-9]|[0-9].*[a-zA-Z],但我只推荐您使用的系统只接受一个正则表达式。我无法想象这会比没有交替的两个简单模式更有效。

    【讨论】:

      【解决方案4】:

      这不是您想要的,但可以说我有更多时间。以下应该比正则表达式更快。

          static bool IsAlphaAndNumeric(string str) {
              bool hasDigits = false;
              bool  hasLetters=false;
      
              foreach (char c in str) {
                  bool isDigit = char.IsDigit(c);
                  bool isLetter = char.IsLetter(c);
                  if (!(isDigit | isLetter))
                      return false;
                  hasDigits |= isDigit;
                  hasLetters |= isLetter;
              }
              return hasDigits && hasLetters;
          }
      

      为什么它很快让我们检查一下。 以下是测试字符串生成器。它生成 1/3 的集合完全正确的字符串和 2/3 的广告不正确。在 2/3 中,1/2 是所有字母,另一半是所有数字。

          static IEnumerable<string> GenerateTest(int minChars, int maxChars, int setSize) {
              string letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
              string numbers = "0123456789";            
              Random rnd = new Random();
              int maxStrLength = maxChars-minChars;
              float probablityOfLetter = 0.0f;
              float probablityInc = 1.0f / setSize;
              for (int i = 0; i < setSize; i++) {
                  probablityOfLetter = probablityOfLetter + probablityInc;
                  int length = minChars + rnd.Next() % maxStrLength;
                  char[] str = new char[length];
                  for (int w = 0; w < length; w++) {
                      if (probablityOfLetter < rnd.NextDouble())
                          str[w] = letters[rnd.Next() % letters.Length];
                      else 
                          str[w] = numbers[rnd.Next() % numbers.Length];                    
                  }
                  yield return new string(str);
              }
          }
      

      以下是darin 两个解决方案。一个已经编译,另一个是未编译版本。

      class DarinDimitrovSolution
      {
          const string regExpression = @"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$";
          private static readonly Regex _regex = new Regex(
              regExpression, RegexOptions.Compiled);
      
          public static bool IsAlphaAndNumeric_1(string s) {
              return _regex.IsMatch(s);
          }
          public static bool IsAlphaAndNumeric_0(string s) {
              return Regex.IsMatch(s, regExpression);
          }
      

      以下是测试循环的主要部分

          static void Main(string[] args) {
      
              int minChars = 3;
              int maxChars = 13;
              int testSetSize = 5000;
              DateTime start = DateTime.Now;
              foreach (string testStr in
                  GenerateTest(minChars, maxChars, testSetSize)) {
                  IsAlphaNumeric(testStr);
              }
              Console.WriteLine("My solution : {0}", (DateTime.Now - start).ToString());
      
              start = DateTime.Now;
              foreach (string testStr in
                  GenerateTest(minChars, maxChars, testSetSize)) {
                  DarinDimitrovSolution.IsAlphaAndNumeric_0(testStr);
              }
              Console.WriteLine("DarinDimitrov  1 : {0}", (DateTime.Now - start).ToString());
      
              start = DateTime.Now;
              foreach (string testStr in
                  GenerateTest(minChars, maxChars, testSetSize)) {
                  DarinDimitrovSolution.IsAlphaAndNumeric_1(testStr);
              }
              Console.WriteLine("DarinDimitrov(compiled) 2 : {0}", (DateTime.Now - start).ToString());
      
              Console.ReadKey();
          }
      

      以下是结果

      My solution : 00:00:00.0170017    (Gold)
      DarinDimitrov  1 : 00:00:00.0320032  (Silver medal) 
      DarinDimitrov(compiled) 2 : 00:00:00.0440044   (Gold)
      

      所以第一个解决方案是最好的。 更多的结果是发布模式和遵循规范

         int minChars = 20;
         int maxChars = 50;
         int testSetSize = 100000;
      
      My solution : 00:00:00.4060406
      DarinDimitrov  1 : 00:00:00.7400740
      DarinDimitrov(compiled) 2 : 00:00:00.3410341 (now that very fast)
      

      我再次检查了 RegexOptions.IgnoreCase 标志。其余参数同上

      My solution : 00:00:00.4290429 (almost same as before)
      DarinDimitrov  1 : 00:00:00.9700970 (it have slowed down )
      DarinDimitrov(compiled) 2 : 00:00:00.8440844 ( this as well still fast but look at .3 in last result)
      

      在 gnarf 提到我的算法存在问题后,它正在检查字符串是否仅由字母和数字组成,因此我对其进行了更改,现在它检查字符串显示是否至少有一个字符和一个数字。

          static bool IsAlphaNumeric(string str) {
              bool hasDigits = false;
              bool hasLetters = false;
      
              foreach (char c in str) {
                  hasDigits |= char.IsDigit(c);
                  hasLetters |= char.IsLetter(c);
                  if (hasDigits && hasLetters)
                      return true;
              }
              return false;
          }
      

      结果

      My solution : 00:00:00.3900390 (Goody Gold Medal)
      DarinDimitrov  1 : 00:00:00.9740974 (Bronze Medal)
      DarinDimitrov(compiled) 2 : 00:00:00.8230823 (Silver)
      

      我的速度很快。

      【讨论】:

      • 任何理由为什么这会比正则表达式更快?
      • 如果它更快,差异将是微不足道的。您必须在一个紧密的循环中测试数百万个字符串才能使这项工作值得。
      • 另外-您的版本检查所有字符是否都是数字或数字... OP 仅测试它是否具有字母和数字字符...也许尝试调整循环以返回 true一旦找到其中一个,它可能会赶上正则表达式编译的版本(不检查整个字符串,它们会扫描一个字母,然后扫描一个数字。最糟糕的测试用例是一个字符串最后包含一个字母,没有数字,这将在正则表达式引擎中花费最多的时间
      • 维持这种怪物的时间表现如何?
      • @affan - 为什么要保留不准确的版本?您的答案上半部分中提到的所有功能都没有满足 OP 的要求,我只是使用返回正确答案的方法重写/测试它。
      【解决方案5】:
      private static readonly Regex _regex = new Regex(
          @"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$", RegexOptions.Compiled);
      
      public static bool IsAlphaAndNumeric(string s)
      {
          return _regex.IsMatch(s);
      }
      

      如果你想忽略大小写,你可以使用RegexOptions.Compiled | RegexOptions.IgnoreCase

      【讨论】:

      • 对于 OP,在此页面上查找正向前瞻:msdn.microsoft.com/en-us/library/1400241x(VS.85).aspx
      • 这个正则表达式只匹配包含小写和大写字母的字符串...
      • 另外,它需要 RegexOptions.Singleline 或不匹配在三个必需字符(大写字母、小写字母和数字)之一之前包含换行符的字符串
      • 如果您通过了RegexOptions.IgnoreCase,则无需提前(?=.*[A-Z])
      【解决方案6】:

      以下不仅比其他前瞻构造更快,而且(在我看来)更接近要求:

      [a-zA-Z\d]((?<=\d)[^a-zA-Z]*[a-zA-Z]|[^\d]*\d)
      

      在我的(不可否认的粗略测试)中,它的运行时间大约是其他正则表达式解决方案所需时间的一半,并且它的优点是它不会关心输入字符串中的换行符。 (如果出于某种原因它应该包含它,那么如何包含它是显而易见的)。

      以下是它的工作原理(以及原因):

      第 1 步:匹配单个字符(我们称之为 c),即数字或字母。
      第 2 步:它会检查 c 是否为数字。如果是这样:
      步骤 2.1:它允许无限数量的非字母字符,后跟单个字母。如果匹配,我们有一个数字 (c),后跟一个字母。
      步骤 2.2:如果 c 不是数字,它必须是字母(否则不会匹配)。在这种情况下,我们允许无限数量的非数字,后跟一个数字。这意味着我们有一个字母 (c) 后跟一个数字。

      【讨论】:

      • 从逻辑上讲,这类似于 Anonymous 的回答,但更复杂。你确定这很快吗?如果失败,它不会测试每个匹配的字母吗? (例如,600 个“X”)
      • 与@affan 的回答一样,无论如何这都不值得付出努力。人们过于担心正则表达式的性能。
      • 如果第一个分支失败,@Anonymous 答案将匹配第一个字母之前的任何字符两次,因为第二个分支会回溯到最开始。如果您可以合理地确定输入字符串有一个接近开头的字母,它将导致相同的性能(并且在替换点之后即使具有相同的含义)。 -- 也感谢您添加丢失的插入符号-不知道我在发布过程中是如何杀死它的;)
      • 哦,担心正则表达式的性能:我来这里是为了好玩,不是为了赚钱;)
      • 您可以通过在正则表达式前面加上^(?&gt;[^A-Za-z0-9]*) 来完全避免回溯问题。完成此操作后,我认为后视将不再真正发挥作用。为了获得最佳性能,我会选择^(?&gt;[^A-Za-z0-9]*)(?:[a-zA-Z](?&gt;[^0-9]*)[0-9]|[0-9](?&gt;[^A-Za-z]*)[a-zA-Z])。如果我担心性能,那就是...... ;)
      猜你喜欢
      • 1970-01-01
      • 2015-07-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-13
      • 2022-08-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多