Умный способ написать цикл, который имеет специальную логику для первого элемента в коллекции

Часто мне приходится кодировать цикл, который в специальном случае должен содержать первый элемент в коллекции, код никогда не кажется ясным, как это должно быть.

За исключением редизайна языка С#, как лучше всего кодировать эти циклы?

// 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.
   }
}

Ответ 1

Вы можете попробовать:

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 понадобится метод run (например, Collection, FirstAction и RestAction):

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

Ответ 2

Как насчет:

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

Ответ 3

У меня возникнет соблазн использовать немного linq

using System.Linq;

var theCollectionImWorkingOn = ...

var firstItem = theCollectionImWorkingOn.First();
firstItem.DoSomeWork();

foreach(var item in theCollectionImWorkingOn.Skip(1))
{
    item.DoSomeOtherWork();
}

Ответ 4

Я использую метод переменной 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
}

Ответ 5

То, как вы его написали, вероятно, является самым чистым способом его написания. В конце концов, существует логика, специфичная для первого элемента, поэтому ее нужно как-то представить.

Ответ 6

В таких случаях я бы просто использовал цикл for следующим образом:

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

Использование цикла for также позволит вам делать что-то особенное в других случаях, например, на последнем элементе, на четных элементах в последовательности,..etc.

Ответ 7

ИМХО самый чистый способ: старайтесь избегать особых случаев для первого элемента. Конечно, это может не работать в любой ситуации, но "особые случаи" могут указывать на то, что ваша программная логика более сложна, чем она должна быть.

Кстати, я бы не закодировал

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

но вместо этого

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

(что само по себе является простым примером того, как избежать ненужного обращения со специальным случаем.)

Ответ 8

Вот несколько более простой метод расширения, который выполняет задание. Это сочетание KeithS solution и моего ответа на связанный Java-вопрос:

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;
    }
}

Ответ 9

Оба из них являются вполне приемлемыми алгоритмами для обработки первого элемента по-разному, и на самом деле нет другого способа сделать это. Если этот шаблон повторяется много, вы можете скрыть его за перегрузку 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));

Ответ 10

Пока я лично этого не делал, есть другой способ с использованием перечислений, что облегчает необходимость условной логики. Что-то вроде этого:

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

Ответ 11

Другой вариант, с которым я пришел, -

enum ItemType
{
  First,
  Last,
  Normal
}

list.Foreach(T item, ItemType itemType) =>
{
   if (itemType == ItemType.First)
   {
   }

   // rest of code
};

Написание метода расширения остается в качестве упражнения для читателя... Также должны использоваться два булевых флага "IsFirst" и "IsLast" вместо перечисления ItemType, или ItemType - объект с свойствами "IsFirst" и "IsLast"?

Ответ 12

Мое решение:

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