Умный способ написать цикл, который имеет специальную логику для первого элемента в коллекции
Часто мне приходится кодировать цикл, который в специальном случае должен содержать первый элемент в коллекции, код никогда не кажется ясным, как это должно быть.
За исключением редизайна языка С#, как лучше всего кодировать эти циклы?
// 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
}
}