【问题标题】:How could I take 1 more item from Linq's TakeWhile?我怎样才能从 Linq 的 TakeWhile 中再拿 1 件物品?
【发布时间】:2017-11-03 00:02:10
【问题描述】:

(感兴趣的代码行是最后一行,其余的只是为了完整的表示)

使用下面的代码,我想要投票直到我超过所需的最大票数,但它在达到最大票数之前就停止了,所以我的选民池中的选民比我少 1通缉。

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

【问题讨论】:

    标签: c# .net linq


    【解决方案1】:

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

    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 的实现无关的解决方案,但更简单。

    【讨论】:

    • 并且没有外部/捕获的状态变量。
    • 这是一个非常不错的扩展,毫无疑问它会被添加到我的扩展库中。我只需将其名称更改为TakeWhileIncludingLast 并将if 子句修改为if (!predicate(el)) 以使其具有与现有扩展TakeWhile 类似的行为。
    • 当元素是列表中的第一个元素时,这对我不起作用 - 我得到了前两个元素。最后改用这个:stackoverflow.com/a/3098714/1579626
    【解决方案2】:

    你在寻找

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

    如果你坚持单线,这将通过比较以前的值来工作:

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

    【讨论】:

    • 注意:记住voicesSoFar在循环结束时是不正确的,它只是一个辅助变量。
    • @Benny Jobigan:这种扩展方法的好处是每次需要这种功能时都不必诉诸这种诡计。在其他地方封装所需的行为,并在需要时重用它。
    【解决方案3】:

    只需编写自己的扩展方法:

    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
    

    【讨论】:

    • 也就是说,您的 lambda 表达式正在改变一个外部变量,这让我感到恶心。特别是,您不能枚举eligibleVoters 两次并看到相同的结果,这很讨厌。
    • 是的,后来我意识到这一点,甚至开始了这个新问题:stackoverflow.com/questions/2242371/…至于现在,我正试图绕过这段代码,我是新手:P跨度>
    • @PRINCESS FLUFF:先关注第二种方法;第一个只是以一种奇特的方式调用第二个。基本上我模仿了TakeWhile 有两个重载,一个是索引基,另一个不是。
    【解决方案4】:

    Kobi 答案的变体,但演示了 (value, index) 的使用。 index 可用于解决类似问题,尽管不是 OP。

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

    【讨论】:

      【解决方案5】:

      我也遇到了同样的问题。 我已经使用了 Union 和 Skip 方法,所以一直使用到它为止

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

      跳过直到

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

      还有 Take 方法,它获取一些初始结果。

      【讨论】:

      • 您的 TakeWhile() 会将“stop”添加到不包含“stop”的列表的末尾。这可能是不可取的。
      猜你喜欢
      • 2019-09-01
      • 1970-01-01
      • 2023-01-28
      • 1970-01-01
      • 1970-01-01
      • 2021-05-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多