【问题标题】:How to select 2 items at a time?如何一次选择 2 个项目?
【发布时间】:2013-06-27 03:00:27
【问题描述】:

我正在使用 Regex.Split 编写一个 PascalCaseParser,我想一次从一个集合中选择两个项目。

此示例代码演示。

void Main()
{
    string pascalCasedString = "JustLikeYouAndMe";
    var words = WordsFromPascalCasedString(pascalCasedString);
    words.Dump();
}

IEnumerable<string> WordsFromPascalCasedString(string pascalCasedString)
{
    var rx = new Regex("([A-Z])");
    return rx.Split(pascalCasedString)
             .Where(c => !string.IsNullOrEmpty(c))
             // how to select 2 elements at a time?
             ;
}

以上代码的结果是:

IEnumerable<String> (10 items)
J 
ust 
L 
ike 
Y 
ou 
A 
nd 
M 
e 

集合的每两个元素都会产生一个我希望函数 WordsFromPascalCasedString 产生的结果。

我的问题是:一般而言,您将如何处理一次退回两件商品的要求。我很好奇是否有任何有趣的非暴力方法。

【问题讨论】:

    标签: c# regex linq


    【解决方案1】:

    就个人而言,在这种特殊情况下,我会选择 Simon Belanger 的答案。但一般来说,要从IEnumerable 中选择连续的对,你会使用这个:

    IEnumerable<Tuple<string, string>> WordsFromPascalCasedString(string pascalCasedString)
    {
        var rx = new Regex("([A-Z])");
        var array = rx.Split(pascalCasedString)
                      .Where(c => !string.IsNullOrEmpty(c))
                      .ToArray();
        var items = Enumerable.Range(0, array.Length / 2)
                              .Select(i => Tuple.Create(array[i * 2], array[i * 2 + 1]);
    }
    

    或者这个,需要更多的努力,但它可重用且更高效:

    IEnumerable<Tuple<T, T>> Pairs<T>(IEnumerable<T> input)
    {
        var array = new T[2];
        int i = 0;
        foreach(var x in input)
        {
            array[i] = x;
            i = (i + 1) % 2;
            if (i == 0)
            {
                yield return Tuple.Create(array[0], array[1]);
            }
        }
    }
    
    
    IEnumerable<Tuple<string, string>> WordsFromPascalCasedString(string pascalCasedString)
    {
        var rx = new Regex("([A-Z])");
        var output = rx.Split(pascalCasedString)
                       .Where(c => !string.IsNullOrEmpty(c));
        var items = Pairs(output);
    }
    

    它可以很容易地扩展到n的组:

    IEnumerable<IEnumerable<T>> Batches<T>(IEnumerable<T> input, int n)
    {
        var array = new T[n];
        int i = 0;
        foreach(var x in input)
        {
            array[i] = x;
            i = (i + 1) % n;
            if (i == 0)
            {
                yield return array.ToArray();
            }
        }
    
        if (i != 0)
        {
            yield return array.Take(i);
        }
    }
    

    MoreLINQ中也有类似的方法。

    【讨论】:

      【解决方案2】:

      正则表达式应该是([A-Z][a-z]*)。如果您还想包含数字,请调整最后一部分。如果您希望在大写分隔符后至少有一个小写元素,请使用 + 而不是 *

      编辑 至于实际问题,您需要在for 循环中实现和迭代以获得更好的性能(通过一次列表)。在您的具体问题中,您可以使用Regex.Matches

      var result = Regex.Matches("([A-Z][a-z]*)([A-Z][a-z]*)?", "AbCdEfGhIj")
                        .OfType<Match>()
                        .Where(m => m.Success)
                        .Select(m => Tuple.Create(m.Groups[1].Value, m.Groups[2].Value));
      

      【讨论】:

      • +1 用于解决我的实际问题(谢谢)...如果我提出的问题得到答案,我仍然会感兴趣...
      • 另一个问题是什么?
      • 是的,我并没有真正尝试弄清楚解析器,我真的很想看到其他人想要使用一次处理 2 个项目的 linq 样式选择......但是再次感谢您的帮助
      【解决方案3】:

      这只是为了分享,我在受到其他答案的启发后提出了我想出的解决方案。它并不比其他人好......

      void Main()
      {
          string pascalCasedString = "JustLikeYouAndMe";
          var words = WordsFromPascalCasedString(pascalCasedString);
          words.Dump();
      }
      
      IEnumerable<string> WordsFromPascalCasedString(string pascalCasedString)
      {
          var rx = new Regex("([A-Z])");
          return rx.Split(pascalCasedString)
                   .Where(c => !string.IsNullOrEmpty(c))
                   .InPieces(2)
                   .Select(c => c.ElementAt(0) + c.ElementAt(1));
      }
      
      static class Ext
      {
          public static IEnumerable<IEnumerable<T>> InPieces<T>(this IEnumerable<T> seq, int len)
          {
              if(!seq.Any()) 
                  yield break;
      
              yield return seq.Take(len);
      
              foreach (var element in InPieces(seq.Skip(len), len))
                  yield return element;
          }
      }
      

      【讨论】:

      • +1 我见过很多“批处理”技术,但这是我见过的第一个递归技术。它可能不是最有效的,但绝对是有创意的。
      【解决方案4】:

      最简单的方法是编写只返回对的函数。

      类似:

      IEnumerable<Tuple<T,T>> Pairs<T>(IEnumerable<T> items)
      {
          T first = default(T);
          bool hasFirst = false;
          foreach(T item in items)
          {
             if (hasFirst)
                yield return Tuple.Create(first, item);
             else
                 first = item;
             hasFirst = !hasFirst;
          }
      }
      

      Aggregate 可能只是一种单行方法。由于在途中创建了大量垃圾,这纯粹是娱乐代码,但没有使用可变对象。

      IEnumerable<Tuple<T,T>> Pairs<T>(IEnumerable<T> collection)
      {
        return collection
          .Aggregate(
            Tuple.Create(false, default(T), Enumerable.Empty<Tuple<T,T>>()),
               (accumulate, item)=> !accumulate.Item1 ? 
              Tuple.Create(true, item, accumulate.Item3) :
                  Tuple.Create(false, default(T),
                    accumulate.Item3.Concat(
                       Enumerable.Repeat(Tuple.Create(accumulate.Item2, item), 1))),
            accumulate => accumulate.Item3); 
      }
      

      Zip 的奇数和偶数元素 (index %2 ==/!= 0) 是 2 行方法。请注意,源集合会迭代两次。

      IEnumerable<Tuple<T,T>> Pairs<T>(IEnumerable<T> collection)
      {
        return collection
         .Where((item, index)=>index %2 == 0)
         .Zip(collection.Where((item, index)=>index %2 != 0),
         (first,second)=> Tuple.Create(first,second));
      }
      

      【讨论】:

      • 这是纯粹的娱乐回答 - p.s.w.g (+1) 已经涵盖了创建对的更好版本。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-10-14
      • 2021-11-13
      • 1970-01-01
      • 1970-01-01
      • 2023-03-20
      • 1970-01-01
      • 2012-04-17
      相关资源
      最近更新 更多