2010-02-11 13 views
14

(linia kodu zainteresowania jest ostatnim, reszta jest tylko dla pełnej reprezentacji)Jak mogę zabrać 1 dodatkowy przedmiot z TakeWhile Linq?

Korzystanie poniższy kod, chciałem wziąć wyborców aż przekroczyła maksymalnych głosów potrzebnych, ale zatrzymuje się tuż przed osiągnięciem tej maksymalnej liczby głosów, więc moja pula wyborców ma o 1 mniej wyborcę, niż chciałem.

Czy w LINQ istnieje czysty sposób, w jaki mógłbym zdobyć głosy, AŻ osiągnął maksymalną liczbę głosów? Wiem, że mógłbym dodać jeszcze jednego wyborcę lub zrobić to w pętli, ale jestem ciekawy, czy byłby to dobry sposób na zrobienie tego z LINQ.

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)); 

Odpowiedz

15

Szukacie

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

Jeśli nalegać na jednej liniowej, to będzie działać porównując poprzednią wartość:

voters.TakeWhile(p => (voicesSoFar += p.Voices) - p.Voices < voicesNeeded); 
+0

Uwaga: należy pamiętać, że 'voicesSoFar' nie jest poprawny przed końcem pętli, jest zmienną pomocniczą. – Kobi

+0

+1 Dla rozwiązania, które nie wymaga napisania niepotrzebnej metody rozszerzenia. –

+0

To dziwne, ale nie mogę dostać pierwszej wersji, żeby pokazać cokolwiek ... Jedyny liniowiec działa idealnie. –

6

Wystarczy napisać własną metodę rozszerzenia:

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++; 
     } 
    } 
} 

Zastosowanie:

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

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

wyjściowa:

Alice 
Bob 
Catherine 
+1

To powiedziawszy, twoja ekspresja lambda, która mutuje zmienną zewnętrzną, sprawia, że ​​czuję się podekscytowany. W szczególności nie można dwukrotnie wyliczyć "kwalifikowanych użytkowników" i zobaczyć te same wyniki, które są po prostu nieprzyjemne. – jason

+0

Tak, zdałem sobie sprawę, że potem, a nawet zaczęło się to nowe pytanie: http://stackoverflow.com/questions/2242371/does-the-code-revious-cause-an-access-to-modified-closure-problem As na razie staram się objąć tym kodem, jestem nowy: P –

+1

@PRINCESS FLUFF: Skoncentruj się na drugiej metodzie; pierwszy po prostu przywołuje drugi w fantazyjny sposób. Zasadniczo naśladowałem fakt, że 'TakeWhile' ma dwa przeciążenia, jeden jest indeksowany, a drugi, który nie jest. – jason

19

W sytuacji, gdy chciałem wykonać funkcję aż włącznie uderzył warunek końcowy zrobiłem :

public static IEnumerable<T> TakeUntilIncluding<T>(this IEnumerable<T> list, Func<T, bool> predicate) 
{ 
    foreach(T el in list) 
    { 
     yield return el; 
     if (predicate(el)) 
      yield break; 
    } 
} 

Pracowałem dla mnie! Myślę, że jest to rozwiązanie niezależne od implementacji, takie jak Jason, ale prostsze.

+1

I nie ma zmiennej stanu zewnętrznego/przechwyconego. – Tormod

0

Zmiana odpowiedzi Kobi, ale demonstruje użycie (value, index). index jest przydatny w rozwiązywaniu podobnych problemów, ale nie w PO.

voters.TakeWhile((value, index) => (voicesSoFar += value.Voices) - value.Voices < voicesNeeded); 
0

Miałem do czynienia z tym samym problemem. Użyłem Unię i Przejdź metod, tak aby wziąć aż było

IEnumerable<Something> newSomethings = somethings.TakeWhile(s => s != stop).Union(new List<Something>(){stop}); 

i skrzyni ładunkowej aż

IEnumerable<Something> newSomethings = somethings.SkipWhile(s => s != stop).Skip(1); 

Istnieje również Take metodę, która przyjmuje niektóre int pierwszych wyników.

Powiązane problemy