39

Seems like a trivial task with LINQ (and probably it is), but I cannot figure out how to drop the last item of squence with LINQ. Using Take and passing the length of the sequence - 1 works fine of course. However, that approach seems quite inconvienient when chaining up multiple LINQ in a single line of code.

IEnumerable<T> someList ....

// this works fine
var result = someList.Take(someList.Count() - 1);


// but what if I'm chaining LINQ ?
var result = someList.Where(...).DropLast().Select(...)......;

// Will I have to break this up?
var temp = someList.Where(...);
var result = temp.Take(temp.Count() - 1).Select(...)........;

In Python, I could just do seq[0:-1]. I tried passing -1 to Take method, but it does not seem to do what I need.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Kei
  • 1,596
  • 1
  • 16
  • 25

3 Answers3

72

For .NET Core 2+ and .NET Standard 2.1 (planned), you can use .SkipLast(1).

For other platforms, you could write your own LINQ query operator (that is, an extension method on IEnumerable<T>), for example:

static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source)
{
    using (var e = source.GetEnumerator())
    {
        if (e.MoveNext())
        {
            for (var value = e.Current; e.MoveNext(); value = e.Current)
            {
                yield return value;
            }
        }
    }
}

Unlike other approaches such as xs.Take(xs.Count() - 1), the above will process a sequence only once.

stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
  • 6
    It's a shame that this answer does not appear in the [flagged dupe](http://stackoverflow.com/questions/1779129/how-to-take-all-but-the-last-element-in-a-sequence-using-linq). It's neater than the accepted answer over there. – spender Nov 13 '15 at 13:29
  • How to adapt this - nicely - so as to skip a number n of last items? – Gerard May 09 '17 at 12:57
  • @Gerard: You don't. The solution in this answered is optimized for the case where *n* = 1. The more general case would involve buffering. IIRC there is an answer showing a solution for the more general case if you go to the question of which this is a duplicate. – stakx - no longer contributing May 09 '17 at 15:33
30
someList.Reverse().Skip(1).Reverse()
Saeed Amiri
  • 22,252
  • 5
  • 45
  • 83
  • 3
    @LukeH, It's O(n) and it's one line answer as OP wants. – Saeed Amiri Nov 12 '10 at 16:13
  • 2
    Oh, why don't I think of this :) This is a clever and simple way doing what's available right out of the box. Not quite sure what the performance implication would be, but if I just need a quick and dirty way to get it done, this is a nice approach. – Kei Nov 12 '10 at 16:15
  • 1
    @Saeed: It is O(n) but it requires the equivalent of four passes through the sequence. The first example in Jamiec's answer is also a one-liner and O(n), but that only needs two passes through the sequence (or a single pass if the sequence implements `ICollection` or `ICollection`.) – LukeH Nov 12 '10 at 16:18
  • @Kei, Its performance is not differ with other linq approaches but if you want a better performance you should do it yourself not by link. – Saeed Amiri Nov 12 '10 at 16:18
  • 19
    Note that this also stores the entire sequence in memory - twice. If the sequence is extremely long then that could be a *lot* of memory. Yes, this technique is O(n) in time, but it is also O(n) in space, where it could be O(1) in space. – Eric Lippert Nov 12 '10 at 16:35
  • @Eric Lippert, thanks for your comment, but how it can be O(1) in space? IMHO It's O(n) naturally, because you have an IEnumerable which takes O(n). – Saeed Amiri Nov 13 '10 at 10:34
  • 1
    @Saeed: Why does an IEnumerable have to take O(n)? Enumerable.Range, for example, is O(1) in space no matter how many items are in the range. – Eric Lippert Nov 13 '10 at 18:29
  • @EricLippert, Enumerable.Range is a false analogy, here the list (the only IEnumerable part involved and the corresponding output) is already in O(n) space, how do you want to obtain an O(1)? (for this very particular problem). – Saeed Amiri Mar 17 '14 at 15:33
  • @SaeedAmiri: Who said that the sequence was O(n) in space? I don't see that anywhere in the original question. – Eric Lippert Mar 17 '14 at 15:36
  • @EricLippert, May be I cannot understand what you mean, but then what is the size of sequence, whatever you call, what is the n you mentioned in your very first comment? Or enlighten me what do you mean by your comments? – Saeed Amiri Mar 17 '14 at 15:38
  • 1
    Re-read the original question. See the bit that says `IEnumerable someList ....`. Suppose that said `IEnumerable someList = Enumerable.Range(0, 1000000000);` That is O(1) in space. Your proposal allocates a stack with a billion items *twice*, making it O(n) in space. The answer from stakx allocates a very small constant amount of memory. – Eric Lippert Mar 17 '14 at 15:42
  • @EricLippert,If you mean the input is possibly the result of IEnumerable.Range then is easy to do e.g Enumerable.Range(2,n) instead of (1,n), so the question is stupid if is Enumerable.Range and you should say this in a comment to the OP if you are not sure that is not Enumerable.Range and is even more stupid if someone uses stackx or mine suggestion. But any natural query or not a fake action already causes to O(n) space. May be you say that is parameter to some other procedure, but then I would say that's just bad architecture which passes Enumerable.Range as a parameter, instead of range. – Saeed Amiri Mar 17 '14 at 15:51
  • 2
    OK, let's suppose for the sake of argument that the sequence is O(n) space. Are you then justified in consuming **twice that amount of space again**? That thing could be huge. – Eric Lippert Mar 17 '14 at 15:59
  • @EricLippert, Short answer: No. Longer answer : But then we are in the my first comment to LukeH, I never said this is a most elegant way to do it, but is somehow easiest and fastest (in development scenario) and not a very dumb solution. I mean that O(n) vs O(1) space is just a very bad label to this solution, but I agree with O(n) extra space as a reasonable argument or passing through the list twice as a reasonable argument for a negative points of this approach, but as I said, that's just one line answer and without very significant problem in a *normal* scenario. – Saeed Amiri Mar 17 '14 at 16:07
  • 1
    If you prefer more realistic examples of where this approach is a problem, then consider the case of `File.ReadLines`, which returns an `IEnumerable`. Because this code has a real chance of introducing a surprise failure (which could easily be missed during testing), I'd be hesitant to use it, especially as a public function. If you strongly prefer a one-liner, I would consider Jamiec's one-liner's to be far safer. Further, I consider it far more obvious what his code is doing, though this code is pretty obvious, too. – Brian Mar 17 '14 at 16:42
  • @Brian, You are free to prefer any code. But don't do psychological projection, I never said that I prefer X or Y, so don't assign it implicitly to me. Also please state very clearly what is a possible "surprise failure" chance in this code, and very explicitly mention why this possible failure does not happens in Jamiec code? Just keep in mind, Jamiec code is very dangerous if the input is some query to database, his answer fetches all data, but may be we do restriction on data (in very natural case), so his answer just explodes the memory. --> – Saeed Amiri Mar 17 '14 at 17:07
  • In the end, if you like is good to say why his answer with such a bad behavior in very natural scenario is more suitable for your taste than my answer? (But you are not force to say it, just if you like). Also see LukeH's comment on his answer, even so you like it? Also his code is not O(n) (even in space). – Saeed Amiri Mar 17 '14 at 17:08
  • @LukeH, I just saw your last comment (I guess), first of all Jamiec answer is wrong as you mentioned in your comments, second, his answer is just bad in performance perspective, specially if we have database query, fetching all information before possibly applying any restrictions is much more worst than traversing array 100 times in RAM. Also Except uses hash table, and even in non-database scenario it can uses more memory and runtime than my algorithm. – Saeed Amiri Mar 17 '14 at 17:21
13

You can quite easily do this with an extension method, either using the form you've already found, or perhaps a combination of Take and Count

public static IEnumerable<T> DropLast<T>(this IEnumerable<T> enumerable)
{
  return enumerable.Take(enumerable.Count()-1);
}
Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • 1
    Does't second way fail to compile? Last() returns T, but Except takes IEnumerable. Am I missing something? – Kei Nov 12 '10 at 16:19
  • @Kei - probably right, I didnt test it, but something along those lines would work. Edited answer – Jamiec Nov 12 '10 at 16:27
  • 11
    The `Except` method is unsuitable for this because it also removes any duplicates from the source sequence. If the source is `{1,2,3,4,5,4,3,2,1}` then the OP would expect the results to be `{1,2,3,4,5,4,3,2}`; using your second example (assuming that you fixed the bug) would give `{2,3,4,5}`. – LukeH Nov 12 '10 at 16:36
  • 2
    this also works: xs.TakeWhile((x, i) => i < xs.Count()-1) – Rezo Megrelidze Jul 12 '14 at 19:07
  • This approach was mentioned **in the question itself** (`var result = someList.Take(someList.Count() - 1);`) and is inefficient code, anyways, as it has to enumerate `enumerable` twice. – Lance U. Matthews Dec 12 '21 at 10:10
  • 1
    Yikes, @RazMegrelidze, don't do that. For an `IEnumerable<>` of `n` elements, the code in this answer will end up enumerating `2n - 1` total elements: one full enumeration to calculate `Count()` followed by a partial enumeration without the `n`th element. Calling `Count()` _for each_ (`TakeWhile()`) of the `n` elements, as you are, will end up enumerating `n² + n` total elements; for a `1000`-element sequence that's over **a million** elements enumerated. **Please**, everyone, just because code works (or appears to), actually consider _what it's doing_ before clicking △ or copy-and-pasting! – Lance U. Matthews Dec 15 '21 at 06:48
  • @LanceU.Matthews luckily we have *SkipLast* now in LINQ. – Rezo Megrelidze Aug 21 '22 at 02:55
  • list.Remove(list.Last()); – Konrad Psiuk Nov 22 '22 at 17:45