Перетворення MatchCollection у масив рядків


83

Чи є кращий спосіб перетворити MatchCollection на масив рядків?

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)видає виняток:

Принаймні один елемент у вихідному масиві не може бути відведений до цільового масиву.

Відповіді:


168

Спробуйте:

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

1
Я б використав OfType<Match>()для цього замість Cast<Match>()... Знову ж таки, результат був би однаковим.
Алекс

4
@Alex Ви знаєте, що все повернене буде a Match, тому немає необхідності перевіряти це ще раз під час виконання. Castмає більше сенсу.
Серві

2
@DaveBish Я опублікував якийсь бенчмарк-код нижче, OfType<>виявляється, трохи швидший.
Alex

1
@Frontenderman - Ні, я просто прирівнював це питання до запитувачів
Дейв Біш

1
Ви можете подумати, що це буде простою командою перетворити a MatchCollectionна те string[], як воно є Match.ToString(). Цілком очевидно, що кінцевий тип, необхідний для багатьох Regexзастосувань, буде рядком, тому його слід було легко перетворити.
n00dles

32

Відповідь Дейва Біша хороша і працює належним чином.

Варто зазначити, хоча ця заміна Cast<Match>()на OfType<Match>()пришвидшить процес.

Код став:

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

Результат точно такий же (і вирішує проблему OP точно так само), але для величезних рядків це швидше.

Тестовий код:

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

Результат випливає:

OfType: 6540
Cast: 8743

Отже, для дуже довгих рядків Cast () працює повільніше.


1
Дуже дивно! Враховуючи, що OfType повинен робити порівняння десь усередині та акторський склад (я б подумав?) Будь-які ідеї щодо того, чому Cast <> повільніший? У мене нічого немає!
Дейв Біш

Я, чесно кажучи, не маю уявлення, але мені це "здається" правильно (OfType <> - це лише фільтр, Cast <> - це ... ну, це акторський склад)
Алекс

Більше тестів, схоже, показують, що цей конкретний результат зумовлений регулярним виразом, ніж використовуваним конкретним розширенням linq
Alex,

6

Я провів той самий орієнтир, який опублікував Алекс, і виявив, що іноді це Castбуло швидше, а іноді OfTypeшвидше, але різниця між ними була незначною. Однак, хоча і потворний, цикл for стабільно швидший, ніж обидва інші два.

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

Результати:

OfType: 3462
Cast: 3499
For: 2650

не дивно, що linq працює повільніше, ніж для циклу. Linq може бути простіше писати для деяких людей і "збільшувати" їх продуктивність за рахунок часу виконання. це іноді може бути добре
gg89

1
Тож оригінальна публікація - це насправді найефективніший метод.
n00dles

2

Можна також скористатися цим методом розширення, щоб впоратися з роздратуванням через MatchCollectionвідсутність загального роду. Не те, що це велика справа, але це майже напевно ефективніше, ніж OfTypeабо Cast, тому що це просто перерахування, що обоє з них також повинні зробити.

(Додаткова примітка: Цікаво, чи можливо для команди .NET зробити MatchCollectionспадщину загальних версій ICollectionі IEnumerableв майбутньому? Тоді нам не знадобиться цей додатковий крок, щоб негайно отримати доступні перетворення LINQ).

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

0

Розглянемо наступний код ...

var emailAddress = "joe@sad.com; joe@happy.com; joe@elated.com";
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();

1
тьфу ... На цей регулярний вираз жахливо дивитись. До речі, оскільки не існує надійного регулярного виразу для перевірки електронної пошти, використовуйте об'єкт MailAddress. stackoverflow.com/a/201378/2437521
C. Tewalt
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.