метод расширения linq для извлечения элементов из конца последовательности - PullRequest
7 голосов
/ 23 сентября 2010

Существует перечисляемый метод расширения

Take<TSource>(
    IEnumerable<TSource> source,
    int count
)

, который берет первые count элементы с самого начала.

Есть ли способ получить элементы с конца?или еще лучше способ взять элементы от смещения до конца?

Спасибо

Ответы [ 4 ]

13 голосов
/ 23 сентября 2010
finiteList.Reverse().Take(count).Reverse();

или

finiteList.Skip(finiteList.Count() - count)

В этом есть некоторые издержки, поэтому лучше использовать пользовательский метод.

Обновление: Пользовательский метод

public static class EnumerableExtensions
{
    public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int count)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (count < 0) throw new ArgumentOutOfRangeException("count");

        if (count == 0) yield break;

        var queue = new Queue<T>(count);

        foreach (var t in source)
        {
            if (queue.Count == count) queue.Dequeue();

            queue.Enqueue(t);
        }

        foreach (var t in queue)
            yield return t;
    }
}

Обновление: Изменен небольшой код с идеями из ответа dtb: -)

Комментарий для Bear: Посмотрите на этот пример:

var lastFive = Enumerable.Range(1, 10).TakeLast(5);
var lastFive2 = Enumerable.Range(1, 10).TakeLast2(5); //Bear´s way

Queue<int> q = (Queue<int>)lastFive2;
q.Dequeue();

//Is lastFive2 still last five? no...

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

.

Что я имел в виду под сейфом, так это:

var lastFive2 = Enumerable.Range(1, 10).TakeLast2(5); //Bear´s way

//some = Some method which you don't control - it could be from another assembly which represents a crazy plugin etc.
some(lastFive2);
//Now what?

В этих случаях вам придется сделать копию, чтобы быть уверенным.Но в большинстве случаев ваш путь будет в порядке - и немного более эффективным, чем этот, поэтому +1:)

Идея состоит в том, чтобы использовать очередь, которая имеет только внутренний Enqueue и т. Д.

3 голосов
/ 23 сентября 2010

MoreLINQ предоставляет метод расширения TakeLast :

var last10 = finiteList.TakeLast(10);

Чтобы взять элементы со смещения до конца, Enumerable.Skip должен сделать трюк:

var allFromOffsetToEnd = finiteList.Skip(offset);
2 голосов
/ 23 сентября 2010

@ lasseespeholt:

public static class EnumerableExtensions
{
    public static ReadOnlyEnumerable<T> AsReadOnly<T>(
         this IEnumerable<T> source)
    {
        return new ReadOnlyEnumerable<T>(source);
    }
}

public sealed class ReadOnlyEnumerable<T> : IEnumerable<T>
{
    private readonly IEnumerable<T> _source;

    public ReadOnlyEnumerable(IEnumerable<T> source)
    {
        if (_source == null)
        {
            throw new ArgumentNullException("source");
        }

        _source = source;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _source.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _source.GetEnumerator();
    }
}

public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int count) 
{ 
    if (source == null) throw new ArgumentNullException("source"); 
    if (count < 0) throw new ArgumentOutOfRangeException("count"); 

    if (count == 0) 
       return Enumerable.Empty<T>();

    var queue = new Queue<T>(count); 

    foreach (var t in source) 
    { 
        if (queue.Count == count) queue.Dequeue(); 

        queue.Enqueue(t); 
    } 

    return queue.AsReadOnly(); 
} 
1 голос
/ 23 сентября 2010

Примечание о производительности. Здесь много ответов, работающих на IEnumerable<>, и это, вероятно, то, что вам нужно и нужно использовать.

Но если наборы данных велики и имеют тип List<> или аналогичный, вы можете предотвратить множество ненужных повторений, например:

// demo, no errorhandling
public static IEnumerable<T> TakeFrom<T>(this IList<T> list, int offset)
{
    for (int i = offset; i < list.Count; i += 1)
    {
        yield return list[i];
    }
}
...