按特定顺序分配资金

时间:2012-08-02 12:51:02

标签: c# algorithm

我有一笔雇主支付的总金额,这笔金额需要在员工之间分配。

例如

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%的人含有负数。

我是否有一种聪明的方法可以对列表进行排序?或者只是多次尝试分配会更好。例如,将正面和负面分成两个列表。插入正数直到出现一个错误,然后插入负数直到出现错误,然后在列表之间来回切换,直到全部分配或直到它们都出错。

5 个答案:

答案 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)

我认为你想做的是:

  1. 过滤掉任何始终失败的值
  2. 按最小绝对值对交易进行排序 - 较小的交易意味着我们可以在达到限制之前完成更多工作
  3. 继续处理肯定,直到下一个会导致我们超过限制
  4. 继续处理否定值,直到我们用完否定数据或下一个将我们低于$ 0
  5. 重复3-4次
  6. 我没有测试下面的代码,但它应该是模糊的正确形状:

    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

  • 选择正值的子集,使总和最大化,但小于 max ,添加这些资源
  • 选择负值的子集,使其绝对值之和最大化但仍然小于当前余额,减去那些资源

显然,在某些时候您会发现您搜索的子集不存在,因此您必须中断规则才能继续。

请注意,选择子集时,按顺序排序和选择较小值的贪婪方法将不起作用。

编辑:如果您可以拆分转换,则更容易。只是保持循环。如果找不到下一个子集,请将最大值拆分并继续搜索。

答案 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问题类似。然后,您可以再次使用相同的技术寻找不会使您低于零的最佳负值。重复,直到添加所有值。