tags:

views:

256

answers:

2

(line of code of interest is the last one, the rest is just for a full representation)

Using the following code, I wanted to take VOTERS until I exceeded the maximum votes needed, but it stops right before reaching that maximum number of votes, so my voters pool has 1 fewer voter than I wanted.

Is there a clean way in LINQ where I could have made it take votes UNTIL it reached the maximum numbers of votes? I know I could add one more voter or do this in a loop but I am curious if there was a good way to do this with LINQ instead.

var voters = new List<Person>
                             {
                                 new Person("Alice", Vote.Yes ),
                                 new Person("Bob", Vote.Yes),
                                 new Person("Catherine", Vote.No),
                                 new Person("Denzel", Vote.Yes),
                                 new Person("Einrich", Vote.Abstain),
                                 new Person("Frederica", Vote.Abstain),
                                 new Person("Goeffried", Vote.Abstain),
                             };
            voters.Single(c => c.Name == "Alice").Voices = 100;
            voters.Single(c => c.Name == "Bob").Voices = 150;
            voters.Single(c => c.Name == "Catherine").Voices = 99;
            voters.Single(c => c.Name == "Denzel").Voices = 24;
            voters.Single(c => c.Name == "Einrich").Voices = 52;
            voters.Single(c => c.Name == "Frederica").Voices = 39;
            voters.Single(c => c.Name == "Goeffried").Voices = 99;

// this takes voters until we are BEFORE reaching X voices...
int voicesSoFar = 0;
int voicesNeeded = 300;
var eligibleVoters = voters.TakeWhile((p => (voicesSoFar += p.Voices) < voicesNeeded ));
+4  A: 

Just write your own extension method:

static class IEnumerableExtensions {
    public static IEnumerable<T> TakeUntil<T>(
        this IEnumerable<T> elements,
        Func<T, bool> predicate
    ) {
        return elements.Select((x, i) => new { Item = x, Index = i })
                       .TakeUntil((x, i) => predicate(x.Item))
                       .Select(x => x.Item);
    }

    public static IEnumerable<T> TakeUntil<T>(
        this IEnumerable<T> elements,
        Func<T, int, bool> predicate
    ) {
        int i = 0;
        foreach (T element in elements) {
            if (predicate(element, i)) {
                yield return element;
                yield break;
            }
            yield return element;
            i++;
        }
    }
}

Usage:

var eligibleVoters = voters.TakeUntil(
                         p => (voicesSoFar += p.Voices) >= voicesNeeded
                     );

foreach(var voter in eligibleVoters) {
    Console.WriteLine(voter.Name);
}

Output:

Alice
Bob
Catherine
Jason
That said, your lambda expression that is mutating an outer variable makes me feel yucky. In particular, you can't enumerate `eligibleVoters` twice and see the same results which is just nasty.
Jason
Yeah, I realized that afterward and even started this new question:http://stackoverflow.com/questions/2242371/does-this-code-really-cause-an-access-to-modified-closure-problemAs for now, I'm trying to wrap my head around this code, I'm new to this :P
PRINCESS FLUFF
@PRINCESS FLUFF: Focus on the second method first; the first just invokes the second in a fancy way. Basically I mimiced the fact that `TakeWhile` has two overloads one that is indexed base and the other that is not.
Jason
Alright I got it thanks! I had to look up YIELD again :)
PRINCESS FLUFF
+1 for the double enumeration comment.
Kobi
Thank you for the help, this gave me practice in making my own extension method as well, although I think the one-liner version is a better accepted answer.
PRINCESS FLUFF
+5  A: 

You're looking for

voters.TakeWhile(p => {
   bool exceeded = voicesSoFar > voicesNeeded ;
   voicesSoFar += p.Voices;
   return !exceeded;
});

If you insist on a one-liner, this will work by comparing the previous value:

voters.TakeWhile(p => (voicesSoFar += p.Voices) - p.Voices < voicesNeeded);
Kobi
Note: keep in mind `voicesSoFar` is not correct by the end on the loop, it is juts an helper variable.
Kobi
+1 For a solution that doesn't require writing an unnecessary extension method.
Benny Jobigan
It's odd but I can't get the first version to show anything at all... The one-liner works perfectly though.
PRINCESS FLUFF
Ah, you need to change the last line to: return !exceeded;
PRINCESS FLUFF
Thanks, this seems like the best answer
PRINCESS FLUFF
Right! my bad. Maybe the shorted one is less confusing after all...
Kobi
@Benny Jobigan: The nice thing about such an extension method is not having to resort to such trickery every time such functionality is needed. Encapsulate the desired behavior elsewhere and reuse it when needed.
Jason