【问题标题】:Converting a MatchCollection to string array将 MatchCollection 转换为字符串数组
【发布时间】:2012-07-10 02:23:02
【问题描述】:

有没有比这更好的方法将 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;
}

P.S.:mc.CopyTo(strArray,0) 抛出异常:

源数组中的至少一个元素无法转换为目标数组类型。

【问题讨论】:

    标签: c# arrays regex


    【解决方案1】:

    试试:

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

    【讨论】:

    • 我会使用OfType&lt;Match&gt;() 而不是Cast&lt;Match&gt;() ...然后,结果将是相同的。
    • @Alex 您知道返回的所有内容都是Match,因此无需在运行时再次检查。 Cast 更有意义。
    • @DaveBish 我在下面发布了一些基准测试代码,OfType&lt;&gt; 原来速度稍快。
    • @Frontenderman - 不,我只是将它与提问者的问题对齐
    • 您会认为将MatchCollection 转换为string[] 是一个简单的命令,就像Match.ToString() 一样。很明显,很多 Regex 使用中需要的最终类型是字符串,所以它应该很容易转换。
    【解决方案2】:

    Dave Bish 的回答很好,并且工作正常。

    值得注意的是,尽管将 Cast&lt;Match&gt;() 替换为 OfType&lt;Match&gt;() 会加快速度。

    代码将变成:

    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() 会更慢。

    【讨论】:

    • 非常令人惊讶!鉴于 OfType 必须在内部某处进行“是”比较并进行演员表(我曾想过?)关于为什么 Cast 较慢的任何想法?我什么都没有!
    • 老实说,我不知道,但它“感觉”对我来说是正确的(OfType 只是一个过滤器,Cast 是......好吧,是一个演员)
    • 更多的基准测试似乎表明这个特定的结果是由于正则表达式而不是使用的特定 linq 扩展
    【解决方案3】:

    我运行了 Alex 发布的完全相同的基准测试,发现有时 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 比 for 循环慢。对于某些人来说,Linq 可能更容易编写,并以牺牲执行时间为代价“提高”他们的生产力。有时会很好
    • 所以原帖确实是最有效的方法。
    【解决方案4】:

    也可以使用这种扩展方法来处理MatchCollection 不通用的烦恼。并不是说它有什么大不了的,但这几乎肯定比OfTypeCast 性能更高,因为它只是枚举,这两者都必须这样做。

    (旁注:我想知道.NET 团队将来是否有可能让MatchCollection 继承ICollectionIEnumerable 的通用版本?那么我们不需要这个额外的步骤来立即有可用的 LINQ 转换)。

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

    【讨论】:

      【解决方案5】:

      考虑下面的代码...

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

      【讨论】:

      • 呃......那个正则表达式看起来很可怕。顺便说一句,由于不存在用于验证电子邮件的万无一失的正则表达式,请使用 MailAddress 对象。 stackoverflow.com/a/201378/2437521
      【解决方案6】:

      如果您需要递归捕获,例如。 标记数学方程:

      //INPUT (I need this tokenized to do math)
          string sTests = "(1234+5678)/ (56.78-   1234   )";
                  
          Regex splitter = new Regex(@"([\d,\.]+|\D)+");
          Match match = splitter.Match(sTests.Replace(" ", ""));
          string[] captures = (from capture in match.Groups.Cast<Group>().Last().Captures.Cast<Capture>()
                               select capture.Value).ToArray();
      

      ...因为您需要追踪最后一组中的最后一次捕获。

      【讨论】:

        猜你喜欢
        • 2017-11-27
        • 2011-06-18
        • 1970-01-01
        • 1970-01-01
        • 2018-03-08
        • 2011-02-15
        相关资源
        最近更新 更多