Round number up to a certain combination of given values

时间:2016-03-02 10:59:56

标签: algorithm rounding

I've been having a hard time solving this and also explaining it to people, but I will try: I have a float number and I want it rounded to the nearest combination of a few other given numbers.

I will now go straight into examples, otherwise I'm afraid I'll lose you:

Let's say our numbers to round up to are: 190, 290, 540, 1000

I'll provide some examples of given numbers and expected results just to make sure we are on the same page:

Given number: 54,6
Expected result: 190 (1x190)

Given number: 287,5
Expected result: 290 (1x290)

Given number: 575
Expected result: 580 (2x290) 

Given number: 1150 
Expected result: 1190 (1x1000 + 1x190)

Given number: 1955
Expected result: 2020 (1x1000 + 1x540 + 1x290 + 1x190)

Given number: 2875
Expected result: 3020 (2x1000 + 1x540 + 1x290 + 1x190)

So the point is to get the sum of values that equal or exceed the given number (and extract the one that exceeds the least?) I wrote a simple-minded function that somehow does what I want, but not exactly:

function roundUpBasedOnPackaging(neededAmount, packagingIntegerArray) {

  console.log("We need to fill: " + neededAmount);

  var roundTo = 0;

  for (var len = packagingIntegerArray.length, i = len - 1; i >= 0; i--) {
    var currentItemGrams = packagingIntegerArray[i];

    //console.log("we are at " + currentItemGrams);

    if (roundTo + currentItemGrams <= neededAmount) {
      console.log("-- add: " + currentItemGrams);
      roundTo += currentItemGrams;
      i = len; // try this again
    }
    // if last and we'r not filled yet
    else if (i == 0 && roundTo < neededAmount) {
      console.log("-- add to get over neededAmount: " + currentItemGrams);
      roundTo += currentItemGrams;
    }

  }

  console.log("rounded to " + roundTo);

}

roundUpBasedOnPackaging(287.5, [190, 290, 540, 1000]);

You can clearly see what I did: loop from highest to lowest and add if the value is not equal or over our initial value. But this of course won't work in the example I provided (for value 287,5 since it would be much better to just choose 290 instead of two times 190).

It would be nice if I could get the algorithm to choose bigger numbers first and then use smaller ones.. but if you can provide a solution where smaller ones are used as a priority - that would be useful also.

I reckon I'd need a combination of sums to solve this - all possible combinations?!, but it would probably get messy. I don't mind a bit of recursion though...

Also, I think the second condition (else if) in my code is not really optimal - I'm sure there is a case where this wouldn't work properly and even go over the given value.

Any tips appreciated. Will work on my second solution until then :)

3 个答案:

答案 0 :(得分:2)

如果优先考虑最大金额并且不能超过给定数量,则解决方案很简单。

计算最大金额适合的次数(这是除法的商)并推导出。

然后重复下一个最大的,依此类推。

1385: 1385/1000 = 1 => 385 remain
 385:  385/ 540 = 0 => 385 remain
 385:  385/ 290 = 1 =>  95 remain
  95:   95/ 190 = 0 => done.

答案 1 :(得分:1)

似乎这个问题类似于背包问题,这是NP难的,但如果初始权重具有足够大的公约数,则可以使用动态方法在可接受的时间内找到解决方案。

{K, N, M, ...} - 初始权重集S - 我们需要向上舍入的数字。

  1. 生成一组权重W = {K, 2K, 3K,..., (|S/K| + 1)*K, N, 2N, ..., (|S/N| + 1)*N, M, ...},用于填充背包。对于重复权重,请根据偏好规则进行选择。

  2. 构建动态排序组合D,其中项目(组合)按摘要权重排序,跟踪S中等于或大于minExtra的最小额外权重。换句话说,minExtra是目前为止发现的最佳体重。

  3. 算法:

    D = {};
    minExtra = +inf 
    for each (e in W) {                 // take next element from W
        for each (c in D) {             // iterate over existing combinations in D
            nw = c.weight + e.weight;   // weight resulting of adding e
            if (nw < minExtra) {
                D.put(nw, c.add(e))     // assign to nw copy of c with e added
                if (nw >= S)
                   minExtra = nw;
            }
        }
        if (e.weight < minExtra) {   
            D.put(new C(e))             // put combination of only item e
            if (e.weight >= S)
                minExtra = e.weight
        }
    }
    return D.get(minExtra)
    
    如果首选更多项目的组合,

    D.put()应接受现有重量的元素,否则将丢弃。

答案 2 :(得分:-1)

   Given number: 2875
   Expected result: 3020 (2x1000 + 1x540 + 1x290 + 1x190)

期待10 × 290 = 2900更接近。如果这符合您的规则。

如果我是对的,这真的让我想起Knapsack problem,虽然它不是完全一样的。

编辑。如果你需要尽可能多的大数字,你正在寻找的算法看起来像这样:

-> fit as many 1000s as you can, save in "a"
-> take the smallest next number that makes the sum greater, save result in "b"
-> use the biggest number smaller than the "smaller next number" in previous step, add them in "a" until you can't fit them anymore
-> take the smallest next number that makes the sum greater, save result in "c"
-> etc.
-> return the closest value you end up with

如果它的表现不够好(结果不够好),那么你需要根据一些任意值来衡量这些数字,那就是完全一个背包问题。