20

I'd like to use the LINQ TakeWhile function on LINQ to Objects. However, I also need to know the first element that "broke" the function, i.e. the first element where the condition was not true.

Is there a single function to get all of the objects that don't match, plus the first that does?

For example, given the set {1, 2, 3, 4, 5, 6, 7, 8},

mySet.MagicTakeWhile(x => x != 5);

=> {1, 2, 3, 4, 5}

AustinWBryan
  • 3,249
  • 3
  • 24
  • 42
David Pfeffer
  • 38,869
  • 30
  • 127
  • 202
  • You can write a method like that easily, but this won't be "lazy" since you must know the position of the last item in order to get the next one. – Amiram Korach Aug 09 '12 at 12:33
  • [This question](https://stackoverflow.com/q/2242318/241211) is not quite a duplicate, but shares [an answer.](https://stackoverflow.com/a/6817553/241211) – Michael Oct 11 '17 at 15:27

3 Answers3

13

I think you can use SkipWhile, and then take the first element.

var elementThatBrokeIt = data.SkipWhile(x => x.SomeThing).Take(1);

UPDATE

If you want a single extension method, you can use the following:

public static IEnumerable<T> MagicTakeWhile<T>(this IEnumerable<T> data, Func<T, bool> predicate) {
    foreach (var item in data) {
        yield return item;
        if (!predicate(item))
            break;
    }
}
VV5198722
  • 374
  • 5
  • 19
Maarten
  • 22,527
  • 3
  • 47
  • 68
  • 1
    That would require multiple calls. I could just do `FirstOrDefault` in that case. – David Pfeffer Aug 09 '12 at 12:31
  • @Maarten, good answer - you can save a line by removing the final `yield break;` – allonhadaya Apr 23 '13 at 21:17
  • The original answer one-liner can't work. That is not how it works. .Take(n) takes a collection and returns the same collection, but caps it after n element(s). In this case. It will take the collection returned from SkipWhile and then take 1 element from it. Effectively a FirstOrDefault. It will NOT "append" the collection. – Tormod Aug 29 '19 at 06:16
  • This didn't work for me when the element was the first in the list - I get the first two elements. Ended up using this instead: https://stackoverflow.com/a/3098714/1579626 – sǝɯɐſ Jan 26 '21 at 14:53
7

LINQ to Objects doesn't have such an operator. But it's straightforward to implement a TakeUntil extension yourself. Here's one such implementation from moreLinq.

Ani
  • 111,048
  • 26
  • 262
  • 307
0

Just for fun:

var a = new[] 
    {
        "two",
        "three",
        "four",
        "five",
    };
  Func<string, bool> predicate = item => item.StartsWith("t");      
  a.TakeWhile(predicate).Concat(new[] { a.SkipWhile(predicate).FirstOrDefault() })
Dennis
  • 37,026
  • 10
  • 82
  • 150
  • This also takes two function calls. Also, you could skip the `SkipWhile` and go right to `a.FirstOrDefault(predicate)`. – David Pfeffer Aug 10 '12 at 11:43
  • It's worth noting that this doesn't work if the source enumerable changes with each enumeration, i.e. a stream of numbers from a RNG. – Enigmativity Oct 12 '20 at 23:19