tags:

views:

151

answers:

3

Hi everyone,

Quick question, what's the preferable way to programmaticaly ask "Is there exactly one element in this sequence that satisfies X condition?" using Linq?

i.e.

// Pretend that the .OneAndOnlyOne() method exists
int[] sequence = new int[] { 1, 1, 2, 3, 5, 8 };
Assert.IsTrue(sequence.OneAndOnlyOne(x => x == 2);
Assert.IsFalse(sequence.OneAndOnlyOne(x => x == 1);

something like this can be done with:

sequence.SingleOrDefault(x => x == 2) != null;

but that's a little clunky.

I suppose I could roll my own extension method, but this seems to be a common pattern in my code and I want to make sure there's a good clean way to do it. Is there a way using the built-in LINQ methods?

Thanks!

-Mike

A: 

The simplest way is to just use Count. Single won't work for you, because it throws an exception if there isn't just that single element.

LBushkin suggests (in the comments) to use SequenceEqual to compare a sequence with another one. You could use that by skipping the first element with Skip(1), and comparing the resulting sequence to an empty sequence such as what you can get from Empty

Michael Madsen
Yeah, but if my .OneAndOnlyOne() method runs on a sequence and hits a 2nd element that satisfies the predicate - it can return false right away. Count() will enumerate the entire sequence
Mike
That is true, but going "beyond" Count might be a case of premature optimization - depending on your needs, of course. If you want to prevent that, you could use .Skip(1) and see if you get an empty sequence.
Michael Madsen
@Michael: You don't need to use Count. You can use `SequenceEqual()` which handles empty sequences just fine without throwing exceptions. And, you can always roll your own extension method ... not that it's really necessary for the OP's use case.
LBushkin
Huh. Didn't even know that existed (probably because I've never needed it). I'll update. As for the extension method: of course you could write one, but you still need to know how to write it, and as far as I'm aware, it's not really feasible to write one from scratch (that is, not using the existing LINQ methods).
Michael Madsen
+12  A: 

You could do:

bool onlyOne = source.Where(/*condition*/).Take(2).Count() == 1

Which will prevent count from enumerating a large sequence unnecessarily in the event of multiple matches.

Lee
+1, and I wish I could give you another +1 for the `.Take(2)` portion (as that very much makes sense.)
Adam Maras
heh, I kinda like that. I'll probably alias that behind my own extension method.
Mike
Why do you take 2 elements? What sense is behind that? ;)
citronas
If there was no .Take(2), Count() would iterate through the whole collection counting how many elements there are. If there are many, or even an infinite number of items in the collection it will take a long/infinite amount of time. With the .Take(2) it will iterate over at most 2 items.
ICR
A: 

You could use this extension method, which I think should have been included in the standard extension methods for linq:

public static int CountUpTo<T>(this IEnumerable<T> sequence, int maxCount) {
    if (sequence == null) throw new ArgumentNullException("sequence");
    if (maxCount < 0) throw new ArgumentOutOfRangeException("maxCount");

    var count = 0;
    var enumerator = sequence.GetEnumerator();
    while (count < maxCount && enumerator.MoveNext())
        count += 1;
    return count;
}

Used like so:

return sequence.CountUpTo(2) == 1;
Strilanc