我有一个包含两个列的数据表:数字和日期,对于示例:
125 | 2013年10月20日
100 | 2013年10月21日
150 | 2013年10月24日
225 | 2013年10月24日
250 | 2013年10月28日
310 |二○一三年十月三十○日
现在,我想搜索按日期排序的所有记录,其中数字的总和匹配500.我可以很容易地看到第一,第三和第四条记录(125 + 150 + 225 = 500)提供了匹配,但是对于这样的程序,我只能想到在数据表中经过数万次,直到找到正确的匹配。
有人有更明智的想法吗?
答案 0 :(得分:2)
在最糟糕的情况下,您必须浏览数据集的所有2^n
子集,但如果您的所有项目都是非负数,则可以通过item.Number <= 500
上的过滤开始。
这是一种可能的Subsets
方法(实际上是How to get all subsets of an array?的答案,但不要告诉他们):
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)都不会很好(订单2^n
),但如果Number
的大多数值与您的数量级相似目标总和,就像您的示例中的情况一样,然后停止条件将进入您的救援并为您节省大量不必要的操作。