2010-02-11 70 views
14

(感兴趣的代码行是最后一个,剩下的只是一个完整的表示)我怎么能从Linq的TakeWhile中拿出更多的物品?

使用下面的代码,我想采取VOTERS直到我超过所需的最大票,但它就停在达到最大数量的选票之前,我的选民池比我想要的要少一个选民。

LINQ中有没有一种干净的方式,我可以让它投票直到达到最大票数?我知道我可以再添加一个选民,或者在一个循环中做这个,但我很好奇,如果有一个好的方法来用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)); 

回答

15

您正在寻找

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

如果你坚持一个班轮,这将通过比较前值工作:

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

注意:记住'voicesSoFar'在循环结束时是不正确的,它是一个辅助变量。 – Kobi 2010-02-11 05:27:54

+0

+1对于不需要编写不必要的扩展方法的解决方案。 – 2010-02-11 05:33:40

+0

这很奇怪,但我不能得到第一个版本来展示任何东西......尽管如此,单线程仍然完美。 – 2010-02-11 05:36:41

6

只写你自己的扩展方法:

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

用法:

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

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

输出:

Alice 
Bob 
Catherine 
+1

也就是说,你的lambda表达式正在改变一个外部变量,这让我感到很难受。特别是,你不能枚举'eligibleVoters'两次,看到相同的结果,这只是讨厌的。 – jason 2010-02-11 05:15:47

+0

是的,我意识到后来甚至开始了这个新问题: http://stackoverflow.com/questions/2242371/does-this-code-really-cause-an-access-to-modified-closure-problem As现在,我试图围绕这个代码进行包装,我是新手:P – 2010-02-11 05:19:31

+1

@PRINCESS FLUFF:首先关注第二种方法;第一个以一种奇特的方式调用第二个。基本上我模仿了一个事实,即TakeWhile有两个重载,一个是索引基,另一个不是。 – jason 2010-02-11 05:21:37

19

在的情况下,我想执行一个函数,直到并包括它击中我做了一个结束条件:

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

为我工作!我认为这是一个像Jason一样的不依赖于实现的解决方案,但更简单。

+1

并且没有外部/捕获状态变量。 – Tormod 2015-04-09 13:26:09

0

Kobi的答案的变化,但演示使用(value, index)index在解决类似问题中非常有用,尽管不是OP的问题。

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

我正面临同样的问题。 我用联盟和跳过方法,所以要等到它

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

和跳跃,直到

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

另外也采取方法,它的第一个结果一些INT。