如何使用linq基于累加器对记录进行分组?

时间:2015-08-19 20:37:05

标签: linq

给出以下格式列举的记录:

Name (string)
Amount (number)

例如:

Laverne   4
Lenny     2
Shirley   3
Squiggy   5

我想对记录进行分组,以便每个组的总金额不超过某个组的限制。例如,10。

Group 1 (Laverne,Lenny,Shirley) with Total Amount 9
Group 2 (Squiggy) with Total Amount 5

保证金额始终小于分组限制。

4 个答案:

答案 0 :(得分:2)

这里我有一个仅使用LINQ函数的解决方案:

// Record definition
class Record
{
    public string Name;
    public int Amount;
    public Record(string name, int amount)
    {
        Name = name;
        Amount = amount;
    }
}

// actual code for setup and LINQ
List<Record> records = new List<Record>()
{
    new Record("Laverne", 4),
    new Record("Lenny", 2),
    new Record("Shirley", 3),
    new Record("Squiggy", 5)
};
int groupLimit = 10;

// the solution
List<Record[]> test = 
    records.GroupBy(record => records.TakeWhile(r => r != record)
                                     .Concat(new[] { record })
                                     .Sum(r => r.Amount) / (groupLimit + 1))
           .Select(g => g.ToArray()).ToList();

这给出了正确的结果:

test = 
{
    { [ "Laverne", 4 ], [ "Lenny", 2 ], [ "shirley", 3 ] },
    { [ "Squiggly", 5 ] }
}

唯一的缺点是这是O(n 2 )。它基本上按组的索引分组(通过使用记录的总和到当前的来定义)。请注意,我们需要groupLimit + 1才能实际包含0groupLimit之间的群组。

我正试图找到一种让它更漂亮的方法,但it doesn't look easy

答案 1 :(得分:2)

如果允许捕获的变量维持状态,则变得更容易。如果我们有:

int limit = 10;

然后:

int groupTotal = 0;
int groupNum = 0;
var grouped = records.Select(r =>
{
    int newCount = groupTotal + r.Amount;
    if (newCount > limit)
    {
        groupNum++;
        groupTotal = r.Amount;
    }
    else
        groupTotal = newCount;
    return new{Records = r, Group = groupNum};
}
).GroupBy(g => g.Group, g => g.Records);

它是O(n),只有SelectGroupBy,但是捕获的变量的使用可能不像人们想要的那样在提供者之间可移植。

对于linq-to-objects,它很好。

答案 2 :(得分:0)

一个使用Aggregate的解决方案:

https://dotnetfiddle.net/gVgONH

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    // Record definition
    public class Record
    {
        public string Name;
        public int Amount;
        public Record(string name, int amount)
        {
            Name = name;
            Amount = amount;
        }
    }

    public static void Main()
    {
        // actual code for setup and LINQ
        List<Record> records = new List<Record>()
        {
        new Record("Alice", 1), new Record("Bob", 5), new Record("Charly", 4), new Record("Laverne", 4), new Record("Lenny", 2), new Record("Shirley", 3), new Record("Squiggy", 5)}

        ;
        int groupLimit = 10;
        int sum = 0;
        var result = records.Aggregate(new List<List<Record>>(), (accumulated, next) =>
        {
            if ((sum + next.Amount >= groupLimit) || accumulated.Count() == 0)
            {
                Console.WriteLine("New team: " + accumulated.Count());
                accumulated.Add(new List<Record>());
                sum = 0;
            }

            sum += next.Amount;
            Console.WriteLine("New member {0} ({1}): adds up to {2} ", next.Name, next.Amount, sum);
            accumulated.Last().Add(next);
            return accumulated;
        }

        );
        Console.WriteLine("Team count: " + result.Count());
    }
}

输出:

New team: 0
New member Alice (1): adds up to 1 
New member Bob (5): adds up to 6 
New team: 1
New member Charly (4): adds up to 4 
New member Laverne (4): adds up to 8 
New team: 2
New member Lenny (2): adds up to 2 
New member Shirley (3): adds up to 5 
New team: 3
New member Squiggy (5): adds up to 5 
Team count: 4

答案 3 :(得分:-1)

使用我所知道的内置Linq运算符,没有'高性能'的方法。但是,您可以创建自己的扩展方法:

public static class EnumerableExtensions
{
    public static IEnumerable<TResult> GroupWhile<TSource, TAccumulation, TResult>(
        this IEnumerable<TSource> source,
        Func<TAccumulation> seedFactory,
        Func<TAccumulation, TSource, TAccumulation> accumulator,
        Func<TAccumulation, bool> predicate,
        Func<TAccumulation, IEnumerable<TSource>, TResult> selector)
    {
        TAccumulation accumulation = seedFactory();
        List<TSource> result = new List<TSource>();
        using(IEnumerator<TSource> enumerator = source.GetEnumerator())
        {
            while(enumerator.MoveNext())
            {
                if(!predicate(accumulator(accumulation, enumerator.Current)))
                {
                    yield return selector(accumulation, result);
                    accumulation = seedFactory();
                    result = new List<TSource>();
                }
                result.Add(enumerator.Current);
                accumulation = accumulator(accumulation, enumerator.Current); 
            }

            if(result.Count > 0)
            {
                yield return selector(accumulation, result);
            }
        }
    }
}

然后像这样称呼它:

int limit = 10;
var groups =
    records
    .GroupWhile(
        () => 0,
        (a, x) => a + x,
        (a) => a <= limit,
        (a, g) => new { Total = a, Group = g });

当前写入的方式,如果任何单个记录超过该限制,则该记录将自行返回。您可以修改它以排除超出限制的记录或保留原样,并使用Where执行排除。

此解决方案具有O(n)运行时。