Konvertieren einer MatchCollection in ein String-Array

82

Gibt es eine bessere Möglichkeit, eine MatchCollection in ein String-Array zu konvertieren?

MatchCollection mc = Regex.Matches(strText, @"\b[A-Za-z-']+\b");
string[] strArray = new string[mc.Count];
for (int i = 0; i < mc.Count;i++ )
{
    strArray[i] = mc[i].Groups[0].Value;
}

PS: mc.CopyTo(strArray,0)löst eine Ausnahme aus:

Mindestens ein Element im Quellarray konnte nicht auf den Zielarraytyp herabgesetzt werden.

Vil
quelle

Antworten:

166

Versuchen:

var arr = Regex.Matches(strText, @"\b[A-Za-z-']+\b")
    .Cast<Match>()
    .Select(m => m.Value)
    .ToArray();
Dave Bish
quelle
1
Ich hätte dafür verwendet, OfType<Match>()anstatt Cast<Match>()... Andererseits wäre das Ergebnis das gleiche.
Alex
4
@Alex Sie wissen, dass alles zurückgegeben wird Match, sodass Sie es zur Laufzeit nicht erneut überprüfen müssen. Castmacht mehr Sinn.
Servy
2
@ DaveBish Ich habe unten eine Art Benchmarking-Code gepostet, der OfType<>sich als etwas schneller herausstellt.
Alex
1
@Frontenderman - Nein, ich habe es gerade mit der Frage des Fragenden in Einklang gebracht
Dave Bish
1
Sie würden denken, es wäre ein einfacher Befehl, a MatchCollectionin a zu verwandeln string[], wie es ist Match.ToString(). Es ist ziemlich offensichtlich, dass der endgültige Typ, der für viele RegexAnwendungen benötigt wird, eine Zeichenfolge ist, daher sollte die Konvertierung einfach sein.
n00dles
31

Dave Bishs Antwort ist gut und funktioniert richtig.

Es ist erwähnenswert, obwohl das Ersetzen Cast<Match>()durch OfType<Match>()die Dinge beschleunigt.

Code wird werden:

var arr = Regex.Matches(strText, @"\b[A-Za-z-']+\b")
    .OfType<Match>()
    .Select(m => m.Groups[0].Value)
    .ToArray();

Das Ergebnis ist genau das gleiche (und behandelt das Problem von OP genauso), aber für große Zeichenfolgen ist es schneller.

Testcode:

// put it in a console application
static void Test()
{
    Stopwatch sw = new Stopwatch();
    StringBuilder sb = new StringBuilder();
    string strText = "this will become a very long string after my code has done appending it to the stringbuilder ";

    Enumerable.Range(1, 100000).ToList().ForEach(i => sb.Append(strText));
    strText = sb.ToString();

    sw.Start();
    var arr = Regex.Matches(strText, @"\b[A-Za-z-']+\b")
              .OfType<Match>()
              .Select(m => m.Groups[0].Value)
              .ToArray();
    sw.Stop();

    Console.WriteLine("OfType: " + sw.ElapsedMilliseconds.ToString());
    sw.Reset();

    sw.Start();
    var arr2 = Regex.Matches(strText, @"\b[A-Za-z-']+\b")
              .Cast<Match>()
              .Select(m => m.Groups[0].Value)
              .ToArray();
    sw.Stop();
    Console.WriteLine("Cast: " + sw.ElapsedMilliseconds.ToString());
}

Ausgabe folgt:

OfType: 6540
Cast: 8743

Bei sehr langen Saiten ist Cast () daher langsamer.

Alex
quelle
1
Sehr überraschend! Angesichts der Tatsache, dass OfType irgendwo im Inneren einen 'Ist'-Vergleich und eine Besetzung durchführen muss (hätte ich gedacht?) Irgendwelche Ideen, warum Besetzung <> langsamer ist? Ich habe nichts!
Dave Bish
Ich habe ehrlich gesagt keine Ahnung, aber es "fühlt" sich richtig für mich an (OfType <> ist nur ein Filter, Cast <> ist ... nun, ist eine Besetzung)
Alex
Weitere Benchmarks scheinen zu zeigen, dass dieses spezielle Ergebnis auf Regex zurückzuführen ist, mehr als auf eine bestimmte verwendete Linq-Erweiterung
Alex
6

Ich habe genau den gleichen Benchmark durchgeführt, den Alex veröffentlicht hat, und festgestellt, dass er manchmal Castschneller und manchmal OfTypeschneller war, aber der Unterschied zwischen beiden war vernachlässigbar. Obwohl hässlich, ist die for-Schleife durchweg schneller als die beiden anderen.

Stopwatch sw = new Stopwatch();
StringBuilder sb = new StringBuilder();
string strText = "this will become a very long string after my code has done appending it to the stringbuilder ";
Enumerable.Range(1, 100000).ToList().ForEach(i => sb.Append(strText));
strText = sb.ToString();

//First two benchmarks

sw.Start();
MatchCollection mc = Regex.Matches(strText, @"\b[A-Za-z-']+\b");
var matches = new string[mc.Count];
for (int i = 0; i < matches.Length; i++)
{
    matches[i] = mc[i].ToString();
}
sw.Stop();

Ergebnisse:

OfType: 3462
Cast: 3499
For: 2650
David DeMar
quelle
Kein Wunder, dass linq langsamer ist als for loop. Für manche Menschen ist es möglicherweise einfacher, Linq zu schreiben und ihre Produktivität auf Kosten der Ausführungszeit zu "steigern". das kann manchmal gut sein
gg89
1
Der ursprüngliche Beitrag ist also die effizienteste Methode.
n00dles
2

Man könnte diese Erweiterungsmethode auch verwenden, um mit dem Ärger umzugehen, MatchCollectionnicht generisch zu sein. Nicht, dass es eine große Sache wäre, aber dies ist mit ziemlicher Sicherheit performanter als OfTypeoder Cast, weil es nur eine Aufzählung ist, was beide auch tun müssen.

(Randnotiz: Ich frage mich, ob es dem .NET-Team möglich sein würde, MatchCollectiongenerische Versionen von ICollectionund IEnumerablein Zukunft zu erben . Dann würden wir diesen zusätzlichen Schritt nicht benötigen, um sofort LINQ-Transformationen verfügbar zu haben.)

public static IEnumerable<Match> ToEnumerable(this MatchCollection mc)
{
    if (mc != null) {
        foreach (Match m in mc)
            yield return m;
    }
}
Nicholas Petersen
quelle
0

Betrachten Sie den folgenden Code ...

var emailAddress = "[email protected]; [email protected]; [email protected]";
List<string> emails = new List<string>();
emails = Regex.Matches(emailAddress, @"([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})")
                .Cast<Match>()
                .Select(m => m.Groups[0].Value)
                .ToList();
gpmurthy
quelle
1
ugh ... Diese Regex ist schrecklich anzusehen. Übrigens, da es keinen narrensicheren regulären Ausdruck für die Validierung von E-Mails gibt, verwenden Sie das MailAddress-Objekt. stackoverflow.com/a/201378/2437521
C. Tewalt