LINQ集团的财产总和

时间:2014-07-08 11:28:11

标签: c# linq group-by sum

我试图创建一个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 }
   }
}

所以这些项目被分组为&#34;行&#34;其中内部List中的Sum(width)小于或等于数字(maxWidth - 在我的实例中,4)。 它有点像GroupBy的衍生物,但是GroupBy依赖于数组中较早的值 - 这是我难以接受的地方。

任何想法都会受到赞赏。

3 个答案:

答案 0 :(得分:2)

我们可以将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);

答案 1 :(得分:1)

您可以使用MoreLinq库中的Batch方法来执行此操作,该方法可用作NuGet包。结果是List<IEnumerable<object>>。这是代码:

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

<强>输出

enter image description here

答案 2 :(得分:0)

我认为你不能用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所做的,但不仅计算每个批次中的元素,而且总结所需的属性。可以使用自定义选择器来概括我的代码,以便在批量大小&#34;方面更加灵活。