我有一笔雇主支付的总金额,这笔金额需要在员工之间分配。
例如
a $100
b $200
c -$200
d -$200
e $500
应付总金额是所有项目的总和,在这种情况下为400美元
问题是我必须呼叫第三方系统逐一分配这些金额。但在分配期间,我不能让余额低于$ 0或高于总金额($ 400)。
因此,如果我按上述顺序插入a,b,c将起作用,因此当前分配的总和= 100 + 200 - 200 = $ 100。 但是,当我尝试分配d。系统将尝试添加 - $ 200,这将使当前分配的金额 - $ 100,即< $ 0是不允许的,所以它将被系统拒绝。
如果我对列表进行排序,那么负面项目是最后一个。即。
a $100
b $200
e $500
c -$200
d -$200
a将工作,b将工作,但当它试图插入e时,将有不足的资金错误,因为我们已超过400美元的最大值。我已经认识到没有灵丹妙药,并且总会出现会破坏的情景。但是我想提出一个能够最的解决方案。
正常的数据样本将包含5到100个项目。只有2-15%的人含有负数。
我是否有一种聪明的方法可以对列表进行排序?或者只是多次尝试分配会更好。例如,将正面和负面分成两个列表。插入正数直到出现一个错误,然后插入负数直到出现错误,然后在列表之间来回切换,直到全部分配或直到它们都出错。
答案 0 :(得分:2)
虽然这实际上和Haile的答案一样(我在发布他的帖子之前就开始做出答案,然后打我一拳)我想我会发布它,因为它包含一些源代码,可能会帮助想要一个人具体实现(抱歉它不在C#中,C ++是我目前最接近的东西)
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
using namespace std;
vector<int> orderTransactions(const vector<int>& input) {
int max = accumulate(input.begin(), input.end(), 0);
vector<int> results;
// if the sum is negative or zero there are no transactions that can be added
if (max <= 0) {
return results;
}
// split the input into positives and negatives
vector<int> sorted = vector<int>(input);
sort(sorted.begin(), sorted.end());
vector<int> positives;
vector<int> negatives;
for (int i = 0; i < sorted.size(); i++) {
if (sorted[i] >= 0) {
positives.push_back(sorted[i]);
} else {
negatives.push_back(sorted[i]);
}
}
// try to process all the transactions
int sum = 0;
while (!positives.empty() || !negatives.empty()) {
// find the largest positive transaction that can be added without exceeding the max
bool positiveFound = false;
for (int i = (int)positives.size()-1; i >= 0; i--) {
int n = positives[i];
if ((sum + n) <= max) {
sum += n;
results.push_back(n);
positives.erase(positives.begin()+i);
positiveFound = true;
break;
}
}
if (positiveFound == true) {
continue;
}
// if there is no positive find the smallest negative transaction that keep the sum above 0
bool negativeFound = false;
for (int i = (int)negatives.size()-1; i >= 0; i--) {
int n = negatives[i];
if ((sum + n) >= 0) {
sum += n;
results.push_back(n);
negatives.erase(negatives.begin()+i);
negativeFound = true;
break;
}
}
// if there is neither then this as far as we can go without splitting the transactions
if (!negativeFound) {
return results;
}
}
return results;
}
int main(int argc, const char * argv[]) {
vector<int> quantities;
quantities.push_back(-304);
quantities.push_back(-154);
quantities.push_back(-491);
quantities.push_back(-132);
quantities.push_back(276);
quantities.push_back(-393);
quantities.push_back(136);
quantities.push_back(172);
quantities.push_back(589);
quantities.push_back(-131);
quantities.push_back(-331);
quantities.push_back(-142);
quantities.push_back(321);
quantities.push_back(705);
quantities.push_back(210);
quantities.push_back(731);
quantities.push_back(92);
quantities.push_back(-90);
vector<int> results = orderTransactions(quantities);
if (results.size() != quantities.size()) {
cout << "ERROR: Couldn't find a complete ordering for the transactions. This is as far as we got:" << endl;
}
for (int i = 0; i < results.size(); i++) {
cout << results[i] << endl;
}
return 0;
}
答案 1 :(得分:1)
我认为你想做的是:
我没有测试下面的代码,但它应该是模糊的正确形状:
var validValues = values
.Where(v => Math.Abs(v) < upperLimit) //filter out anything that will always fail
.OrderBy(v => Math.Abs(v)); //sort by the absolute value (to maximise # of transactions)
var additions = validValues.Where(v => v >= 0);
var subtractionsEnumerator = validValues.Where(v => v < 0).GetEnumerator();
var currentTotal = 0.0;
//go through all of the additions
foreach (var addition in additions)
{
if (currentTotal + addition > upperLimit) //would the next addition take us over the limit?
{
//keep processing negative values until the next one would take us past $0
while (subtractionsEnumerator.MoveNext() && currentTotal + subtractionsEnumerator.Current > 0)
{
currentTotal += subtractionsEnumerator.Current;
}
}
if (currentTotal + addition > upperLimit) //if we weren't able to reduce by enough
throw new Exception("Can't process transactions");
currentTotal += addition;
}
//do we have any left over negatives? better process those as well
while (subtractionsEnumerator.MoveNext())
{
if (currentTotal + subtractionsEnumerator.Current < 0)
throw new Exception("Can't process transactions");
currentTotal += subtractionsEnumerator.Current;
}
答案 2 :(得分:1)
如果你想减少你“破坏”规则的次数(低于0 $或超过$ $),我认为以下方法可以解决问题:
<强> LOOP 强>:
显然,在某些时候您会发现您搜索的子集不存在,因此您必须中断规则才能继续。
请注意,选择子集时,按顺序排序和选择较小值的贪婪方法将不起作用。
编辑:如果您可以拆分转换,则更容易。只是保持循环。如果找不到下一个子集,请将最大值拆分并继续搜索。
答案 3 :(得分:1)
您可以尝试的算法。
实现很脏,分配了许多列表,结果是相反的顺序。 当然,在大名单上它确实很慢。
static List<int> GetDeepestPossibility(List<int> values, int sum = 0)
{
List<int> deepest = new List<int>();
for (int i = 0; i < values.Count; i++)
{
if (Allowed(values[i] + sum))
{
List<int> subValues = new List<int>(values);
subValues.RemoveAt(i);
List<int> possibility = GetDeepestPossibility(subValues, values[i] + sum);
possibility.Add(values[i]);
if (possibility.Count + 1 > deepest.Count)
{
possibility.Add(values[i]);
deepest = possibility;
if (deepest.Count == values.Count - 1)
break;
}
}
}
return deepest;
}
private static bool Allowed(int p)
{
return p >= 0 && p <= 600;
}
如果您可以拆分交易,那么在您被阻止时采用Tony的算法并拆分交易。
答案 4 :(得分:1)
我认为这是一个相当困难但有趣的问题!
搜索每个排列是不可行的,因为有n!订购清单的方式。
一种方法可能是尝试找到不超过总限制的最佳拟值。这个问题与Knapsack问题类似。然后,您可以再次使用相同的技术寻找不会使您低于零的最佳负值。重复,直到添加所有值。