Chuyển đổi MatchCollection thành mảng chuỗi


81

Có cách nào tốt hơn cách này để chuyển đổi MatchCollection thành một mảng chuỗi không?

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)ném một ngoại lệ:

Ít nhất một phần tử trong mảng nguồn không thể được truyền xuống kiểu mảng đích.

Câu trả lời:


164

Thử:

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

1
Tôi sẽ sử dụng OfType<Match>()cho điều này thay vì Cast<Match>()... Sau đó, một lần nữa, kết quả sẽ giống nhau.
Alex

4
@Alex Bạn biết rằng mọi thứ được trả về sẽ là a Match, vì vậy không cần phải kiểm tra lại trong thời gian chạy. Castcó ý nghĩa hơn.
Servy

2
@DaveBish Tôi đã đăng một số loại mã đo điểm chuẩn bên dưới, OfType<>hóa ra là nhanh hơn một chút.
Alex

1
@Frontenderman - Không, tôi chỉ đang điều chỉnh nó với câu hỏi của người hỏi
Dave Bish

1
Bạn sẽ nghĩ rằng nó sẽ là một lệnh đơn giản để biến a MatchCollectionthành a string[], vì nó là cho Match.ToString(). Rõ ràng là loại cuối cùng cần thiết trong nhiều Regexmục đích sử dụng sẽ là một chuỗi, vì vậy nó đáng lẽ phải dễ dàng chuyển đổi.
n00dles

31

Câu trả lời của Dave Bish là tốt và hoạt động đúng.

Điều đáng chú ý là mặc dù thay thế Cast<Match>()bằng OfType<Match>()sẽ đẩy nhanh tốc độ.

Code wold trở thành:

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

Kết quả hoàn toàn giống nhau (và giải quyết vấn đề của OP theo cùng một cách) nhưng đối với các chuỗi lớn thì nhanh hơn.

Mã kiểm tra:

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

Đầu ra như sau:

OfType: 6540
Cast: 8743

Đối với các chuỗi rất dài Cast () do đó sẽ chậm hơn.


1
Rất ngạc nhiên! Cho rằng OfType phải thực hiện một phép so sánh 'là' ở đâu đó bên trong và dàn diễn viên (tôi đã nghĩ?) Bất kỳ ý tưởng nào về lý do tại sao Truyền <> chậm hơn? Tôi không có gì cả!
Dave Bish

Thực sự tôi không có một đầu mối, nhưng nó "cảm thấy" đúng với tôi (OfType <> chỉ là một bộ lọc, Cast <> là ... tốt, là một diễn viên)
Alex

Nhiều tiêu chuẩn dường như hiển thị kết quả cụ thể điều này là do regex hơn mở rộng cụ thể LINQ sử dụng
Alex

6

Tôi đã chạy cùng một điểm chuẩn mà Alex đã đăng và thấy rằng đôi khi Castnhanh hơn và đôi khi OfTypenhanh hơn, nhưng sự khác biệt giữa cả hai là không đáng kể. Tuy nhiên, trong khi xấu xí, vòng lặp for luôn nhanh hơn cả hai vòng lặp kia.

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

Các kết quả:

OfType: 3462
Cast: 3499
For: 2650

không có gì ngạc nhiên khi linq chậm hơn vòng lặp for. Linq có thể dễ viết hơn đối với một số người và "tăng" năng suất của họ với thời gian thực hiện chi phí. điều đó đôi khi có thể tốt
gg89

1
Vì vậy, bài viết gốc thực sự là phương pháp hiệu quả nhất.
n00dles

2

Người ta cũng có thể sử dụng phương pháp mở rộng này để đối phó với sự khó chịu của việc MatchCollectionkhông chung chung. Đó không phải là một vấn đề lớn, nhưng điều này gần như chắc chắn hiệu quả hơn OfTypehoặc Cast, bởi vì nó chỉ là liệt kê, mà cả hai điều này cũng phải làm.

(Lưu ý: Tôi tự hỏi liệu nhóm .NET có thể tạo MatchCollectioncác phiên bản chung kế thừa của ICollectionIEnumerabletrong tương lai không? Sau đó, chúng tôi sẽ không cần thêm bước này để có sẵn các biến đổi LINQ ngay lập tức).

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

0

Hãy xem xét đoạn mã sau ...

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
ugh ... Regex đó thật khủng khiếp khi nhìn vào. BTW, vì không tồn tại một regex an toàn để xác thực email, hãy sử dụng đối tượng MailAddress. stackoverflow.com/a/201378/2437521
C. Tewalt
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.