假设我有一个看起来像这样的对象
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();
答案 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
}
}