【问题标题】:Neat way to write loop that has special logic for the first item in a collection编写循环的巧妙方法,该循环对集合中的第一项具有特殊逻辑
【发布时间】:2011-05-22 08:56:31
【问题描述】:

通常我必须编写一个需要为第一项特殊情况编写的循环,代码似乎永远不会像理想情况下那样清晰。

没有重新设计 C# 语言,编写这些循环的最佳方法是什么?

// this is more code to read then I would like for such a common concept
// and it is to easy to forget to update "firstItem"
foreach (x in yyy)
{
  if (firstItem)
  {
     firstItem = false;
     // other code when first item
  }
  // normal processing code
}

// this code is even harder to understand
if (yyy.Length > 0)
{
   //Process first item;
   for (int i = 1; i < yyy.Length; i++)
   {  
      // process the other items.
   }
}

【问题讨论】:

  • 我真的看不出(你的第一个例子)检查布尔值有什么问题,任何看到它的人都会很快知道你在做什么。

标签: c# collections


【解决方案1】:

我的解决方案:

foreach (var x in yyy.Select((o, i) => new { Object = o, Index = i } )
{
  if (x.Index == 0)
  {
    // First item logic
  }
  else
  {
    // Rest of items
  }
}

【讨论】:

    【解决方案2】:

    对于以不同方式处理第一个元素,这两种算法都是完全可以接受的,而且确实没有不同的方法可以做到这一点。如果这种模式重复很多次,您可以将其隐藏在 ForEach() 的重载之后:

    public static void ForEach<T>(this IEnumerable<T> elements, Action<T> firstElementAction, Action<T> standardAction)
    {
        var firstItem = true;
        foreach(T element in elements)
        {
            if(firstItem)
            {
                firstItem = false;
                firstElementAction(element)
            }
            else
                standardAction(element)
        }
    }
    
    ...
    
    //usage
    yyy.ForEach(t=>(other code when first item), t=>(normal processing code));
    

    Linq 让它更干净一点:

    PerformActionOnFirstElement(yyy.FirstOrDefault());
    yyy.Skip(1).ForEach(x=>(normal processing code));
    

    【讨论】:

      【解决方案3】:

      我想出的另一个选择是

      enum ItemType
      {
        First,
        Last,
        Normal
      }
      
      list.Foreach(T item, ItemType itemType) =>
      {
         if (itemType == ItemType.First)
         {
         }
      
         // rest of code
      };
      

      编写扩展方法留给读者作为练习...... 是否还应该使用两个布尔标志“IsFirst”和“IsLast”来代替 ItemType 枚举,或者 ItemType 是具有“IsFirst”和“IsLast”属性的对象?

      【讨论】:

        【解决方案4】:

        这里有一个稍微简单的扩展方法来完成这项工作。这是KeithS's solutionmy answer to a related Java question 的组合:

        public static void ForEach<T>(this IEnumerable<T> elements,
                                      Action<T> firstElementAction,
                                      Action<T> standardAction)
        {
            var currentAction = firstElementAction;
            foreach(T element in elements)
            {
                currentAction(element);
                currentAction = standardAction;
            }
        }
        

        【讨论】:

          【解决方案5】:

          你可以试试:

          collection.first(x=>
          {
              //...
          }).rest(x=>
          {
              //...
          }).run();
          

          first / rest 看起来像:

          FirstPart<T> first<T>(this IEnumerable<T> c, Action<T> a)
          {
              return new FirstPart<T>(c, a);
          }
          
          FirstRest rest<T>(this FirstPart<T> fp, Action<T> a)
          {
              return new FirstRest(fp.Collection, fp.Action, a);
          }
          

          您需要定义分类的 FirstPart 和 FirstRest。 FirstRest 需要这样的运行方法(Collection、FirstAction 和 RestAction 是属性):

          void run()
          {
              bool first = true;
              foreach (var x in Collection)
              {
                  if (first) {
                      FirstAction(x);
                      first = false;
                  }
                  else {
                       RestAction(x);
                  }
              }
          }
          

          【讨论】:

          【解决方案6】:

          虽然我个人不会这样做,但还有另一种方法using enumerators,它可以减轻对条件逻辑的需求。像这样的:

          void Main()
          {
              var numbers = Enumerable.Range(1, 5);
              IEnumerator num = numbers.GetEnumerator();
          
              num.MoveNext();
              ProcessFirstItem(num.Current); // First item
          
              while(num.MoveNext()) // Iterate rest
              {
                  Console.WriteLine(num.Current);
              }
          
          }
          
              void ProcessFirstItem(object first)
              {
                  Console.WriteLine("First is: " + first);
              }
          

          示例输出为:

          First is: 1
          2
          3
          4
          5
          

          【讨论】:

            【解决方案7】:

            怎么样:

            using (var erator = enumerable.GetEnumerator())
            {
                if (erator.MoveNext())
                {
                    ProcessFirst(erator.Current);
                    //ProcessOther(erator.Current); // Include if appropriate.
            
                    while (erator.MoveNext())
                        ProcessOther(erator.Current);
                }
            }
            

            如果你愿意,你可以把它变成一个扩展:

            public static void Do<T>(this IEnumerable<T> source, 
                                     Action<T> firstItemAction,
                                     Action<T> otherItemAction)
            {
               // null-checks omitted
            
                using (var erator = source.GetEnumerator())
                {
                    if (!erator.MoveNext())
                        return;
            
                    firstItemAction(erator.Current);
            
                    while (erator.MoveNext())
                       otherItemAction(erator.Current);            
                }
            }
            

            【讨论】:

            • 如果有人能为这个扩展方法想出一个清晰的名字就好了。
            • @Ian,DoForFirstThen() 怎么样?
            • +1:我说叫它ForEach,争论的名字让事情变得一目了然,特别是如果它是ForEach&lt;T&gt;(this IEnumerable&lt;T&gt; source, Action&lt;T&gt; actionForFirstItem, Action&lt;T&gt; actionForAllOtherItems)
            【解决方案8】:

            恕我直言,最干净的方法是:尽量避免第一项的特殊情况。当然,这可能并不适用于所有情况,但“特殊情况”可能表明您的程序逻辑比它需要的复杂。

            顺便说一句,我不会编码

            if (yyy.Length > 0)
            {
               for(int i = 1; i <yyy.Length; i++)
               {  
                  // ...
               }
            }
            

            而是

               for(int i = 1; i <yyy.Length; i++)
               {  
                  // ...
               }
            

            (这本身就是一个简单的例子,说明如何避免不必要地处理特殊情况。)

            【讨论】:

            • +1 表示“尽量避免第一项的特殊情况”句子。
            • 您是说这个stackoverflow.com/questions/4390406/… 吗?如果是这样,最好的答案会在您的完整集合中循环显示,这正是我所说的。
            【解决方案9】:

            在这种情况下,我只会使用这样的 for 循环:

            for(int i = 0;  i < yyy.Count; i++){
                  if(i == 0){
                      //special logic here
                  }
            }
            

            使用 for 循环还可以让您在其他情况下做一些特殊的事情,例如对最后一项、对序列中的偶数项等。

            【讨论】:

              【解决方案10】:

              我很想使用一点 linq

              using System.Linq;
              
              var theCollectionImWorkingOn = ...
              
              var firstItem = theCollectionImWorkingOn.First();
              firstItem.DoSomeWork();
              
              foreach(var item in theCollectionImWorkingOn.Skip(1))
              {
                  item.DoSomeOtherWork();
              }
              

              【讨论】:

              • 避免在循环中检查 bool 标志似乎有点过头了。
              • firstItem 逻辑应该包含在检查中以确保集合包含任何元素...
              • 易读,圈复杂度较低。我不确定在哪里矫枉过正。
              • 假设对firstItem 逻辑的某种检查的稳健性,您的示例与if 和简单foreach 内的标志相同,圈复杂度相同。
              【解决方案11】:

              我一直使用first 变量方法,这对我来说似乎完全正常。 如果你更喜欢这样,你可以使用 LINQ First()Skip(1)

              var firstItem = yyy.First();
              // do the whatever on first item
              
              foreach (var y in yyy.Skip(1))
              {
              // process the rest of the collection
              }
              

              【讨论】:

                【解决方案12】:

                你写它的方式可能是它可以写​​的最干净的方式。毕竟,第一个元素有特定的逻辑,所以它必须以某种方式表示。

                【讨论】:

                • 同意。 OPs原代码第二部分sn-p最清楚。
                猜你喜欢
                • 1970-01-01
                • 2021-07-28
                • 1970-01-01
                • 2021-10-27
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多