如何按运行总计对对象列表进行分组?

时间:2015-03-22 22:25:49

标签: c# linq c#-4.0

假设我有一个看起来像这样的对象

public class TestItem
{
    public Int32 ID { get; set; }
    public Int32 Weight { get; set; }
}

我有一个这些对象的列表,我希望在权重总和的运行总数超过限制时分割列表,比如100。

我试过在Linq做这件事,我无法得到正确的分组。然后我尝试使用foreach迭代执行此操作,并获得超出限制的组。我看了一些Rollup linq Extensions,尝试了一些不同的策略去检查限制,我仍然得到高于限制的组。

即使允许单个TestItem已经超出限制,我的应用程序也没问题。

我没有关于如何根据运行总计的设定限制创建群组的想法。有什么想法吗?

编辑#1这就是我刚试过的。仍然没有骰子。

double sum = 0;
int cartonnum = 1;
var groupedCarton = ordered.GroupBy(x =>
{
    sum += x.TotalWeight * x.Qty;
    if (sum > maxWeight)
    {
         sum = 0;
         cartonnum++;
         return cartonnum;
    }else{

        return cartonnum;
    }
});

foreach (var g in groupedCarton)
{
    foreach (var item in g)
    {
        item.CartonNumber = g.Key;
    }
}

var uniqueLine = ordered.GroupBy(g => g.CartonNumber, (key, g) => new { Carton = key, Sum = g.Sum(k => k.TotalWeight) }).ToList();

Example Data

3 个答案:

答案 0 :(得分:2)

通过最新的编辑,您可以更接近解决方案并澄清了您的问题。尽管可以使用一些LINQ分组来解决这个问题,但它并没有真正阐明您尝试实现的规则,也没有明确说明您预期输出的“形状”。这样做会更容易推理。

我建议做类似下面的事情:

namespace TryOuts
{
    using System;
    using System.Linq;
    using System.Collections.Generic;

    public class TestItem
    {
        public Int32 ID { get; set; }
        public Int32 Weight { get; set; }
    }

    public class TestItemPartition
    {
        public static IEnumerable<TestItemPartition> PartitionItems(IEnumerable<TestItem> items, Int32 weightPartitionLimit)
        {
            var weightSum = 0;
            var itemList = new List<TestItem>();
            var partitionNumber = 1;

            foreach (var item in items)
            {
                if (weightSum + item.Weight > weightPartitionLimit && itemList.Count > 0)
                {
                    // limit reached and at least one item is in the partition
                    yield return new TestItemPartition(partitionNumber++, weightSum, itemList);
                    // re-initialize for next partition.
                    weightSum = 0;
                    itemList = new List<TestItem>();
                }
                // limit not reached, or a first single item exceeds the partition limit.
                // add item and increase weight sum.
                weightSum += item.Weight;
                itemList.Add(item);
            }
            // return partition for the last remaining items (if any).
            if (itemList.Count > 0)
                yield return new TestItemPartition(partitionNumber, weightSum, itemList);
        }

        public int PartitionNumber { get; private set; }
        public Int32 TotalWeight { get; private set; }
        public int TotalItems 
        {
            get { return _items.Count(); }
        }
        public IEnumerable<TestItem> Items
        {
            get { return _items; }
        }

        private TestItemPartition(int number, Int32 totalWeight, List<TestItem> items)
        {
            PartitionNumber = number;
            TotalWeight = totalWeight;
            _items = items;
        }
        private List<TestItem> _items;
    }

    class Program
    {
        public static void Main(params string[] args)
        {
            var items = new[] {
                new TestItem { ID = 1, Weight = 10  },
                new TestItem { ID = 2, Weight = 120 },
                new TestItem { ID = 3, Weight = 30  },
                new TestItem { ID = 4, Weight = 70  },
                new TestItem { ID = 5, Weight = 60  },
                new TestItem { ID = 6, Weight = 10  }
            };

            foreach (var partition in TestItemPartition.PartitionItems(items, 100))
                Console.WriteLine(
                    "#{0} - {1} items of total weight {2}.", 
                    partition.PartitionNumber, 
                    partition.TotalItems, 
                    partition.TotalWeight);

            Console.WriteLine("done");
            Console.ReadLine();
        }
    }
}

答案 1 :(得分:2)

我非常喜欢亚历克斯的答案。但是可以制作一个稍微高效的石斑鱼 - 我不是指代码,而是“更全面”的群体。因此,当下一个项目的权重会使其超过所需限制时,它会停止添加到分区。

例如,如果其中包含项目{0, 1, 2}的组,则项目#3可能会导致其超重,但项目14可能仍然适合。使用200个项目的测试数据集,下面的代码减少了组的数量s fair degree。如果这些是要装运的纸箱,那么单独处理和纸箱的差异可能是值得注意的。 1 如果你几乎在总重量超过100的情况下创建组,那可能无关紧要(较小的数据集具有较少的组合)。

以下内容迭代项目列表,添加下一个项目,直到剩余可用空间小于最重项目的空闲空间。然后它选择一个具有最接近重量的物品来尝试并填充当前的纸箱/组。 2 如果剩下的都是更轻的物品,它可能会失败,在这种情况下它会继续。

为了反复扫描列表,我添加了一个bool Grouped属性来标记哪些已经分组。 (在实际代码中几乎可以肯定有类似的东西)。我试图没有它,但它有助于确保一切都被分组但只有一次。此版本现在使用Groups类,但这只是一种便利。

FooItem只是TestItem +一个int GroupID属性。

public class FooGroup
{
    public List<FooItem> Items { get; private set; }
    public int ID { get; private set; }

    public FooGroup(int ndx)
    {
        Items = new List<FooItem>();
        ID = ndx;
    }

    public void Add(FooItem f)
    {
        Items.Add(f);
    }

    public void AddRange(FooItem[] f)
    {
        foreach (FooItem ff in f)
        {
            this.Add(ff);
        }
    }

    public int Count()
    {
        return Items.Count;
    }

    public int TotalWeight()
    {
        return Items.Sum(w => w.Weight);
    }

    public override string ToString()
    {
        return String.Format("ID: {0} Wt: {1}", 
            ID.ToString(), this.TotalWeight().ToString());
    }  
}

对于测试项目,而不是纯粹的随机值,我使用了随机数的设定值。我不知道这些代表现实世界有多好:

Random r = new Random();
int[] wts = {20,  25, 30, 35, 40, 45};    // Item weights
List<FooItem> fooList = new List<FooItem>();

for (int n = 1; n <= 200; n++)
{ 
    fooList.Add(new FooItem(n, 
         wts[ r.Next(6)]));
}

分组程序:

private IEnumerable<FooGroup> GroupFooItems(List<FooItem> foo, 
              Int32 weightLimit, int Grp = 0)
{
    // temp copy of the list - wasteful, but
    // allows us to remove packed items
    List<FooItem> ftmp = new List<FooItem>(foo);

    while ((ftmp.Count > 3) && (ftmp.Sum(w => w.Weight) > weightLimit))
    {
        FooGroup tmp = NewFooGroup(ftmp, weightLimit, ++Grp);
        // could also remove where GroupID != -1
        ftmp.RemoveAll(r => tmp.Items.Contains(r));
        yield return tmp;
     }

    // there can be stuff left over - dump it into a final group
    if (ftmp.Count > 0)
    { 
       FooGroup grp = new FooGroup(-1);
       grp.AddRange(ftmp.ToArray());
       yield return grp;
    }
}

迭代项目并将它们放在一个组中:

private FooGroup NewFooGroup(List<FooItem> foo, 
           Int32 weightLimit, int nextGrp)
{
    FooGroup grp = new FooGroup(nextGrp);
    int FitWt = (weightLimit - 45);       // 45 is max wt in test set

    // the find best fit below might have "looked ahead"
    // and used the current one, so filter those out
    foreach (FooItem f in foo.Where( g => g.GroupID == -1))
    {

        if ((grp.TotalWeight() + f.Weight) < weightLimit)
        {
            f.GroupID = nextGrp;
            grp.Add(f);
        }

        // full enough that one more item could fill
        if (grp.TotalWeight() >= FitWt)
        {
            int wtThreshold = (weightLimit - grp.TotalWeight());
            // find items that fit ordered by wt
            List<FooItem> xfoo = foo.Where(
                w => (w.Weight <= wtThreshold) & (w.GroupID == -1)
                            ).OrderByDescending(w => w.Weight).ToList();

            // pack up the best fit
            if (xfoo.Count > 0)
            {
                xfoo[0].GroupID = nextGrp;
                grp.Add(xfoo[0]);
            }
        }
    }
    return grp;
}

测试和比较代码(使用上面的200个项目):

// use TestItemPartition-er for comparison
var Partitions = TestItemPartition.PartitionItems(fooList, 100).ToList();          

// grouping results
var myGrps = GroupFooItems(fooList, 100).ToList();

我有很多代码可以迭代和收集垃圾因子,轻量级等等指标,检查所有内容是否已进入群组等等。为简洁起见,我们会对此进行编辑。

示例输出:

  

*结果*
  分区:75,组:65
  完美包装:分区:14,团体:55
  包装&lt; 75:分区:10,组:0
  废物因子:分区:15.1%,组:2.1
  详细信息:
  Grp#00:Ct部分:3 TWt 090 || Grp Ct:3 TWt:090
  Grp#01:Ct部分:2 TWt 080 || Grp Ct:3 TWt:100
  Grp#02:Ct部分:3 TWt 100 || Grp Ct:3 TWt:100
  Grp#03:Ct部分:3 TWt 090 || Grp Ct:3 TWt:100
  Grp#04:Ct部分:3 TWt 095 || Grp Ct:3 TWt:100
  Grp#05:Ct部分:3 TWt 100 || Grp Ct:3 TWt:100

这些指标最初是为了确定我的方法是否在Alex的方面有所改进,足以打扰发布。他们现在正做着相当不同的事情,比较不是很有效。

第一个指标是它们适合的纸箱/组数量,最重要的是。完美包装是那些完全等于100的包装(与轻型纸箱的数量一样,无意义的装运)。废物因子是每个纸箱/组中未使用的容量除以总物品重量的总和。

他们都做得不错,因为我们对权重和项目清单大小的性质知之甚少。较小的起始列表(例如20个项目)效率较低,因为制作完美包装的机会较少。

虽然修订确实尝试将每个组填充到100(或者weightLimit是什么),但它更适合于一种合适的东西而不是最合适的东西:不完美组中的项目可能非常很好地重新包装成更少的组。 使用测试数据,增益将是最小的,因为65 中只有10个可能能够更有效地打包。


1 数据的性质很可能会影响结果。如果使用的随机权重不代表实际项目权重范围,则实际结果可能完全不同。我们当然不知道。

2 那说这仍然不是任何一种最合适的算法:当第4项不合适时,它只检查如果另一项不适合 它们中的任何一种都适合。

答案 2 :(得分:0)

您需要在进行分组时跟踪sum

请参阅下文。

        List<TestItem> list = new List<TestItem>()
        {
            new TestItem() { ID = 1, Weight = 30 },
            new TestItem() { ID = 2, Weight = 40 },
            new TestItem() { ID = 3, Weight = 50 },
            new TestItem() { ID = 4, Weight = 60 },
            new TestItem() { ID = 5, Weight = 70 },
        };
        int sum = 0, limit = 100;

        var grouping = list.GroupBy(x => 
        {
            // check whether grouping new item will exceed the limit or not
            if (sum + x.Weight > limit) 
            { 
                return 1; 
            } 
            else 
            {
                // it is important to keep track of `sum` here
                sum += x.Weight; 
                return 0; 
            } 
        });

        foreach (var group in grouping)
        {
             if(group.Key == 0) {
                 // this group contains Weigth 30 & 40
             } else if(group.Key == 1) {
                 // this group cintains Weight 50 & 60 & 70
             }

        }