在最坏的情况下,您必须遍历数据集的所有2^n
子集,但如果您的所有项目都是非负数,您可以从过滤开始item.Number <= 500
。
这是一种可能的Subsets
方法(实际上是如何获取数组的所有子集的答案?,但不要告诉他们):
public static IEnumerable<IEnumerable<T>> Subsets(this IEnumerable<T> source)
{
var first = source.FirstOrDefault();
if (first == null) return new[] { Enumerable.Empty<T>() };
var others = source.Skip(1).Subsets();
return others.Concat(others.Select(s => s.Concat(new { first })));
}
一旦你有了你的Subsets
方法,你就可以按如下方式过滤结果,尽管性能仍然是一个巨大的数量级(或者2^n
如果你想挑剔的话)。
var sets = items.Where(i => i.Number <= 500)
.Subsets().Where(s => s.Sum(i => i.Number) == 500);
但是,如果您确实对 有约束Number
,即它是非负的,则可以将该Subsets
操作与搜索目标总和相结合。这意味着你会定义
public static IEnumerable<IEnumerable<T>> SubsetsAddingUpTo(this IEnumerable<T> source, int target)
{
// This stopping condition ensures that you will not have to walk the rest of the tree when you have already hit or exceeded your target.
// It assumes that the Number values are all non-negative.
if (target < 0) return Enumerable.Empty<IEnumerable<T>>();
var first = source.FirstOrDefault();
if (first == null) return Enumerable.Empty<IEnumerable<T>>();
var tail = source.Skip(1).Where(i => i.Number <= target).ToList();
var othersIncludingFirst = tail.SubsetsAddingUpTo(target - first.Number);
var othersExcludingFirst = tail.SubsetsAddingUpTo(target);
return othersExcludingFirst.Concat(othersIncludingFirst.Select(s => s.Concat(new { first })));
}
因为检查<= target
发生在方法内部,所以您不必进行任何预过滤。但是,您可以在进行搜索之前执行排序,以按分层日期顺序为您提供集合。电话将是
var sets = items.OrderByDescending(i => i.Date).SubsetsAddingUpTo(500);
这实际上应该给你不错的表现。最坏的情况(每个项目都有一个数字 0 或 1)不会很好( order 2^n
),但是如果 的大多数值Number
与您的目标总和具有相似的数量级,就像您的示例中的情况一样,那么停止条件就会飞入你的救援并为你节省大量不必要的操作。