2011-02-08 3 views
4

Что бы самым читаемым способ применить следующие последовательности с помощью LINQ:Linq к объектам: TakeWhileOrFirst

TakeWhile elements are valid but always at least the first element 

EDIT: Я обновил название, чтобы быть более точным. Прошу прощения за любую путаницу, ответы ниже определенно научили меня чему-то!

Ожидаемое поведение: принять, если элемент действителен. Если результат пустой последовательности, все равно возьмите первый элемент.

+0

Я обновил вопрос. См. Правки. Извините за путаницу. – asgerhallas

ответ

1

Это было бы наиболее эффективно реализовано вручную, насколько я могу судить, чтобы убедиться, что он не перечислит больше, чем необходимо.

public static IEnumerable<TSource> TakeWhileOrFirst<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) 
{ 
    using (var enumerator = source.GetEnumerator()) 
    { 
     if (!enumerator.MoveNext()) 
      yield break; 
     TSource current = enumerator.Current; 
     yield return current; 
     if (predicate(current)) 
     { 
      while (enumerator.MoveNext() && predicate(current = enumerator.Current)) 
       yield return current; 
     } 
    } 
} 

И ради завершения, перегрузка, которая включает в себя индекс:

public static IEnumerable<TSource> TakeWhileOrFirst<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate) 
{ 
    using (var enumerator = source.GetEnumerator()) 
    { 
     if (!enumerator.MoveNext()) 
      yield break; 
     TSource current = enumerator.Current; 
     int index = 0; 
     yield return current; 
     if (predicate(current, index++)) 
     { 
      while (enumerator.MoveNext() && predicate(current = enumerator.Current, index++)) 
       yield return current; 
     } 
    } 
} 
+0

Я просто удалил аналогичный ответ: это неправильно. Если первый элемент не соответствует предикату, мы должны уступить его и остановиться. Это не так. – Ani

+0

@Ani: Я не уверен, какая интерпретация вопроса - это правая. Но я думаю, что «всегда, по крайней мере, первый элемент» мог означать «всегда элемент головы по крайней мере» и необязательно должен соответствовать условию. Если бы это было иначе, было бы лучше сформулировано: «по крайней мере один элемент, который соответствует условию». Я думаю, мы были в порядке, если ОП не разъяснит. Я посмотрю, смогу ли я найти способ интерпретировать эту интерпретацию и включить ее в мой ответ, если кто-то не доберется до нее первым. –

+0

думаю. Мои рассуждения основывались исключительно на том, как я буду интерпретировать «TakeWhileButAtLeastOne» без контекста. :) – Ani

4

следующие работы: *, и, кажется, довольно хорошо читаемым мне:

seq.Take(1).Concat(seq.TakeWhile(condition).Skip(1)); 

Там может быть лучше, не уверен.

* с благодаря @Jeff M для коррекции

+3

Вам понадобится 'IEnumerable <>' для 'Concat()' to. Я бы использовал 'Take (1)' вместо 'First()'. –

+0

@ Rune: Хорошая точка! – asgerhallas

+0

@Rune: Обратите внимание, что я пропускаю первое значение из принятого набора, вместо того, чтобы пропускать первое значение в наборе, а затем принимать во время согласования условия. Если первый является несоответствием, а следующие, возвращается только первый. –

5

Я думаю, что это делает намерение вполне понятно:

things.TakeWhile(x => x.Whatever).DefaultIfEmpty(things.First()); 

Мой ранее, более многословным решение:

var query = things.TakeWhile(x => x.Whatever); 
if (!query.Any()) { query = things.Take(1); } 
+0

Не думаю, что это делает его менее читаемым. Это просто способ сделать это, используя функциональную парадигму (которую LINQ позволяет нам делать). Если у вас нет функционального кода, но только процедурный код, то да, вероятно, он будет считаться «нечитаемым», но по всем неправильным причинам. –

+1

Говоря нечитабельно, я имел в виду «не сделать первоначальное намерение ясным». Функциональный код может быть вполне читаемым, если сделать правильно. Но я должен признать, что в этом случае действительно есть однострочники, которые делают это ясно. – Botz3000

+0

А это хорошее обновление. Я всегда забываю об этих операциях, когда не занимаюсь написанием внешних объединений. –

1

ОТКАЗ: Это изменение Джеф Ms хороший ответ, и как таковой, предназначены только для просмотра вместо этого используется код do-while. Он предоставляется только в качестве ответа на вопрос Джеффса.

public static IEnumerable<TSource> TakeWhileOrFirst<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) 
{ 
    using (var enumerator = source.GetEnumerator()) 
    { 
     if (!enumerator.MoveNext()) 
      yield break; 
     var current = enumerator.Current; 
     do{ 
      yield return current 
     } while (predicate(current) && 
       enumerator.MoveNext() && 
       predicate(current = enumerator.Current)); 
    } 
} 

, конечно, это вопрос стиля я лично хотел бы иметь низкий уровень вложенности моей условной логики, как это возможно, но двойное использование предиката может быть трудно понять, и может быть свиньей небольшой производительности (в зависимости на оптимизацию и прогнозирование ветвлений)

+0

Если низкий уровень вложенности был целью, я думаю, что это было бы самым чистым с тем, что у меня было (с циклом while), но вместо этого получило «выходный разрыв», если первая проверка –

+0

@Jeff Я считаю, что чем больше точек выхода у вас (назовите их continue, return, goto или что требуется от языка sepc), тем сложнее читать и понимать код, и то же самое происходит для дополнительной разветвления ц. Было показано, что обе они увеличивают вероятность ошибок и, с учетом этого, я думаю, что дополнительная, если и дополнительная точка выхода имеет отрицательное влияние –

Смежные вопросы