Konwertowanie MatchCollection na tablicę ciągów

83

Czy istnieje lepszy sposób niż ten, aby przekonwertować MatchCollection na tablicę ciągów?

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)zgłasza wyjątek:

Co najmniej jeden element w tablicy źródłowej nie mógł zostać rzutowany na typ tablicy docelowej.

Vil
źródło

Odpowiedzi:

168

Próbować:

var arr = Regex.Matches(strText, @"\b[A-Za-z-']+\b")
    .Cast<Match>()
    .Select(m => m.Value)
    .ToArray();
Dave Bish
źródło
1
Użyłbym OfType<Match>()tego zamiast Cast<Match>()... Z drugiej strony, wynik byłby taki sam.
Alex
4
@Alex Wiesz, że wszystko zostanie zwrócone Match, więc nie ma potrzeby sprawdzania tego ponownie w czasie wykonywania. Castma więcej sensu.
Servy
2
@DaveBish Opublikowałem poniżej jakiś kod testowy, OfType<>okazuje się być nieco szybszy.
Alex
1
@Frontenderman - Nie, właśnie dopasowałem to do pytania pytającego
Dave Bish
1
Można by pomyśleć, że byłoby prostym poleceniem przekształcenie a MatchCollectionw a string[], tak jak to jest w przypadku Match.ToString(). Jest całkiem oczywiste, że ostatecznym typem potrzebnym w wielu Regexzastosowaniach byłby ciąg, więc konwersja powinna być łatwa.
n00dles
32

Odpowiedź Dave'a Bisha jest dobra i działa poprawnie.

Warto zauważyć, chociaż zastąpienie Cast<Match>()go OfType<Match>()przyspieszy.

Kod stał się:

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

Wynik jest dokładnie taki sam (i rozwiązuje problem OP dokładnie w ten sam sposób), ale w przypadku dużych ciągów jest szybszy.

Kod testowy:

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

Wynik jest następujący:

OfType: 6540
Cast: 8743

Dlatego w przypadku bardzo długich ciągów Cast () jest wolniejsze.

Alex
źródło
1
Bardzo zaskakujące! Biorąc pod uwagę, że OfType musi wykonać porównanie „jest” gdzieś w środku i obsadę (pomyślałbym?) Jakieś pomysły, dlaczego Cast <> jest wolniejsze? Nie mam nic!
Dave Bish,
Szczerze mówiąc, nie mam pojęcia, ale "czuję" się dobrze (OfType <> to tylko filtr, Cast <> to ... cóż, to obsada)
Alex.
Wydaje się, że więcej testów porównawczych pokazuje, że ten konkretny wynik jest spowodowany bardziej regexem niż konkretnym rozszerzeniem linq
Alex
6

Przeprowadziłem dokładnie ten sam test porównawczy, który opublikował Alex i stwierdziłem, że czasami Castbył szybszy, a czasami OfTypeszybszy, ale różnica między nimi była nieistotna. Jednak, choć brzydka, pętla for jest konsekwentnie szybsza niż obie pozostałe dwie.

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

Wyniki:

OfType: 3462
Cast: 3499
For: 2650
David DeMar
źródło
nic dziwnego, że linq jest wolniejszy niż pętla for. Linq może być łatwiejszy do napisania dla niektórych ludzi i „zwiększyć” ich produktywność kosztem czasu wykonania. to może być czasami dobre
gg89
1
Więc oryginalny post jest naprawdę najbardziej wydajną metodą.
n00dles
2

Można również skorzystać z tej metody rozszerzenia, aby poradzić sobie z irytacją wynikającą z MatchCollectionbraku bycia ogólnym. Nie żeby to była wielka sprawa, ale prawie na pewno jest to bardziej wydajne niż OfTypelub Cast, ponieważ to tylko wyliczenie, co obaj też muszą zrobić.

(Nota boczna: zastanawiam się, czy zespół .NET mógłby MatchCollectiondziedziczyć generyczne wersje programu ICollectioniw IEnumerableprzyszłości? Wtedy nie potrzebowalibyśmy tego dodatkowego kroku, aby natychmiast mieć dostępne transformacje LINQ).

public static IEnumerable<Match> ToEnumerable(this MatchCollection mc)
{
    if (mc != null) {
        foreach (Match m in mc)
            yield return m;
    }
}
Nicholas Petersen
źródło
0

Rozważ następujący kod ...

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
źródło
1
ugh ... Ten regex jest przerażający. BTW, ponieważ nie istnieje niezawodne wyrażenie regularne do sprawdzania poprawności wiadomości e-mail, użyj obiektu MailAddress. stackoverflow.com/a/201378/2437521
C. Tewalt