【问题标题】:LINQ group by sum of propertyLINQ 按属性总和分组
【发布时间】:2014-07-08 11:28:11
【问题描述】:

我正在尝试创建一个 LINQ 查询,它是 SelectMany 的衍生产品。 我有 N 项:

new {
   { Text = "Hello", Width = 2 },
   { Text = "Something else", Width = 1 },
   { Text = "Another", Width = 1 },
   { Text = "Extra-wide", Width = 3 },
   { Text = "Random", Width = 1 }
}

我希望结果是 List<List<object>>(),其中:

List<List<object>> = new {
   // first "row"
   {
      { Text = "Hello", Width = 2 },
      { Text = "Something else", Width = 1 },
      { Text = "Another", Width = 1 }
   },
   // second "row"
   {
      { Text = "Extra-wide", Width = 3 },
      { Text = "Random", Width = 1 }
   }
}

因此,这些项目被分组为“行”,其中内部列表中的 Sum(width) 小于或等于一个数字(maxWidth - 在我的实例中为 4)。 它有点像 GroupBy 的派生词,但 GroupBy 依赖于数组中较早的值 - 这就是我被难住的地方。

任何想法都将不胜感激。

【问题讨论】:

    标签: c# linq group-by sum


    【解决方案1】:

    我们可以将 LINQ 的 Aggregate 方法的思想与 GroupWhile 方法结合起来,在满足条件时对连续的项目进行分组,从而为当前组构建一个聚合值,以用于谓词:

    public static IEnumerable<IEnumerable<T>> GroupWhileAggregating<T, TAccume>(
        this IEnumerable<T> source,
        TAccume seed,
        Func<TAccume, T, TAccume> accumulator,
        Func<TAccume, T, bool> predicate)
    {
        using (var iterator = source.GetEnumerator())
        {
            if (!iterator.MoveNext())
                yield break;
    
            List<T> list = new List<T>() { iterator.Current };
            TAccume accume = accumulator(seed, iterator.Current);
            while (iterator.MoveNext())
            {
                accume = accumulator(accume, iterator.Current);
                if (predicate(accume, iterator.Current))
                {
                    list.Add(iterator.Current);
                }
                else
                {
                    yield return list;
                    list = new List<T>() { iterator.Current };
                    accume = accumulator(seed, iterator.Current);
                }
            }
            yield return list;
        }
    }
    

    使用这种分组方法,我们现在可以编写:

    var query = data.GroupWhileAggregating(0,
        (sum, item) => sum + item.Width,
        (sum, item) => sum <= 4);
    

    【讨论】:

    • 很好,但它或多或少是(我必须同意更加优雅和灵活!)下面我的答案的实现。
    • @derpirscher 遗憾的是优雅赢得了这一轮 - 他的解决方案是正确的。
    • 应该是 (sum, item) => sum + item.Width
    • @ChrisDaMour 不,因为在这种情况下,累加器在谓词之前运行,所以谓词在“下一个”项目已经改变累加器的情况下运行。如果您将分组方法更改为在累积下一项之前调用谓词,那么您需要这样做。
    • hm 在我的测试中我必须这样做,但也许我的情况不同......我不想超过限制
    【解决方案2】:

    您可以使用 MoreLinq 库中的 Batch 方法来完成此操作,该库可作为 NuGet 包使用。结果是List&lt;IEnumerable&lt;object&gt;&gt;。代码如下:

    class Obj
    {
        public string Text {get;set;}
        public int Width {get;set;}
    }
    
    void Main()
    {
    
        var data = new [] {
            new Obj { Text = "Hello", Width = 2 },
            new Obj { Text = "Something else", Width = 1 },
            new Obj { Text = "Another", Width = 1 },
            new Obj { Text = "Extra-wide", Width = 3 },
            new Obj { Text = "Random", Width = 1 }
        };
    
        var maxWidth = data.Max (d => d.Width );
        var result = data.Batch(maxWidth).ToList();
        result.Dump(); // Dump is a linqpad method
    

    输出

    【讨论】:

    • this answer 也找到了一个工作批处理函数。
    • 这是一个相当聪明的实现
    • Batch(maxSize) 将创建批次,其中每个批次最多具有 maxSize 元素。它不考虑元素的任何属性。 IE。如果将“Hello”的 Width 更改为 4,则第一批也将包含“Something else”和“another”甚至“Extra-wide”,尽管从最初的问题来看,它应该只包含“Hello”。
    【解决方案3】:

    我认为你不能用 LINQ 做到这一点。一种替代方法如下:

    var data = ... // original data
    var newdata = new List<List<object>>();
    int csum = 0;
    var crow = new List<object>();
    foreach (var o in data) {
        if (csum + o.Width > 4) {  //check if the current element fits into current row
            newdata.Add(crow);  //if not add current row to list
            csum = 0;
            crow = new List<object>();  //and create new row
        }
        crow.Add(o); //add current object to current row
        csum += o.Width;
    }
    
    if (crow.Count() > 0)  //last row
        newData.Add(c);
    

    编辑:另一个答案建议使用 MoreLinq 库中的批处理。其实上面的源码,和 Batch 做的事情差不多,只不过不是只计算每个batch中的元素,而是总结出想要的属性。可以使用自定义选择器概括我的代码,以便在“批量大小”方面更加灵活。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-01-26
      • 1970-01-01
      • 2017-11-17
      • 1970-01-01
      • 2017-07-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多