2009-06-09 1 views
0

Мне нужен был способ, чтобы дать мне все, кроме последнего элемента в последовательности. Это моя текущая реализация:C#: реализация SkipLast

public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source) 
    { 
     using (IEnumerator<T> iterator = source.GetEnumerator()) 
     { 
      if(iterator.MoveNext()) 
       while(true) 
       { 
        var current = iterator.Current; 
        if(!iterator.MoveNext()) 
         yield break; 
        yield return current; 
       } 
     } 
    } 

Для чего мне это нужно - это что-то делать со всеми предметами, кроме последнего. В моем случае у меня есть последовательность объектов с различными свойствами. Затем я заказываю их по дате, а затем мне нужно выполнить настройку для всех из них, за исключением последнего элемента (который будет последним после заказа).

Дело в том, что я не слишком вхожу в эти счетчики, но на самом деле у меня нет никого, кто мог бы спросить: p Что мне интересно, если это хорошая реализация, или если я сделал небольшую или Большая ошибка. Или, может быть, это проблема, является странной и т. Д.

Я предполагаю, что более общей реализацией может быть метод AllExceptMaxBy. Так как это то, что есть. У MoreLinq есть метод MaxBy и MinBy, и мой метод должен делать то же самое, но возвращать каждый элемент, кроме максимального или минимального.

ответ

8

Это сложно, поскольку «последний элемент» не является точкой остановки Маркова: вы не можете сказать, что у вас есть последний элемент, пока не попытаетесь получить следующий. Это выполнимо, но только если вы не возражаете, чтобы постоянно быть «одним элементом позади». Это в основном то, что делает ваша текущая реализация, и это выглядит хорошо, хотя я бы, вероятно, написал ее несколько иначе.

Альтернативный подход заключается в использовании foreach, всегда получая ранее возвращаемое значение, если вы не были на первой итерации:

public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source) 
{ 
    T previous = default(T); 
    bool first = true; 
    foreach (T element in source) 
    { 
     if (!first) 
     { 
      yield return previous; 
     } 
     previous = element; 
     first = false; 
    } 
} 

Другой вариант, ближе к коду:

public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source) 
{ 
    using (IEnumerator<T> iterator = source.GetEnumerator()) 
    { 
     if(!iterator.MoveNext()) 
     { 
      yield break; 
     } 
     T previous = iterator.Current; 
     while (iterator.MoveNext()) 
     { 
      yield return previous; 
      previous = iterator.Current; 
     } 
    } 
} 

Это избегает гнездования столь же глубоко (делая ранний выход, если последовательность пуста), и использует «реальное» условие, а не while(true)

+0

Что такое точка остановки Маркова? В любом случае, хорошие предложения! Глубокое гнездование и пока (правда) были некоторыми из причин, почему мое решение было немного странным, хе-хе. – Svish

+0

@ Jon, спасибо за разъяснение, что такое настоящая проблема с функциями Last-family. В моем случае, похоже, что SkipLast (5) должен быть 5 элементов позади ..., который, пожалуй, выглядит как хорошая работа для очереди. –

1

Ваша реализация выглядит прекрасно для меня - возможно, так я и сделал бы это.

Единственное упрощение, которое я мог бы предложить в связи с вашей ситуацией, - заказать список в обратном направлении (т. Е. Восходящий, а не убывающий). Хотя это может оказаться неприемлемым в вашем коде, это позволит вам просто использовать collection.Skip(1), чтобы взять все элементы, кроме самого последнего.

Если это невозможно по причинам, которые вы не указали в своем сообщении, то ваша текущая реализация не является проблемой вообще.

+0

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

0

Если вы используете .NET 3.5, я думаю, вы могли бы использовать:

public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source) 
{ 
    return source.TakeWhile((item, index) => index < source.Count() - 1)) 
} 
+0

Это перечисляет коллекцию дважды, однако (если только sourcis фактически не является списком), значит, это, наверное, плохая идея! (И даже не будет работать одинаково в общем случае недетерминированного итератора.) – Noldorin

+1

Для Count также потребуется Count() вместо Count. И на самом деле, он будет перечислять коллекцию много, много раз, потому что выражение лямбда будет считать узлы каждый раз, когда она будет оценена! –

+0

для моего собственного образования: было бы хорошо, если бы счет был сделан за пределами лямбда? Я не совсем понимаю, почему он дважды перечислил коллекцию. – Razzie

0
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source) 
{ 
    if (!source.Any()) 
    { 
     yield break; 
    } 
    Queue<T> items = new Queue<T>(); 
    items.Enqueue(source.First()); 
    foreach(T item in source.Skip(1)) 
    { 
     yield return items.Dequeue(); 
     items.Enqueue(item); 
    } 
} 
0

(Старый ответ на слом; этот код был протестирован и работает.) Он печатает
первого
второго
ПЕРВОГО
ВТОРОГО
ТРЕТЬЕГО


public static class ExtNum{ 
    public static IEnumerable skipLast(this IEnumerable source){ 
    if (! source.Any()) 
     yield break; 
    for (int i = 0 ; i <=source.Count()-2 ; i++) 
     yield return source.ElementAt(i); 
    yield break; 
    } 
} 
class Program 
{ 
    static void Main(string[] args) 
    { 
    Queue qq = new Queue(); 
    qq.Enqueue("first");qq.Enqueue("second");qq.Enqueue("third"); 
    List lq = new List(); 
    lq.Add("FIRST"); lq.Add("SECOND"); lq.Add("THIRD"); lq.Add("FOURTH"); 
    foreach(string s1 in qq.skipLast()) 
     Console.WriteLine(s1); 
    foreach (string s2 in lq.skipLast()) 
     Console.WriteLine(s2); 
    } 
} 
+0

В IEnumerable нет счетчика или индексатора. Поэтому, чтобы сделать это, вы должны были бы включить его в список или что-то в этом роде. И это означает, что вам придется пройти через элементы дважды. Однажды, чтобы поместить их в список, и один раз, чтобы дать им. – Svish