嗨我有一个List<decimal>
,其值包含在0; 1]之间。
我想检查这些值的总和(或小计)是否等于1(或几乎)。
我还可以使用Linq
函数来过滤或操作列表。
期望的结果:
显然,我会想要性能成本最低的东西。
答案 0 :(得分:3)
更新 - 现在不再重复总和 试试这个
bool isClose(IEnumerable<decimal> list, decimal epislon) {
return isClose(Enumerable.Empty<decimal>(),list,0,list.Sum(),epislon);
}
// Define other methods and classes here
bool isClose(IEnumerable<decimal> left,IEnumerable<decimal> right, decimal leftSum,decimal rightSum, decimal epsilon) {
if (leftSum>=1-epsilon && leftSum<=1+epsilon) return true;
if (leftSum>1+epsilon) return false;
if (leftSum+right.Sum()< 1-epsilon) return false;
if (!right.Any()) return false;
for (var i=0;i<right.Count();i++) {
var skip=right.Skip(i);
var newItem=skip.First();
if (isClose(left.Concat(skip.Take(1)),skip.Skip(1),leftSum+newItem,rightSum-newItem,epsilon)) return true;
}
return false;
}
isClose(new[] {0.7m,0.7m,0.7m},0.001m); // returns false
isClose(new[] {0.7m,0.3m,0.7m},0.001m); //returns true
isClose(new[] {0.777777m,0.2m,0.1m},0.001m); //returns false
isClose(new[] {0.33333m,0.33333m,0.33333m},0.001m); //returns true
编辑第五次测试
isClose(new[] {0.4m, 0.5m, 0.6m, 0.3m},0.001m); //returns true
答案 1 :(得分:1)
这是子集和问题,这是背包问题的一个特例。这很难(NP完全)。最着名的算法具有指数复杂性。然而,存在具有多项式复杂度的近似算法。这些回答的问题是“是否存在总和为1±ε的子集?”
答案 2 :(得分:1)
这是一种相当简单,狡猾,蛮力的方法。它效率不高,但希望它更容易理解。
private const decimal threshold = .001M;
public static bool CloseEnough(decimal first, decimal second, decimal threshold)
{
return Math.Abs(first - second) < threshold;
}
private static bool SubsetSum(List<decimal> data, int desiredSum)
{
int numIteratons = (int)Math.Pow(2, data.Count);
for (int i = 1; i < numIteratons; i++)
{
decimal sum = 0;
int mask = 1;
for (int j = 0; j < data.Count && sum < desiredSum + threshold; j++)
{
if ((i & mask) > 0)
{
sum += data[j];
}
mask <<= 1;
}
if (CloseEnough(sum, desiredSum, threshold))
{
return true;
}
}
return false;
}
答案 3 :(得分:0)
public static bool SubListAddsTo(this IEnumerable<decimal> source,
decimal target, decimal tolerance)
{
decimal lowtarget = target - tolerance;
decimal hightarget = target + tolerance;
HashSet<decimal> sums = new HashSet<decimal>();
sums.Add(0m);
List<decimal> newSums = new List<decimal>();
foreach(decimal item in source)
{
foreach(decimal oldSum in sums)
{
decimal sum = oldSum + item;
if (sum < lowtarget)
{
newSums.Add(sum);//keep trying
}
else if (sum < hightarget)
{
return true;
}
//else { do nothing, discard }
}
foreach (decimal newSum in newSums)
{
sums.Add(newSum);
}
newSums.Clear();
}
return false;
}
测试者:
List<decimal> list1 = new List<decimal>(){0.7m, 0.7m, 0.7m};
List<decimal> list2 = new List<decimal>(){0.7m, 0.3m, 0.7m};
List<decimal> list3= new List<decimal>(){0.777777m, 0.2m, 0.1m};
List<decimal> list4 = new List<decimal>() { 0.33333m, 0.33333m, 0.33333m };
List<decimal> list5 = new List<decimal>() { 0.4m, 0.5m, 0.6m, 0.3m };
Console.WriteLine(list1.SubListAddsTo(1m, 0.001m)); //false
Console.WriteLine(list2.SubListAddsTo(1m, 0.001m)); //true
Console.WriteLine(list3.SubListAddsTo(1m, 0.001m)); //false
Console.WriteLine(list4.SubListAddsTo(1m, 0.001m)); //true
Console.WriteLine(list5.SubListAddsTo(1m, 0.001m)); //true
答案 4 :(得分:0)
已编辑:我的原始代码不允许进行近似(0.9999 = 1)。
这使用变量数量的位图来屏蔽源数组中的值,以强制所有变体。
在百万计数循环中测试所有五个测试用例时,这比接受的答案快约7.5倍(约41秒vs约5.5秒)。它大约是David B's sln的两倍,比Servy的sln快约15%。
public static bool Test(decimal[] list, decimal epsilon)
{
var listLength = list.Length;
var variations = (int)(Math.Pow(2, listLength) - 1);
var bits = new bool[9];
for (var variation = variations; variation > 0; variation--)
{
decimal sum = 0;
bits[1] = (variation & 1) == 1;
bits[2] = (variation & 2) == 2;
bits[3] = (variation & 4) == 4;
bits[4] = (variation & 8) == 8;
bits[5] = (variation & 16) == 16;
bits[6] = (variation & 32) == 32;
bits[7] = (variation & 64) == 64;
bits[8] = (variation & 128) == 128;
for (var bit = listLength; bit > 0; bit--)
{
if (bits[bit])
{
sum += list[bit - 1];
}
}
if (Math.Abs(sum - 1) < epsilon)
{
return true;
}
}
return false;
}
编辑:此NewTest版本比上述版本快30%,速度比接受的解决方案快十倍。它删除了构建数组以提供位掩码以支持Servy的方法,这是大部分改进的来源。它还删除了Math.Abs调用,该调用提供了微小的改进。
private const decimal Epislon = 0.001m;
private const decimal Upper = 1 + Epislon;
private const decimal Lower = 1 - Epislon;
private static bool NewTest(decimal[] list)
{
var listLength = list.Length;
var variations = (int)(Math.Pow(2, listLength) - 1);
for (var variation = variations; variation > 0; variation--)
{
decimal sum = 0;
int mask = 1;
for (var bit = listLength; bit > 0; bit--)
{
if ((variation & mask) == mask)
{
sum += list[bit - 1];
}
mask <<= 1;
}
if (sum > Lower && sum < Upper)
{
return true;
}
}
return false;
}