2010-01-27 33 views
10

Tengo el siguiente en C#:La combinación de estas dos expresiones regulares en una sola

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

Quiero comprobar si el parámetro s contiene al menos un carácter alfabético y un dígito y que escribió el método anterior para hacer asi que.

¿Pero hay alguna manera de combinar las dos expresiones regulares ("[a-zA-Z]+" y "\d+") en una?

+2

Si solo quiere verificar que al menos una de ellas exista, no use el operador '+' para hacer coincidir una cadena innecesariamente más larga. – kennytm

+2

Creo que la versión original es más elegante y legible que la mayoría de las respuestas. – Kobi

+3

Me parece que este método se debe llamar ** HasAlphaAndNumeric **. Solo está comprobando que * contiene * uno de cada uno; el resto de los personajes podría ser cualquier cosa, o nada. Por ejemplo, 'A1' y'! @ # 1%^& A() _ 'ambos pasan, ¿es eso lo que pretendías? –

Respuesta

9
@"^(?=.*[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) 

utiliza dos positive lookaheads para asegurar que encuentre una letra y un número antes sucesivas a. Agregue el ^ para intentar solo mirar hacia adelante una vez, desde el inicio de la cadena. De lo contrario, el motor regexp trataría de coincidir en cada punto de la cadena.

2
private static readonly Regex _regex = new Regex(
    @"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$", RegexOptions.Compiled); 

public static bool IsAlphaAndNumeric(string s) 
{ 
    return _regex.IsMatch(s); 
} 

Si quiere ignorar el caso puede usar RegexOptions.Compiled | RegexOptions.IgnoreCase.

+0

+1 Pero OP quiere que no distinga entre mayúsculas y minúsculas. – Amarghosh

+0

Para OP, búsqueda positiva de búsqueda hacia adelante en esta página: http://msdn.microsoft.com/en-us/library/1400241x(VS.85).aspx –

+1

Esta expresión regular solo coincide con cadenas que contienen letras minúsculas Y mayúsculas. .. –

3

Puede usar [a-zA-Z].*[0-9]|[0-9].*[a-zA-Z], pero solo lo recomendaría si el sistema que estaba usando solo aceptara una sola expresión regular. No puedo imaginar que esto sea más eficiente que dos patrones simples sin alternancia.

3

No es exactamente lo que quieres, pero digamos que tengo más tiempo. Lo siguiente debería funcionar más rápido que regex.

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; 
    } 

Por qué es rápido de verlo. A continuación se muestra el generador de cadena de prueba. Genera 1/3 del conjunto cadena completamente correcta y 2/3 anuncio incorrecto. En 2/3 1/2 están todos los alfos y la otra mitad son todos los dígitos.

static IEnumerable<string> GenerateTest(int minChars, int maxChars, int setSize) { 
     string letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 
     string numbers = "";    
     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); 
     } 
    } 

El siguiente es darin two solution. Uno ha compilado y el otro es una versión no compilada.

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); 
    } 

siguiente es el principal del bucle de prueba

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(); 
    } 

A continuación se presenta resultados

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

Así que la primera solución era la mejor. Algunos más Resultado en modo de lanzamiento y siguiendo las especificaciones

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) 

he comprobado una vez más con la bandera RegexOptions.IgnoreCase. resto del parámetro igual al anterior

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) 

Después gnarf mencionar que había un problema con mi algo que estaba verificando si la cadena consiste solamente en letra y dígitos por lo que lo cambio y ahora es comprobar que muestran cadena tiene al menos con un carácter y un dígito.

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; 
    } 

Resultados

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) 

mina es rápido por un factor grande.

+0

¿Alguna justificación de por qué esto sería más rápido que regex? – Amarghosh

+0

Y si * es * más rápido, la diferencia será trivial. Tendría que probar millones de cadenas en un círculo cerrado para que esto valga la pena. –

+0

He publicado el resultado del rendimiento. En mi respuesta. Dijo que tienes tiempo. – affan

10

para C# con LINQ:

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

¡Por favor, alguien marque esto como la respuesta! – Benjol

+0

Esto requerirá dos repeticiones completas de los caracteres de cadena en el peor de los casos. – affan

+0

@affan - en el peor de los casos, debe verificar cada carácter dos veces; esto es verdad para cada solución posible. Ya sea que ocurra en uno o dos bucles, no hay diferencia, aparte de crear otro iterador de caracteres: para una cadena en memoria, esta es una pequeña sobrecarga a lo sumo. – Kobi

0

El siguiente es no sólo más rápido que las otras construcciones de búsqueda hacia delante, es también (en mis ojos) más cerca de las necesidades:

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

En mi (prueba evidentemente cruda) se ejecuta en aproximadamente la mitad del tiempo requerido por las otras soluciones de expresiones regulares, y tiene la ventaja de que no le importará las nuevas líneas en la cadena de entrada. (Y si por alguna razón debería, es obvio cómo incluirlo).

Aquí es cómo (y por qué) funciona:

Paso 1: Se ajusta a un único carácter (llamémosla c) que es un número o una letra.
Paso 2: Hace un seguimiento para verificar si c es un número. Si es así:
Paso 2.1: Permite un número ilimitado de caracteres que no son una letra, seguidos de una sola letra. Si esto coincide, tenemos un número (c) seguido de una letra.
Paso 2.2: Si c no es un número, debe ser una letra (de lo contrario, no habría sido coincidente). En este caso, permitimos un número ilimitado de no dígitos, seguidos de un solo dígito. Esto significa que tenemos una carta (c) seguida de un número.

+0

Lógicamente, esto es similar a la respuesta de Anonymous, pero más complejo. ¿Estás seguro de que esto es rápido? en caso de falla, ¿no probaría todas y cada una de las letras? (Por ejemplo, 600 'X's) – Kobi

+0

Al igual que con la respuesta de @ affan, es muy poco probable que valga la pena el esfuerzo de todos modos. La gente se preocupa demasiado por el rendimiento de la expresión regular. –

+0

@La respuesta anónima hará coincidir cualquier carácter antes de la primera letra dos veces si la primera rama falla, ya que la segunda rama realiza un retroceso desde el principio. Si puede estar razonablemente seguro de que la cadena de entrada tiene una letra cerca del comienzo, dará como resultado el mismo rendimiento (y después de reemplazar los puntos, incluso con el mismo significado). - también gracias por poner el caret faltante - no tengo idea de cómo lo maté durante la publicación;) –

Cuestiones relacionadas