513

I would like to use Linq to query a bus schedule in my project, so that at any time I can get the next 5 bus arrival times. How can I limit my query to the first 5 results?

More generally, how can I take a slice of a list in C#? (In Python I would use mylist[:5] to get the first 5 elements.)

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
RexE
  • 17,085
  • 16
  • 58
  • 81

7 Answers7

929
var firstFiveItems = myList.Take(5);

Or to slice:

var secondFiveItems = myList.Skip(5).Take(5);

And of course often it's convenient to get the first five items according to some kind of order:

var firstFiveArrivals = myList.OrderBy(i => i.ArrivalTime).Take(5);
Matt Hamilton
  • 200,371
  • 61
  • 386
  • 320
  • 133
    Does it throw exception if there are only, for example, 3 items in the list? Or will it take as many as there are up to 5? – bobek Feb 22 '12 at 21:08
  • 134
    @bobek : It does not throw an exception. It simply returns what it has if there aren't enough elements. – KatDevsGames Sep 19 '12 at 04:15
  • 1
    exactly, no exceptions thrown Skip and Take combined solved my problem as I wanted take any generic collection and process x items per batch – JohanLarsson Jun 24 '15 at 09:02
  • 12
    It should be noted that `.Take(n)` returns a TakeIterator; it does not return a list with `n` elements in it (assuming that that many are available). Use `.ToArray()` or `.ToList()` on the result of the `Take` to get a concrete array or list. – Bellarmine Head May 22 '20 at 07:56
  • Also, it looks like ATM you actually *need* to always use OrderBy. This is what EntityFramework told me in the exception message without it: `The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip'.` I'm assuming it's the same for 'Take', but I may be wrong. – S. Kalabukha Dec 11 '20 at 17:50
80

In case anyone is interested (even if the question does not ask for this version), in C# 2 would be: (I have edited the answer, following some suggestions)

myList.Sort(CLASS_FOR_COMPARER);
List<string> fiveElements = myList.GetRange(0, 5);
netadictos
  • 7,602
  • 2
  • 42
  • 69
  • Maybe add an anonymous predicate as well? – AlexeyMK Nov 26 '08 at 08:33
  • 2
    List.Sort returns void; you would need to sort, then use GetRange separately. You could also use a Comparison anonymous method to remove the need for CLASS_FOR_COMPARER. – Marc Gravell Nov 26 '08 at 08:39
  • @AlexeyMK - you mean a Comparison, not a predicate (Predicate) - a predicate is used to filter data – Marc Gravell Nov 26 '08 at 08:40
  • 1
    I believe this answer is useful even now, 10 years and many C# versions later. For the specific case where you have a list. Especially if you are skipping many items. E.g. you have a list of one million items, and you want a slice of 5 of them, far into the list. GetRange knows exactly where to go to grab them. I don't know whether `Skip` + `Take` is as smart, or whether it enumerates over the skipped items. And I don't need to know -- I just use GetRange (when given a List). Just make sure you realize second parameter is *count* (rather than *last index*). – ToolmakerSteve Mar 01 '18 at 05:45
  • The nice thing about `.Take(n)` is that you don't have to worry if there are less than `n` elements in the sequence it works on. The problem with `List.GetRange(0, count)` is that you do have to worry.... you will get an `ArgumentException` if there aren't `count` items. – Bellarmine Head May 22 '20 at 08:04
10

Like pagination you can use below formule for taking slice of list or elements:

var slice = myList.Skip((pageNumber - 1) * pageSize)
                  .Take(pageSize);

Example 1: first five items

var pageNumber = 1;
var pageSize = 5;

Example 2: second five items

var pageNumber = 2;
var pageSize = 5;

Example 3: third five items

var pageNumber = 3;
var pageSize = 5;

If notice to formule parameters pageSize = 5 and pageNumber is changing, if you want to change number of items in slicing you change pageSize.

Sina Lotfi
  • 3,044
  • 1
  • 23
  • 30
6

Working example:

    [Test]
    public void TestListLinqTake()
    {
        List<string> elements = new List<string>() { "storm", "earth", "fire"};
        List<string> noErrorThrown = elements.Take(5).ToList();
        List<string> someElements = elements.Take(2).ToList();

        Assert.True(
            someElements.First().Equals("storm") &&
            someElements.Count == 2 &&
            noErrorThrown.Count == 3);
    }

Don't forget

using System.Linq;

Based on Bellarmine Head's comment

Heinzlmaen
  • 869
  • 10
  • 20
3

To take first 5 elements better use expression like this one:

var firstFiveArrivals = myList.Where([EXPRESSION]).Take(5);

or

var firstFiveArrivals = myList.Where([EXPRESSION]).Take(5).OrderBy([ORDER EXPR]);

It will be faster than orderBy variant, because LINQ engine will not scan trough all list due to delayed execution, and will not sort all array.

class MyList : IEnumerable<int>
{

    int maxCount = 0;

    public int RequestCount
    {
        get;
        private set;
    }
    public MyList(int maxCount)
    {
        this.maxCount = maxCount;
    }
    public void Reset()
    {
        RequestCount = 0;
    }
    #region IEnumerable<int> Members

    public IEnumerator<int> GetEnumerator()
    {
        int i = 0;
        while (i < maxCount)
        {
            RequestCount++;
            yield return i++;
        }
    }

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }

    #endregion
}
class Program
{
    static void Main(string[] args)
    {
        var list = new MyList(15);
        list.Take(5).ToArray();
        Console.WriteLine(list.RequestCount); // 5;

        list.Reset();
        list.OrderBy(q => q).Take(5).ToArray();
        Console.WriteLine(list.RequestCount); // 15;

        list.Reset();
        list.Where(q => (q & 1) == 0).Take(5).ToArray();
        Console.WriteLine(list.RequestCount); // 9; (first 5 odd)

        list.Reset();
        list.Where(q => (q & 1) == 0).Take(5).OrderBy(q => q).ToArray();
        Console.WriteLine(list.RequestCount); // 9; (first 5 odd)
    }
}
Valera Kolupaev
  • 2,285
  • 14
  • 14
  • 27
    Except that you're now ordering only the first 5 elements after you've selected them. It may be faster, but it also has different semantics, which are less likely to be what people actually want to achieve. – Greg Beech Nov 26 '08 at 09:01
3
        dataGridView1.DataSource = (from S in EE.Stagaire
                                    join F in EE.Filiere on
                                    S.IdFiliere equals F.IdFiliere
                                    where S.Nom.StartsWith("A")
                                    select new
                                    {
                                        ID=S.Id,
                                        Name = S.Nom,
                                        Prénon= S.Prenon,
                                        Email=S.Email,
                                        MoteDePass=S.MoteDePass,
                                        Filiere = F.Filiere1
                                    }).Take(1).ToList();
  • 2
    Hi Mouad, thank you for your answer. While your code might solve the problem in the posted question, good answers explain *why* your solution works. Please consider updating your question. – Connor Low Feb 20 '21 at 00:56
1

I think this is the correct answer, relevant to c# versions starting from 8.0:

Yes! It allows us to work exactly the same as in Python.

From c# 8.0 docs:

C# 8.0 feature specifications:

This feature is about delivering two new operators that allow constructing System.Index and System.Range objects, and using them to index/slice collections at runtime.

C# refer to the dot chars (..) as the range operator

Examples:

var array = new int[] { 1, 2, 3, 4, 5 };
var slice1 = array[2..^3];    // array[new Range(2, new Index(3, fromEnd: true))]
var slice2 = array[..^3];     // array[Range.EndAt(new Index(3, fromEnd: true))]
var slice3 = array[2..];      // array[Range.StartAt(2)]
var slice4 = array[..];       // array[Range.All]
Ester Kaufman
  • 708
  • 10
  • 20