我想找到最接近的交易金额(应该是> =交易金额)或等于给定数字的单笔交易金额,但它应该是最小金额。会有很多数据组合,这些数据是> =给定的数字但是我想要最小的交易数量。
假设我给出金额为100,并且交易金额数字如下
场景1:85,35,25,45,16,100
情景2:55,75,26,55,99
情景3:99,15,66,75,85,88,5
上述情景的预期输出如下
场景1:100
情景2:75,26(即75 + 26 = 101)
情景3:85,15(即85 + 15 = 100)
我当前的代码输出如下
情景1:85,25
情景2:55,26,55
情景3:99,5
这是我的代码
class Program
{
static void Main(string[] args)
{
string input;
decimal transactionAmount;
decimal element;
do
{
Console.WriteLine("Please enter the transaction amount:");
input = Console.ReadLine();
}
while (!decimal.TryParse(input, out transactionAmount));
Console.WriteLine("Please enter the claim amount (separated by spaces)");
input = Console.ReadLine();
string[] elementsText = input.Split(' ');
List<decimal> claimAmountList = new List<decimal>();
foreach (string elementText in elementsText)
{
if (decimal.TryParse(elementText, out element))
{
claimAmountList.Add(element);
}
}
Solver solver = new Solver();
List<List<decimal>> results = solver.Solve(transactionAmount, claimAmountList.ToArray());
foreach (List<decimal> result in results)
{
foreach (decimal value in result)
{
Console.Write("{0}\t", value);
}
Console.WriteLine();
}
Console.ReadLine();
}
public class Solver
{
private List<List<decimal>> mResults;
private decimal minimumTransactionAmount = 0;
public List<List<decimal>> Solve(decimal transactionAmount, decimal[] elements)
{
mResults = new List<List<decimal>>();
RecursiveSolve(transactionAmount, 0.0m,
new List<decimal>(), new List<decimal>(elements), 0);
return mResults;
}
private void RecursiveSolve(decimal transactionAmount, decimal currentSum,
List<decimal> included, List<decimal> notIncluded, int startIndex)
{
decimal a = 0;
for (int index = startIndex; index < notIncluded.Count; index++)
{
decimal nextValue = notIncluded[index];
if (currentSum + nextValue >= transactionAmount)
{
if (a >= currentSum + nextValue)
{
if (minimumTransactionAmount < currentSum + nextValue)
{
minimumTransactionAmount = currentSum + nextValue;
List<decimal> newResult = new List<decimal>(included);
newResult.Add(nextValue);
mResults.Add(newResult);
}
a = currentSum + nextValue;
}
if (a == 0)
{
a = currentSum + nextValue;
}
}
else if (currentSum + nextValue < transactionAmount)
{
List<decimal> nextIncluded = new List<decimal>(included);
nextIncluded.Add(nextValue);
List<decimal> nextNotIncluded = new List<decimal>(notIncluded);
nextNotIncluded.Remove(nextValue);
RecursiveSolve(transactionAmount, currentSum + nextValue,
nextIncluded, nextNotIncluded, startIndex++);
}
}
}
}
}
答案 0 :(得分:3)
好的,在这里我会尝试为答案提出一些建议。基本上,我认为最好制作一些中间类和方法来帮助您解决问题。这个想法如下:
您可以创建一个包含两个元素TotalValue
和Combinations
的自定义类,以存储方案中每个数字组合的总值和组合。像这样的东西
public class CombinationAndValue {
public int TotalValue;
public List<int> Combinations;
}
接下来,您可以制作一个自定义方法,该方法会将值列表(即您的数字集)作为输入并生成所有可能的CombinationAndValue
类&#39;实例
public List<CombinationAndValue> comVals(List<int> vals) {
List<CombinationAndValue> coms = new List<CombinationAndValue>();
//... logic to generate all possible combinations
return coms;
}
要从一组项目中创建所有可能的组合,请考虑此link或其他资源的答案。
一旦你有这两个项目,你可以做简单的LINQ
来获得解决方案:
List<int> vals = new List<int>() { 55, 75, 26, 55, 99 };
int target = 100;
CombinationAndValue sol = comVals(target, vals)
.Where(x => x.TotalValue >= 100) //take everything that has TotalValue >= 100
.OrderBy(x => x.TotalValue) //order by TotalValue from the smallest
.ThenBy(x => x.Combinations.Count) //then by the number of combined elements
.FirstOrDefault(); //get first or default
答案 1 :(得分:1)
你可能不得不蛮力这个。这是一种方法:
编写提供所有可能组合的方法
这是使用uint
中设置的位的标准方法。请注意,此实现仅支持最多包含31个元素的数组。此外,为简洁起见,省略了错误处理。
public static IEnumerable<IEnumerable<T>> Combinations<T>(T[] array)
{
uint max = 1u << array.Length;
for (uint i = 1; i < max; ++i)
yield return select(array, i, max);
}
static IEnumerable<T> select<T>(T[] array, uint bits, uint max)
{
for (int i = 0, bit = 1; bit < max; bit <<= 1, ++i)
if ((bits & bit) != 0)
yield return array[i];
}
编写方法以获取序列的“最大”元素
为此,我们可以使用Jon Skeet等人的“MaxBy”,为了方便我在这里重现(但它是available via NuGet)。
public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
{
return source.MinBy(selector, Comparer<TKey>.Default);
}
public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IComparer<TKey> comparer)
{
using (IEnumerator<TSource> sourceIterator = source.GetEnumerator())
{
if (!sourceIterator.MoveNext())
throw new InvalidOperationException("Sequence was empty");
TSource min = sourceIterator.Current;
TKey minKey = selector(min);
while (sourceIterator.MoveNext())
{
TSource candidate = sourceIterator.Current;
TKey candidateProjected = selector(candidate);
if (comparer.Compare(candidateProjected, minKey) < 0)
{
min = candidate;
minKey = candidateProjected;
}
}
return min;
}
}
编写算法
将样板代码排除在外,我们现在可以编写算法来确定最接近的匹配:
public static IEnumerable<int> FindClosest(int[] array, int target)
{
var result = Combinations(array).MinBy(c => {
int s = c.Sum();
return s >= target ? s : int.MaxValue; });
return result.Sum() >= target ? result : Enumerable.Empty<int>();
}
(请注意,此算法会多次枚举序列,这对于数组来说很好,但对于一般IEnumerable<T>
则不好。)
Compilable Demo
将它完全放入可编辑的演示控制台应用程序中:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Demo
{
static class Program
{
static void Main()
{
int target = 100;
test(85, 35, 25, 45, 16, 100); // Prints 100: 100
test(55, 75, 26, 55, 99); // Prints 101: 75, 26
test(99, 15, 66, 75, 85, 88, 5); // Prints 100: 15, 85
test(1, 1, 1, 1, 1); // Prints 0:
}
static void test(params int[] a)
{
var result = FindClosest(a, 100);
Console.WriteLine(result.Sum() + ": " + string.Join(", ", result));
}
public static IEnumerable<int> FindClosest(int[] array, int target)
{
var result = Combinations(array).MinBy(c => {
int s = c.Sum();
return s >= target ? s : int.MaxValue; });
return result.Sum() >= target ? result : Enumerable.Empty<int>();
}
public static IEnumerable<IEnumerable<T>> Combinations<T>(T[] array)
{
uint max = 1u << array.Length;
for (uint i = 1; i < max; ++i)
yield return select(array, i, max);
}
static IEnumerable<T> select<T>(T[] array, uint bits, uint max)
{
for (int i = 0, bit = 1; bit < max; bit <<= 1, ++i)
if ((bits & bit) != 0)
yield return array[i];
}
public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
{
return source.MinBy(selector, Comparer<TKey>.Default);
}
public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IComparer<TKey> comparer)
{
using (IEnumerator<TSource> sourceIterator = source.GetEnumerator())
{
if (!sourceIterator.MoveNext())
throw new InvalidOperationException("Sequence was empty");
TSource min = sourceIterator.Current;
TKey minKey = selector(min);
while (sourceIterator.MoveNext())
{
TSource candidate = sourceIterator.Current;
TKey candidateProjected = selector(candidate);
if (comparer.Compare(candidateProjected, minKey) < 0)
{
min = candidate;
minKey = candidateProjected;
}
}
return min;
}
}
}
}
使用MinByOrDefault
由于MinBy
不适用于空序列,因此上述解决方案变得复杂。我们可以略微更改它,并将其重命名为MinByOrDefault
。
通过此更改,特殊情况代码将从FindClosest()
消失,如果未找到匹配项,则会返回null
:
public static IEnumerable<int> FindClosest(int[] array, int target)
{
return Combinations(array)
.Where(c => c.Sum() >= target)
.MinByOrDefault(c => c.Sum());
}
我认为这看起来相当优雅 - 但可能会编写更快,更高效(但更复杂)的实现。特别要注意的是,每种组合的总和计算两次。这可以避免。
这是更新的可编译演示程序。我更喜欢这个版本:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Demo
{
static class Program
{
static void Main()
{
int target = 100;
test(85, 35, 25, 45, 16, 100); // Prints 100: 100
test(55, 75, 26, 55, 99); // Prints 101: 75, 26
test(99, 15, 66, 75, 85, 88, 5); // Prints 100: 15, 85
test(1, 1, 1, 1, 1); // Prints 0:
}
static void test(params int[] a)
{
var result = FindClosest(a, 100);
if (result != null)
Console.WriteLine(result.Sum() + ": " + string.Join(", ", result));
else
Console.WriteLine("No result found for: " + string.Join(", ", a));
}
public static IEnumerable<int> FindClosest(int[] array, int target)
{
return Combinations(array)
.Where(c => c.Sum() >= target)
.MinByOrDefault(c => c.Sum());
}
public static IEnumerable<IEnumerable<T>> Combinations<T>(T[] array)
{
uint max = 1u << array.Length;
for (uint i = 1; i < max; ++i)
yield return select(array, i, max);
}
static IEnumerable<T> select<T>(T[] array, uint bits, uint max)
{
for (int i = 0, bit = 1; bit < max; bit <<= 1, ++i)
if ((bits & bit) != 0)
yield return array[i];
}
public static TSource MinByOrDefault<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
{
return source.MinByOrDefault(selector, Comparer<TKey>.Default);
}
public static TSource MinByOrDefault<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IComparer<TKey> comparer)
{
using (IEnumerator<TSource> sourceIterator = source.GetEnumerator())
{
if (!sourceIterator.MoveNext())
return default(TSource);
TSource min = sourceIterator.Current;
TKey minKey = selector(min);
while (sourceIterator.MoveNext())
{
TSource candidate = sourceIterator.Current;
TKey candidateProjected = selector(candidate);
if (comparer.Compare(candidateProjected, minKey) < 0)
{
min = candidate;
minKey = candidateProjected;
}
}
return min;
}
}
}
}
<强>附录强>
这是FindClosest()
的版本,它不会计算两次总和。它不是那么优雅,但可能会更快:
public static IEnumerable<int> FindClosest(int[] array, int target)
{
return Combinations(array)
.Select(c => new {S = c.Sum(), C = c})
.Where(c => c.S >= target)
.MinByOrDefault(x => x.S)
?.C;
}
超过31项的组合
这个版本的Combinations()
最多可以使用63个项目:
public static IEnumerable<IEnumerable<T>> Combinations<T>(T[] array)
{
ulong max = 1ul << array.Length;
for (ulong i = 1; i != max; ++i)
yield return selectComb(array, i);
}
static IEnumerable<T> selectComb<T>(T[] array, ulong bits)
{
ulong bit = 1;
for (int i = 0; i < array.Length; bit <<= 1, ++i)
if ((bits & bit) != 0)
yield return array[i];
}
我认为您不太可能想要生成超过63项的所有组合。
毕竟,63项有2 ^ 63种组合,或9,223,372,036,854,775,808种组合。
即使你可以每秒处理十亿次,也需要250多年才能完成......
答案 2 :(得分:1)
正如评论中所指出的,这是the knapsack problem的变体。但是,如问题Variation on knapsack - minimum total value exceeding 'W'中所述,您的问题在某种意义上是背包问题的相反问题,因为您希望最小化受限制的项目的权重,即总权重应超过最小值。在背包问题中,您希望最大化重量,但要限制总重量不能超过最大值。
幸运的是,accepted answer演示了一个&#34;逆转背包问题&#34;可以通过将物品(交易价值)放入背包来解决。无法放入背包的物品(交易价值)是解决问题的最佳方案。
Google's Operations Research tools为算法提供.NET绑定以解决背包问题。从算法输入开始:
var amounts = new[] { 55, 75, 26, 55, 99 };
var targetAmount = 100;
创建一个背包解算器:
const String name = "https://stackoverflow.com/questions/36195053/";
var solver = new KnapsackSolver(
KnapsackSolver.KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER,
name
);
此处选择算法类型,并且随着输入的大小增加,某些算法可能具有更好的性能,因此您可能需要在此处进行调整,特别是在强力算法开始执行不当时。 (解决背包问题是NP hard。)
将输入转换为背包解算器使用的值:
var profits = amounts.Select(amount => (Int64) amount).ToArray();
var weights = new Int64[1, profits.Length];
for (var i = 0; i < profits.Length; i += 1)
weights[0, i] = profits[i];
var capacities = new Int64[] { amounts.Sum() - targetAmount };
注意如amit所述,将容量设置为所有权重之和减去目标值。
执行求解器:
solver.Init(profits, weights, capacities);
solver.Solve();
var solution = profits
.Where((profit, index) => !solver.BestSolutionContains(index))
.Select(profit => (Int32) profit)
.ToArray();
不的数量使其成为背包的是解决方案值。在这种情况下,75, 26
符合预期。
答案 3 :(得分:0)
假设优化不是问题,像这样的问题总是最容易用蛮力。在这种情况下,只需尝试数字对的每个组合,找到一个给你最低结果的组合。
Heres与我想出了:
public List<List<decimal>> Solve(decimal transactionAmount, decimal[] elements)
{
int combinations = Convert.ToInt32(Math.Pow(2.0, elements.Length));
List<List<decimal>> results = new List<List<decimal>>();
List<decimal> result = new List<decimal>();
decimal bestResult = elements.Sum();
for (int j = 0; j < combinations; j++)
{
string bitArray = Convert.ToString(j, 2).PadLeft(elements.Length, '0');
decimal sum = 0;
for (int i = 0; i < elements.Length; i++)
{
sum += bitArray[i].Equals('1') ? elements[i] : 0;
if (sum > bestResult)
break;
}
if (sum > bestResult || sum < transactionAmount)
continue;
result.Clear();
result.AddRange(elements.Where((t, i) => bitArray[i].Equals('1')));
bestResult = result.Sum();
//Perfect result
if (sum == transactionAmount)
results.Add(new List<decimal>(result));
}
// Get last solution
if (results.All(x => result.Except(x).ToList().Count != 0))
results.Add(new List<decimal>(result));
return results;
}
它只是将数字的总和跟踪为二进制数,告诉它要么添加要么不添加。如果找到比当前解决方案更好的解决方案,则会更新它。否则只需循环以尝试下一个组合。
我在大学时实际上遇到了类似的问题,所以我知道对这种特殊问题有一些优化。我把我记得的唯一一个,如果它已经比你的最佳结果更差,就不需要继续计算总和。
编辑:刚修改它以返回多个解决方案,因为我发现你有一个List
List
。如果您想知道new List
,它会使List获得一个副本而不是指向结果的指针(正在改变)。
EDIT2:我意识到你可以得到重复的解决方案(比如你有50,50,50,50)。如果你想避免这些,你可以这样做:
public List<List<decimal>> Solve(decimal transactionAmount, decimal[] elements)
{
// ....
for (int j = 0; j < combinations; j++)
{
// ....
//Perfect result
if (sum == transactionAmount)
results.Add(new List<decimal>(result.OrderBy(t => t)));
}
results.Add(new List<decimal>(result.OrderBy(t => t)));
return results.Distinct(new ListDecimalEquality()).ToList();
}
public class ListDecimalEquality : IEqualityComparer<List<decimal>>
{
public bool Equals(List<decimal> x, List<decimal> y)
{
return x.SequenceEqual(y);
}
public int GetHashCode(List<decimal> obj)
{
int hashCode = 0;
for (int index = 0; index < obj.Count; index++)
{
hashCode ^= new { Index = index, Item = obj[index] }.GetHashCode();
}
return hashCode;
}
}
答案 4 :(得分:0)
您可以尝试这种简单的方法:
int[] A = {99, 15, 66, 75, 80, 5, 88, 5};
List<Tuple<string, int>> list = new List<Tuple<string, int>>();
list.Add(new Tuple<string, int>(A[0].ToString(),A[0]));
for(int i = 1; i < A.Length; i++)
{
var newlist = new List<Tuple<string, int>>();
list.ForEach(l=>newlist.Add(new Tuple<string, int>(l.Item1 + " " + A[i],l.Item2 + A[i])));
list.Add(new Tuple<string, int>(A[i].ToString(),A[i]));
list.AddRange(newlist);
}
Tuple<string, int> solution = list.Where(l =>l.Item2 >= 100).OrderBy(o=>o.Item2).First();