8

有可枚举的扩展方法

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

它从一开始就采用第一个count元素。

有没有办法从最后获取元素?甚至更好的方法将元素从偏移量带到最后?

谢谢

4

4 回答 4

15
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 回答的想法稍微更改了代码 :-)

对熊的评论:看这个例子:

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 等的队列。

于 2010-09-23T17:09:04.693 回答
3

MoreLINQ提供了TakeLast 扩展方法

var last10 = finiteList.TakeLast(10);

要将元素从偏移量带到末尾,Enumerable.Skip应该可以解决问题:

var allFromOffsetToEnd = finiteList.Skip(offset);
于 2010-09-23T17:18:12.583 回答
2

@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(); 
} 
于 2010-09-23T18:57:42.083 回答
1

关于性能的说明。这里有很多答案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];
    }
}
于 2010-09-23T17:57:42.403 回答