2010-01-27 4 views
10

Ich habe im Anschluss an die in C#:die Kombination dieser beiden Reguläre Ausdrücke in einer

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

ich überprüfen möchten, ob Parameter s enthält mindestens ein alphabetisches Zeichen und eine Ziffer und ich schrieb das obige Verfahren zu tun damit.

Aber gibt es eine Möglichkeit, die zwei regulären Ausdrücke ("[a-zA-Z]+" und "\d+") in einem zu kombinieren?

+2

Wenn Sie nur bestätigen wollen, dass mindestens 1 davon vorhanden ist, verwenden Sie den Operator '+' nicht, um eine unnötig lange Zeichenfolge zu finden. – kennytm

+2

Ich denke, die ursprüngliche Version ist eleganter und lesbarer als die meisten Antworten. – Kobi

+3

Scheint mir, diese Methode sollte ** HasAlphaAndNumeric ** genannt werden. Du überprüfst nur, dass es * einen von jedem * enthält; Der Rest der Charaktere könnte alles oder nichts sein. Zum Beispiel, 'A1' und'! @ # 1%^& A() _ 'beide bestanden - ist das, was Sie beabsichtigten? –

Antwort

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) 

Verwendet zwei positive lookaheads es findet einen Brief zu gewährleisten, und eine Zahl vor succeding. Sie fügen die ^ hinzu, um nur einmal vom Anfang der Zeichenfolge nach vorne zu schauen. Andernfalls würde die Regexp-Engine versuchen, an jedem Punkt in der Zeichenfolge übereinzustimmen.

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

Wenn Sie den Fall ignorieren möchten, können Sie RegexOptions.Compiled | RegexOptions.IgnoreCase verwenden.

+0

+1 Aber OP will case insensitive es scheint. – Amarghosh

+0

Für OP, suchen Sie nach positivem Lookahead auf dieser Seite: http://msdn.microsoft.com/en-us/library/1400241x(VS.85).aspx –

+1

Diese Regex stimmt nur mit Zeichenfolgen überein, die einen Kleinbuchstaben UND einen Großbuchstaben enthalten. .. –

3

Sie könnten [a-zA-Z].*[0-9]|[0-9].*[a-zA-Z] verwenden, aber ich würde es nur empfehlen, wenn das System, das Sie verwenden, nur eine einzige Regex akzeptiert. Ich kann mir nicht vorstellen, dass dies effizienter wäre als zwei einfache Muster ohne Wechsel.

3

Es ist nicht genau das, was Sie wollen, aber lassen Sie mich sagen, ich habe mehr Zeit. Folgendes sollte schneller als Regex funktionieren.

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

Warum ist es schnell lassen Sie es auschecken. Folgendes ist der Test-String-Generator. Es erzeugt 1/3 der vollständig korrekten Zeichenfolge und 2/3 der falschen Anzeige. In 2/3 1/2 sind alle Buchstaben und in der anderen Hälfte alle Ziffern.

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

Folgendes ist Darin zwei Lösung. Einer hat kompiliert und andere sind nicht kompilierte Version.

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

Im Anschluss an die Haupt der Testschleife

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

Es folgt ergibt

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

So ist die erste Lösung, die beste war. Einige führen mehr im Release-Modus und folgende spec

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) 

ich wieder mit RegexOptions.IgnoreCase Flagge geprüft. Rest param wie oben

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) 

Nach gnarf erwähnen, dass es ein Problem mit meinem algo war es überprüft wurde nur, wenn der String von Buchstaben und Ziffern bestehen, so kann ich es ändern, und es jetzt die Zeichenfolge überprüfen hat atleast eine char und eine Ziffer.

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

Ergebnisse

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) 

Meins ist schnell von einem großen Faktor.

+0

Jede Begründung, warum dies schneller als Regex wäre? – Amarghosh

+0

Und wenn es * schneller ist, ist der Unterschied trivial. Sie müssen Millionen von Strings in einer engen Schleife testen, um dies die Mühe wert zu machen. –

+0

Ich habe Leistungsergebnis veröffentlichen. In meiner Antwort. Sagte, du hast Zeit. – affan

10

Für C# mit LINQ:

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

Bitte, jemand markieren dies als die Antwort! – Benjol

+0

Dies erfordert im schlimmsten Fall zwei vollständige Iterationen der String-Zeichen. – affan

+0

@affan - im schlimmsten Fall muss man jedes Zeichen zweimal überprüfen; Dies gilt für jede mögliche Lösung. Ob es in einer Schleife oder in zwei auftritt, macht keinen Unterschied, abgesehen von der Erstellung eines anderen Char-Iterators - für eine In-Memory-Zeichenfolge ist dies höchstens ein winziger Overhead. – Kobi

0

Hier finden Sie nicht nur schneller als die anderen Look-Ahead-Konstrukte, es auch ist (in meinen Augen) näher an die Anforderungen:

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

Auf meinem (zugegebenermaßen grober Test) es läuft in etwa der Hälfte der Zeit, die von den anderen Regex-Lösungen benötigt wird, und hat den Vorteil, dass es sich nicht um Zeilenumbrüche in der Eingabezeichenkette kümmert. (Und wenn es aus irgendeinem Grund sollte, ist es offensichtlich, wie man es einbezieht).

Hier ist, wie (und warum) funktioniert es:

Schritt 1: Es entspricht ein einzelnes Zeichen (lassen Sie uns es c nennen), die eine Zahl oder ein Buchstabe ist.
Schritt 2: Es wird ein Lookbehind durchgeführt, um zu prüfen, ob c eine Zahl ist. Wenn ja:
Schritt 2.1: Es erlaubt eine unbegrenzte Anzahl von Zeichen, die kein Buchstabe sind, gefolgt von einem einzelnen Buchstaben. Wenn dies übereinstimmt, haben wir eine Nummer (c) gefolgt von einem Buchstaben.
Schritt 2.2: Wenn c keine Nummer ist, muss es ein Buchstabe sein (sonst wäre es nicht übereinstimmend). In diesem Fall erlauben wir eine unbegrenzte Anzahl von Nicht-Ziffern, gefolgt von einer einzelnen Ziffer. Das würde bedeuten, dass wir einen Brief haben (c) gefolgt von einer Nummer.

+0

Logischerweise ist das ähnlich wie bei Anonym, aber komplexer. Bist du sicher, dass das schnell geht? würde es im Falle eines Fehlers nicht für jeden passenden Buchstaben testen? (Zum Beispiel 600 X) – Kobi

+0

Wie bei @ affan's Antwort ist es extrem unwahrscheinlich, dass es sich auf jeden Fall lohnt. Die Leute machen sich viel zu viel Gedanken über die Leistung von Regex. –

+0

@Anonymous Antwort wird jedes Zeichen vor dem ersten Buchstaben zweimal übereinstimmen, wenn der erste Zweig fehlschlägt, da der zweite Zweig einen Backtrack an den Anfang zurückführt. Wenn Sie einigermaßen sicher sein können, dass die Eingabezeichenfolge einen Buchstaben nahe am Anfang hat, führt dies zu derselben Leistung (und nach dem Ersetzen der Punkte sogar mit der gleichen Bedeutung). - auch danke, dass ich den fehlenden Caret eingefügt habe - keine Ahnung, wie ich das beim Posting erledigt habe;) –