LINQ按Sum值分组

时间:2012-10-04 02:12:04

标签: c# linq

说我有一个这样的课程:

public class Work
{
    public string Name;
    public double Time;

    public Work(string name, double time)
    {
        Name = name;
        Time = time;
    }
 }

我有一个List<Work>,其中包含约20个值:

List<Work> workToDo = new List<Work>();
// Populate workToDo

是否有任何可能的方法可以将workToDo分组到每个段的时间总和为特定值的段中?说workToDo的值如此:

Name | Time
A    | 3.50
B    | 2.75
C    | 4.25
D    | 2.50
E    | 5.25
F    | 3.75

如果我希望时间总和为7,则每个段或List<Work>应该有一堆值,其中所有Times的总和为7或接近它。这甚至可以远程实现,还是只是一个愚蠢的问题/想法?我使用此代码将workToDo分成4个部分:

var query = workToDo.Select(x => x.Time)
        .Select((x, i) => new { Index = i, Value = x})
        .GroupBy(y => y.Index / 4)
        .ToList();

但我不知道如何根据时代来做这件事。

3 个答案:

答案 0 :(得分:2)

这是一个查询,它将您的数据分组,其中时间接近7但未结束:

Func<List<Work>,int,int,double> sumOfRange = (list, start, end) => list
                  .Skip(start)
                  .TakeWhile ((x, index) => index <= end)
                  .ToList()
                  .Sum (l => l.Time);

double segmentSize = 7;
var result = Enumerable.Range(0, workToDo.Count ())
    .Select (index => workToDo
                         .Skip(index)
                         .TakeWhile ((x,i) => sumOfRange(workToDo, index, i) 
                                              <= segmentSize));

示例数据集的输出为:

A 3.5
B 2.75
total: 6.25

B 2.75
C 4.25
total: 7

C 4.25
D 2.5
total: 6.75

D 2.5
total: 2.5

E 5.25
total: 5.25

F 3.75
total: 3.75

如果您想允许一个段总数超过七个,那么您可以将segmentSize变量增加25%左右(即使其为8.75)。

答案 1 :(得分:1)

您所描述的是packing problem(任务被打包到7小时的容器中)。虽然可以在这个问题的解决方案中使用LINQ语法,但我知道LINQ中没有固有的解决方案。

答案 2 :(得分:1)

此解决方案会递归所有组合并返回其总和足够接近目标总和的组合。

这是一个漂亮的前端方法,可让您指定工作列表,目标总和以及总和必须接近的程度:

public List<List<Work>> GetCombinations(List<Work> workList,
                                        double targetSum,
                                        double threshhold)
{
    return GetCombinations(0,
                           new List<Work>(),
                           workList,
                           targetSum - threshhold,
                           targetSum + threshhold);
}

这是完成所有工作的递归方法:

private List<List<Work>> GetCombinations(double currentSum,
                                         List<Work> currentWorks,
                                         List<Work> remainingWorks,
                                         double minSum,
                                         double maxSum)
{
    // Filter out the works that would go over the maxSum.
    var newRemainingWorks = remainingWorks.Where(x => currentSum + x.Time <= maxSum)
                                          .ToList();
    // Create the possible combinations by adding each newRemainingWork to the 
    // list of current works.
    var sums = newRemainingWorks
                   .Select(x => new
                          {
                              Works = currentWorks.Concat(new [] { x }).ToList(),
                              Sum = currentSum + x.Time
                          })                                
                   .ToList();
    // The initial combinations are the possible combinations that are
    // within the sum range.                   
    var combinations = sums.Where(x => x.Sum >= minSum).Select(x => x.Works);
    // The additional combinations get determined in the recursive call.
    var newCombinations = from index in Enumerable.Range(0, sums.Count)
                          from combo in GetCombinations
                                        (
                                            sums[index].Sum,
                                            sums[index].Works,
                                            newRemainingWorks.Skip(index + 1).ToList(),
                                            minSum,
                                            maxSum
                                        )
                          select combo;
    return combinations.Concat(newCombinations).ToList();        
}

此行将获得总和为7 +/- 1的组合:

GetCombinations(workToDo, 7, 1);