2010-02-11 13 views
14

(línea de código de interés es el último, el resto es sólo para una representación completa)¿Cómo podría tomar 1 artículo más de TakeWhile de Linq?

Usando el siguiente código, que quería tomar VOTANTES hasta que superó el máximo de votos necesarios, pero se detiene justo antes de alcanzar ese número máximo de votos, mi grupo de votantes tiene 1 votante menos de lo que yo quería.

¿Hay alguna manera limpia en LINQ en la que podría haberlo hecho tomar los votos HASTA que haya alcanzado el número máximo de votos? Sé que podría agregar un votante más o hacer esto en un ciclo, pero tengo curiosidad de saber si había una buena manera de hacerlo con 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)); 

Respuesta

15

Usted está buscando

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

Si insiste en una sola línea, esto funcionará mediante la comparación del valor anterior:

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

Nota: tenga en cuenta que 'voicesSoFar' no es correcto para el final del ciclo, sino que es una variable auxiliar. – Kobi

+0

+1 Para una solución que no requiere escribir un método de extensión innecesario. –

+0

Es extraño, pero no puedo obtener la primera versión para mostrar nada ... Sin embargo, el one-liner funciona perfectamente. –

6

Sólo tiene que escribir su propio método de extensión:

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

Uso:

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

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

Salida:

Alice 
Bob 
Catherine 
+1

Dicho esto, su expresión lambda que está mutando una variable externa me hace sentir asqueroso. En particular, no puede enumerar 'eligibleVoters' dos veces y ver los mismos resultados, que es desagradable. – jason

+0

Sí, me di cuenta de que después e incluso comencé esta nueva pregunta: http://stackoverflow.com/questions/2242371/does-this-code-really-cause-an-access-to-modified-problem As por ahora, estoy tratando de entender este código, soy nuevo en esto: P –

+1

@PRINCESS FLUFF: Primero concéntrese en el segundo método; el primero solo invoca el segundo de una manera elegante. Básicamente, imité el hecho de que 'TakeWhile' tiene dos sobrecargas, una que está indexada en la base y la otra que no lo es. – jason

19

En una situación en la que quería ejecutar una función, hasta e incluyendo golpeó una condición final lo hice :

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

¡Funcionó para mí! Creo que esta es una solución independiente de la implementación como la de Jason, pero más simple.

+1

Y sin variable de estado externo/capturado. – Tormod

0

Variación de la respuesta de Kobi pero demuestra el uso de (value, index). index es útil para resolver problemas similares, aunque no de OP.

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

Me enfrentaba el mismo problema. he utilizado Unión y Skip métodos, por lo que tomar hasta que fue

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

y de salto hasta

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

También es tomar método, que lleva algún int de los primeros resultados.

Cuestiones relacionadas