好的,从我从这个答案中得到的一个小扩展开始,
public static IEnumerable<IEnumerable<T>> Combinations<T>(
this IEnumerable<T> source,
int k)
{
if (k == 0)
{
return new[] { Enumerable.Empty<T>() };
}
return source.SelectMany((e, i) =>
source.Skip(i + 1).Combinations(k - 1)
.Select(c => (new[] { e }).Concat(c)));
}
这为您提供了一种非常有效的方法k
,可以从给定的IEnumerable
. 您可以在您的实现中充分利用这一点。
请记住,如果IEnumerable
和k
足够大,这可能需要一些时间,即比您拥有的时间长得多。所以,我已经修改了你的函数以采用CancellationToken
.
private static IEnumerable<decimal> GetWinningValues(
IEnumerable<decimal> allValues,
int numberToGet,
decimal targetValue,
CancellationToken canceller)
{
IList<decimal> currentBest = null;
var currentBestGap = decimal.MaxValue;
var locker = new object();
allValues.Combinations(numberToGet)
.AsParallel()
.WithCancellation(canceller)
.TakeWhile(c => currentBestGap != decimal.Zero)
.ForAll(c =>
{
var gap = Math.Abs(c.Sum() - targetValue);
if (gap < currentBestGap)
{
lock (locker)
{
currentBestGap = gap;
currentBest = c.ToList();
}
}
}
return currentBest;
}
我有一个想法,当总和必须超过目标时,您可以对初始列表进行排序并在某个点停止迭代组合。经过一番考虑,确定这一点并非易事,而且检查的成本可能会超过收益。这个好处必须与目标值和集合的平均值的某些函数相平衡。
我仍然认为进一步优化是可能的,但我也认为这项工作已经完成,我只需要在正确的地方查找它。