C#:Внедрение SkipLast
-
13-09-2019 - |
Вопрос
Мне нужен был метод, который давал бы мне все элементы последовательности, кроме последнего.Это моя текущая реализация:
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
способ.Поскольку это в некотором роде то, что есть.В МореЛинк имеет MaxBy
и MinBy
метод и мой метод вроде как должны делать то же самое, но возвращать все элементы, кроме максимального или минимального.
Решение
Это сложно, так как "последний элемент" не является марковской точкой остановки:вы не можете сказать, что добрались до последнего элемента, пока не попытаетесь получить следующий.Это выполнимо, но только если вы не возражаете постоянно быть "на один элемент позади".Это в основном то, что делает ваша текущая реализация, и это выглядит нормально, хотя я бы, вероятно, написал это немного по-другому.
Альтернативный подход заключался бы в использовании 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 вместо while(true)
Другие советы
Ваша реализация выглядит для меня совершенно нормально - вероятно, именно так я бы это и сделал.
Единственное упрощение, которое я мог бы предложить применительно к вашей ситуации, - это упорядочить список наоборот (т.е.восходящий, а не нисходящий).Хотя это может быть неуместно в вашем коде, это позволило бы вам просто использовать collection.Skip(1)
взять все элементы, кроме самого последнего.
Если это невозможно по причинам, которые вы не указали в своем сообщении, то ваша текущая реализация вообще не представляет проблемы.
Если вы используете .NET 3.5, я думаю, вы могли бы использовать:
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source)
{
return source.TakeWhile((item, index) => index < source.Count() - 1))
}
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);
}
}
(Старый ответ удален;этот код был протестирован и работает.) Он печатает
Первый
второй
Первый
ВТОРОЙ
ТРЕТИЙ
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);
}
}