8

There is the enumerable extension method

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

which takes the first count elements from the start.

Is there a way to take the elements from the end? or even better a way to take the elements from an offset to the end?

Thanks

Fabiano
  • 5,124
  • 6
  • 42
  • 69

4 Answers4

16
finiteList.Reverse().Take(count).Reverse();

or

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

There is some overhead in doing this so a custom method would be better.

Update: A custom method

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;
    }
}

Update: Changed the code a littlebit with ideas from dtb´s answer :-)

Comment to Bear: Look at this example:

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...

You could potentially change the values of lastFive2 and therefore that approach can be unsafe or at least it´s not the functional way.

To Bear:

What I meant about safe is this:

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?

In these cases you would have to make a copy to be sure. But in most cases your way would be fine - and a little bit more efficient than this so +1 :)

An idea is to use a queue which only have internal Enqueue etc.

Lasse Espeholt
  • 17,622
  • 5
  • 63
  • 99
  • Yikes, that'll work, but it's not gonna be efficient, nor will it be elegant. – Mark Sep 23 '10 at 17:11
  • @Mark As I state, I agree with you :) I will come up with a more efficient method... – Lasse Espeholt Sep 23 '10 at 17:13
  • Also, depending on what `finiteList` is, it may or may not even be POSSIBLE to enumerate it twice (which your second suggestion would require) – Mark Sep 23 '10 at 17:14
  • second one is fine, but first is uuugly – Andrey Sep 23 '10 at 17:15
  • arg. didn't see the skip method – Fabiano Sep 23 '10 at 17:28
  • TakeLast doesnt need to be an iterator as its aggregating. The redundant yields can be replaced with return queue. – Bear Monkey Sep 23 '10 at 18:22
  • @Bear ahh yes! Didn't even see that :D thanks for your contribution :) – Lasse Espeholt Sep 23 '10 at 18:39
  • @Bear Hhm no, not quite. A `(Queue)some.TakeLast(5)` could give unexpected results... but generally, it would be fine :) – Lasse Espeholt Sep 23 '10 at 18:41
  • @lasse Not sure I follow? See post below for what i meant. – Bear Monkey Sep 23 '10 at 18:58
  • @Bear Yes, I know what you meant, but I have updated with an example of what I mean :) – Lasse Espeholt Sep 23 '10 at 19:12
  • @lasse ok i see what you mean but it is safe because your not mutating the source collection. Nonetheless your right it probably not functional though. – Bear Monkey Sep 23 '10 at 19:31
  • @lasse ok i concede, i think your approach is better than mine ;) Its shame ReadOnlyCollection doesnt take an ICollection then we could just wrap queue in that and we both win! – Bear Monkey Sep 23 '10 at 19:49
  • @Bear I like your solution - I would use it when skipping a enumerator would be important (properly not very often, though). ReadOnlyCollection would also wrap the IEnumerable so it is properly implemented like my loop and therefore the efficiency is gone. – Lasse Espeholt Sep 23 '10 at 19:56
  • @lasse Ok good point about ReadOnlyCollection but what about ReadOnlyEnumerable? Your thought would be appreciated. Ive added the class to my post I made to my earlier. I could make this a new question if you think its interesting enough. – Bear Monkey Sep 23 '10 at 22:28
3

MoreLINQ provides a TakeLast extension method:

var last10 = finiteList.TakeLast(10);

To take the elements from an offset to the end, Enumerable.Skip should do the trick:

var allFromOffsetToEnd = finiteList.Skip(offset);
dtb
  • 213,145
  • 36
  • 401
  • 431
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(); 
} 
Bear Monkey
  • 513
  • 2
  • 9
  • What exactly is `ReadOnlyEnumerable` there for? The `IEnumerable`/`IEnumerable` interfaces are already read-only. – LukeH Sep 23 '10 at 23:03
  • Please see the accepted answer. I've added this is a reply to a discussion with lasseespeholt. Basically its to guard against modification by consumers who could upcast the IEnumerable to a Queue and modify it. I was hoping to get his opinion. – Bear Monkey Sep 23 '10 at 23:13
1

A note on performance. Plenty of answers here operating on IEnumerable<> and that is probably what you need and should use.

But if the datasets are large and of type List<> or similar, you can prevent a lot of unnecessary iterating with something like:

// 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];
    }
}
H H
  • 263,252
  • 30
  • 330
  • 514
  • +1 And as a comment, most of .Net framework code (including LINQ methods) checks whether the arguments are lists, arrays or just IEnumerables and then outputs the answer the most efficient way :) A combined method would be nice... – Lasse Espeholt Sep 23 '10 at 19:16
  • @lassee: I was wondering if this could work automatically by overload resolution or that you have to write a wrapper that uses is/as. – H H Sep 23 '10 at 19:24
  • Overloading would do it, but an advantage with the is/as approach is that if you are giving a source you don't is a list, then you would have to make the is/as yourself. – Lasse Espeholt Sep 23 '10 at 19:36