选择找零最少或没有找零的硬币

22

我正在制作一个游戏,其中硬币面值为 $10、$5、$3 和 $1。玩家可以在库存中拥有 0 或多个每种类型的货币,最多总共 15 枚硬币。我正在尝试找出如何正确选择硬币,以便最少地给出找零。起初我以为这很容易解决,但现在我在困扰中。

以下是两个进一步解释情况的示例:

示例 1:

用户携带这些硬币:$5、$3、$3、$3、$1、$1、$1、$1,并想购买一件价值为 $12 的物品。解决方案是使用 $5、$3、$3、$1 支付并不找零。

示例 2:

用户没有任何 $1 硬币,携带的是 $5、$3、$3、$3、$3。购买的物品价格为 $12,所以他们用 $5、$3、$3、$3 支付,并找回 $2 的零钱。

由于我们首先选择较大的硬币,我无法弄清楚玩家的库存中是否有足够的低价值硬币(此例中为 $1),以容纳示例 1,如果没有足够的硬币,则如示例 2 中使用更多的高面值硬币。

另一个问题可以从以下示例中看到,尽管我只希望能够使上述两个示例起作用:

示例 3:

用户携带这些硬币:$5、$3、$3、$3。玩家花费 $6 购买物品。最好使用 $3 和 $3 并不找零,而不是使用 $5 和 $3 并找回 $2。

我相信可以使用递归和贪心算法的变化来解决前两个示例。

关于悬赏奖励:

目前我已经提供了自己的临时解决方案,但是我喜欢Llama先生下面的方法(请参见他引用的链接),并且希望找到一个PHP示例来实现这一点。我相信这种方法不需要递归,并使用记忆化。

如果有多个最小更改选项,则希望选择使用最少硬币付款的选项。


@HarryBomrah,是的,绝对会很有帮助。 - kojow7
每种硬币的最大数量、硬币的最大数量和/或硬币的最大总价值是多少?您可能会发现,将计数以某种方式存储而不是使用硬币列表会有所好处。例如:[#10, #5, #3, #1],因此您的示例看起来像:1) [0,1,3,4]2) [0,1,4,1]3) [0,1,3,0] - Nuclearman
我不确定是否理解了你的更新,但我认为我的帖子回答了这个问题。你能确认或给我任何反馈吗? - Adam
在SO上有大量关于“找零问题”的问题。您确定不能在现有的问题和答案中找到解决方案吗? - m69 ''snarky and unwelcoming''
m69:我已经查看了许多,但没有找到一个。 - kojow7
显示剩余3条评论
8个回答

14

该问题可以定义为:

Return a subset of items where the sum is closest to x, but >= x.

这个问题叫做子集和问题。它是NP完全问题。你不会找到一个在伪多项式时间内运行的完美算法,只有不完美的启发式算法。

然而,如果硬币的数量非常少,那么对解决方案空间进行详尽搜索肯定会奏效。

如果硬币数较大,则应查看维基百科以获取概述:https://en.wikipedia.org/wiki/Subset_sum_problem#Polynomial_time_approximate_algorithm


2
有一个完美的算法。它只取决于你愿意花多少时间来找到目标。你可以使用一种天真的方法,尝试所有可能的硬币组合,这将被认为是“完美的”,因为如果答案存在,它不会错过。然而,还有更快的方法:https://dev59.com/6GIk5IYBdhLWcg3wG63b#19572277 - Mr. Llama
这是在现实生活中,我寻找最容易实现且相对有效的解决方案的问题。如果我没记错的话,“贪心”算法(选择小于等于余额的最大剩余账单)在NP完全问题中大约80%的时间是最优解。OP还可以调整账单面额,使每个面额都是下一个最低面额的精确倍数,这将保证在他设计的游戏世界中始终成功使用贪心算法...看起来很合理。 - Ryan Hanekamp

10

我有一个类似的问题,除了允许组合的总和超过目标金额外,必须保持在目标金额以下。最终,我使用了这个答案中介绍的动态方法。你也应该可以使用它。

步骤如下:

  1. 从一个只包含一个空元素的列表开始。
  2. 对于列表中的每个条目...
    1. 复制该条目,并向其中添加第一个不包含的硬币(没有硬币价值!)。
    2. 仅当新的总值不存在于列表中时,将新元素存储在原始列表中。
  3. 重复步骤2,直到进行一次遍历,未向列表添加新元素为止。
  4. 迭代结果列表并保持最佳组合(使用您的标准)。

*:我们可以进行这种优化,因为我们不特别关心使用哪些硬币来组合,只关心硬币集合的总和值。

如果使用总和值作为键,可以对上述算法进行一些优化。


谢谢。我已经发布了一个新的解决方案作为可能的答案。它是时间有效的,但并不能完全解决我的第三个例子。 - kojow7
@kojow7 - 怎么不行呢?在最后一步,第4部分,保留大于或等于目标的最低结果。你是将每个硬币视为一个独立的对象,对吗?(在步骤2.1中,如果没有包括特定的硬币,应该添加它,而不是那个硬币的价值 - Mr. Llama
抱歉,我的解决方案并不能解决我的第三个例子。我仍在努力完全理解您的解决方案,并思考它是否需要递归,因为这可能不是时间有效的。 - kojow7
@kojow - 这根本不需要递归。最多需要N次迭代,其中N是您拥有的硬币数量。将硬币转换为一个对象,其中键是唯一ID(序列号),值是硬币的实际价值。这将使得在集合中检查特定硬币的存在变得更加容易。 - Mr. Llama
那个链接上说对于那个例子,最坏情况下时间复杂度为O(n^4),我猜我的最坏情况应该是O(n^15)? - kojow7
@kojow7 - 正确,那将是最坏的情况(大O符号)。这假设你的每个硬币组合都会产生唯一的价值。然而,这几乎永远不会发生。 - Mr. Llama

1
你可以使用栈来枚举有效的组合。以下版本使用了一个小优化,计算当前面额的最小值是否需要。如果有多个最小找零组合,则会返回多个,可以通过记忆化限制;如果当前面额可以用零找零完成组合,也可以添加早期退出。我希望简短的注释代码是自解释的(如果需要进一步解释,请告诉我):
function leastChange($coin_value,$inventory,$price){
  $n = count($inventory);
  $have = 0;
  for ($i=0; $i<$n; $i++){
    $have += $inventory[$i] * $coin_value[$i];
  }

  $stack = [[0,$price,$have,[]]];
  $best = [-max($coin_value),[]];

  while (!empty($stack)){

    // each stack call traverses a different set of parameters
    $parameters = array_pop($stack);
    $i = $parameters[0];
    $owed = $parameters[1];
    $have = $parameters[2];
    $result = $parameters[3];

    // base case
    if ($owed <= 0){
      if ($owed > $best[0]){
        $best = [$owed,$result];
      } else if ($owed == $best[0]){

        // here you can add a test for a smaller number of coins

        $best[] = $result;
      }
      continue;
    }

    // skip if we have none of this coin
    if ($inventory[$i] == 0){
      $result[] = 0;
      $stack[] = [$i + 1,$owed,$have,$result];
      continue;
    }

    // minimum needed of this coin
    $need = $owed - $have + $inventory[$i] * $coin_value[$i];

    if ($need < 0){
      $min = 0;
    } else {
      $min = ceil($need / $coin_value[$i]);
    }

    // add to stack
    for ($j=$min; $j<=$inventory[$i]; $j++){
      $stack[] = [$i + 1,$owed - $j * $coin_value[$i],$have - $inventory[$i] * $coin_value[$i],array_merge($result,[$j])];
      if ($owed - $j * $coin_value[$i] < 0){
        break;
      }
    }
  }

  return $best;
}

输出:

$coin_value = [10,5,3,1];
$inventory = [0,1,3,4];
$price = 12;

echo json_encode(leastChange($coin_value,$inventory,$price)); // [0,[0,1,2,1],[0,1,1,4],[0,0,3,3]]

$coin_value = [10,5,3,1];
$inventory = [0,1,4,0];
$price = 12;

echo json_encode(leastChange($coin_value,$inventory,$price)); // [0,[0,0,4]]

$coin_value = [10,5,3,1];
$inventory = [0,1,3,0];
$price = 6;

echo json_encode(leastChange($coin_value,$inventory,$price)); // [0,[0,0,2]]

$coin_value = [10,5,3,1];
$inventory = [0,1,3,0];
$price = 7;

echo json_encode(leastChange($coin_value,$inventory,$price)); // [-1,[0,1,1]]

更新:

既然您也对最少硬币数量感兴趣,我认为只有在我们能够保证不会跳过更好的可能性时,备忘录才能发挥作用。我认为,如果我们首先使用最大的硬币进行深度优先搜索,就可以做到这一点。如果我们已经使用较大的硬币达到了相同的总和,那么继续当前线程就没有意义了。确保输入库存按面额大小降序排序,并添加/更改以下内容:

// maximum needed of this coin
$max = min($inventory[$i],ceil($owed / $inventory[$i]));

// add to stack 
for ($j=$max; $j>=$min; $j--){

如果有多个选项可以得到相同数量的找零,我如何只返回使用最少硬币的那个选项? - kojow7
是的,我在问你的代码。我的意思是,如果有几个选项的改变量相同,就像上面的第一个案例一样,你有[$5, $3, $3, $1]、[$5, $3, $1, $1, $1, $1]和[$3, $3, $3, $1, $1, $1]这些都是有效选项,我希望它只返回[$5, $3, $3, $1]选项,因为它使用了最少的硬币。谢谢。 - kojow7
@kojow7 我给你一个提示(如果需要更多指导,请告诉我):考虑如何在“基本情况”下使用变量$result$best以及PHP函数array_sum - גלעד ברקן
@kojow7(或者,您可以将一个增量变量添加到递归参数中,表示当前组合中有多少硬币。) - גלעד ברקן
1
@kojow7,我很高兴我的代码能够帮到你。我更喜欢你发布一个新的答案,而不是在这里修改我的代码。 - גלעד ברקן
显示剩余8条评论

1
我想分享我的解决方案。如果其他人能帮我批判一下,我会很感激。
<?php

$coin_value = array(10,5,3,1);
$inventory = array(1,2,0,2);
$price = 17;

for ($i = 3; $i >= 0; $i--){

        $btotal = 0;
        $barray = array();

        for ($j = 0; $j < 4; $j++){
                $remaining = $price - $btotal;
                $to_add = floor($remaining / $coin_value[$j]);

                if ($i != 3 && $i == $j){
                        $to_add++;
                }

                if ($inventory[$j] < $to_add){
                        $to_add = $inventory[$j];
                }

                $btotal += $to_add * $coin_value[$j];

                for ($k = 0; $k < $to_add; $k++){
                        $barray[] = $coin_value[$j];
                }

                if ($btotal >= $price)
                        break 2; //warning: breaks out of outer loop

        }
}

$change_due = $btotal - $price;

print_r($barray);

echo "Change due: \$$change_due\n";

?>

它涵盖了我原来问题中的示例1和2,但不包括示例3。然而,我认为暂时可以使用它,除非有人能提出更好的解决方案。我决定不使用递归,因为这似乎需要太多时间。

0

这个答案基于גלעד-ברקן的回答。我按照他的要求在这里发布它。虽然没有一个答案完全符合我的要求,但我发现这是发布的最佳选项。这是我目前正在使用的修改后的算法:

<?php

function leastChange($inventory, $price){

    //NOTE: Hard coded these in the function for my purposes, but $coin value can be passed as a parameter for a more general-purpose algorithm
    $num_coin_types = 4;
    $coin_value = [10,5,3,1];

    $have = 0;
    for ($i=0; $i < $num_coin_types; $i++){
            $have += $inventory[$i] * $coin_value[$i];
    }

    //NOTE: Check to see if you have enough money to make this purchase
    if ($price > $have){
            $error = ["error", "Insufficient Funds"];
            return $error;
    }

    $stack = [[0,$price,$have,[]]];
    $best = [-max($coin_value),[]];

    while (!empty($stack)){

            // each stack call traverses a different set of parameters
            $parameters = array_pop($stack);

            $i = $parameters[0];
            $owed = $parameters[1];
            $have = $parameters[2];
            $result = $parameters[3];

            if ($owed <= 0){
                    //NOTE: check for new option with least change OR if same as previous option check which uses the least coins paid
                    if ($owed > $best[0] || ($owed == $best[0] && (array_sum($result) < array_sum($best[1])))){

                            //NOTE: add extra zeros to end if needed
                            while (count($result) < 4){
                                    $result[] = 0;
                            }
                            $best = [$owed,$result];
                    }
                    continue;
            }

            // skip if we have none of this coin
            if ($inventory[$i] == 0){
                    $result[] = 0;
                    $stack[] = [$i + 1,$owed,$have,$result];
                    continue;
            }

            // minimum needed of this coin
            $need = $owed - $have + $inventory[$i] * $coin_value[$i];

            if ($need < 0){
                    $min = 0;
            } else {
                    $min = ceil($need / $coin_value[$i]);
            }

            // add to stack
            for ($j=$min; $j<=$inventory[$i]; $j++){
                    $stack[] = [$i + 1,$owed - $j * $coin_value[$i],$have - $inventory[$i] * $coin_value[$i],array_merge($result,[$j])];
                    if ($owed - $j * $coin_value[$i] < 0){
                            break;
                    }
            }
    }

    return $best;
}

这是我的测试代码:

$start = microtime(true);

$inventory = [0,1,3,4];
$price = 12;
echo "\n";
echo json_encode(leastChange($inventory,$price));
echo "\n";



$inventory = [0,1,4,0];
$price = 12;
echo "\n";
echo json_encode(leastChange($inventory,$price));
echo "\n";

$inventory = [0,1,4,0];
$price = 6;
echo "\n";
echo json_encode(leastChange($inventory,$price));
echo "\n";


$inventory = [0,1,4,0];
$price = 7;
echo "\n";
echo json_encode(leastChange($inventory,$price));
echo "\n";


$inventory = [1,3,3,10];
$price=39;
echo "\n";
echo json_encode(leastChange($inventory,$price));
echo "\n";

$inventory = [1,3,3,10];
$price=45;
echo "\n";
echo json_encode(leastChange($inventory,$price));
echo "\n";

//stress test
$inventory = [25,25,25,1];
$price=449;
echo "\n";
echo json_encode(leastChange($inventory,$price));
echo "\n";

$time_elapsed = microtime(true) - $start;
echo "\n Time taken: $time_elapsed \n";

结果:

[0,[0,1,2,1]]

[0,[0,0,4,0]]

[0,[0,0,2,0]]

[-1,[0,1,1,0]]

[0,[1,3,3,5]]

["error","Insufficient Funds"]

[-1,[25,25,25,0]]

Time taken: 0.0046839714050293

当然,这个时间是以微秒为单位的,因此它在一秒钟内执行了一小部分!


0

我所提供的解决方案涵盖了您在问题中发布的3个示例,并且始终以尽可能少的硬币找零。

我进行的测试似乎执行非常快。

这里是代码:

<?php

//Example values
$coin_value = array(10,5,3,1);
$inventory = array(5,4,3,0);
$price = 29;

//Initialize counters
$btotal = 0;
$barray = array(0,0,0,0);

//Get the sum of coins
$total_coins = array_sum($inventory);

function check_availability($i) {
    global $inventory, $barray;
    $a = $inventory[$i];
    $b = $barray[$i];
    $the_diff = $a - $b;
    return $the_diff != 0;
}

/*
 * Checks the lower currency available
 * Returns index for arrays, or -1 if none available
 */
function check_lower_available() {
    for ($i = 3; $i >= 0; $i--) {
        if (check_availability($i)) {
            return $i;
        }
    }
    return -1;
}

for($i=0;$i<4;$i++) {
    while(check_availability($i) && ($btotal + $coin_value[$i]) <= $price) {
        $btotal += $coin_value[$i];
        $barray[$i]++;
    }
}

if($price != $btotal) {
    $buf = check_lower_available();
    for ($i = $buf; $i >= 0; $i--) {
        if (check_availability($i) && ($btotal + $coin_value[$i]) > $price) {
            $btotal += $coin_value[$i];
            $barray[$i]++;
            break;
        }
    }
}

// Time to pay
$bchange = 0;
$barray_change = array(0,0,0,0);

if ($price > $btotal) {
    echo "You have not enough money.";
}
else {
    $pay_msg = "You paid $".$btotal."\n\n";
    $pay_msg.= "You used ".$barray[0]." coins of $10\n";
    $pay_msg.= "You used ".$barray[1]." coins of $5\n";
    $pay_msg.= "You used ".$barray[2]." coins of $3\n";
    $pay_msg.= "You used ".$barray[3]." coins of $1\n\n\n";
    // Time to give change
    $the_diff = $btotal - $price;
    if (!empty($the_diff)) {
        for ($i = 0; $i < 4; $i++) {
            while($the_diff >= $coin_value[$i]) {
                $bchange += $coin_value[$i];
                $barray_change[$i]++;
                $the_diff -= $coin_value[$i];
            }
        }

        $check_sum = array_sum($inventory) - array_sum($barray);
        $check_sum+= array_sum($barray_change);
        $msg = "";
        if ($check_sum < 15) {
            $change_msg = "Your change: $".$bchange."\n\n";
            $change_msg.= "You received ".$barray_change[0]." coins of $10\n";
            $change_msg.= "You received ".$barray_change[1]." coins of $5\n";
            $change_msg.= "You received ".$barray_change[2]." coins of $3\n";
            $change_msg.= "You received ".$barray_change[3]." coins of $1\n\n";
            $msg = $pay_msg.$change_msg;
        }
        else {
            $msg = "You have not enough space to hold the change.\n";
            $msg.= "Buy cancelled.\n";
        }
    }
    else {
        $msg = $pay_msg."You do not need change\n";
    }
    if ($check_sum < 15) {
        for ($i = 0; $i < 4; $i++) {
            $inventory[$i] -= $barray[$i];
            $total_coins-= $barray[$i];
        }
        for ($i = 0; $i < 4; $i++) {
            $inventory[$i] += $barray_change[$i];
            $total_coins+= $barray[$i];
        }
    }
    echo $msg;
    echo "Now you have:\n";
    echo $inventory[0]." coins of $10\n";
    echo $inventory[1]." coins of $5\n";
    echo $inventory[2]." coins of $3\n";
    echo $inventory[3]." coins of $1\n";
}

抱歉,这对我的第三个示例不起作用。我也建议不要使用全局变量。 - kojow7

0

这是我的解决方案,我不知道它的效率有多高,但它能工作,我很乐意听取建议。

<?php

    $player=array(0,3,1,0);//how much coins you have
    $player_copy=$player;
    $coin_count=array(0,0,0,0);//memorize which coins you gave
    $coin_value=array(1,3,5,10);
    $price=6;       //price of item
    $price_copy=$price;
    $z=3;
    $change=array(-1,-1,-1,-1,-1); //memorise possible changes you can get
    $k=0;
    $flag=0;

label1: for($j=3;$j>=0;$j--){
            $coin_count[$j]=0;
            $player[$j]=$player_copy[$j];
        }
        for($j=$z;$j>=0;$j--){
            while(($price>0) && 1<=$player[$j]){
                $price-=$coin_value[$j];
                $player[$j]--;
                $coin_count[$j]++;
            }
        }
        $change[$k++]=$price;
         if($price!=0){
                for($j=$z;$j>=0;$j--)
                    if($price_copy>$coin_value[$j]){
                        $z=$j-1;
                        $price=$price_copy;
                        goto label1;
                    }
                $flag=1;
         }
    //find minimum change 
        $minv=$change[0];

         for($i=1;$change[$i]>=0 and $i<4;$i++)
             if($change[$i]>$minv)
                $minv=$change[$i];
         $i;
  //when you find minimum change find which coins you have used
         for($i=0;$i<4;$i++)
             if($change[$i]==$minv && $flag==1){
                $flag=2;
                for($j=3;$j>=0;$j--){//reset coin_count and player budget
                    $coin_count[$j]=0;
                    $player[$j]=$player_copy[$j];
                }
                 for($j=3-($i%2)-1;$j>=0;$j--){
                     while(($price>0) && 1<=$player[$j]){
                         $price-=$coin_value[$j];
                         $player[$j]--;
                         $coin_count[$j]++;
                     }
                  }
              }
//prints result
         for($j=0;$j<4;$j++)
            printf("%d x %d\n",$coin_count[$j],$coin_value[$j]);
         printf("change: %d\n",$minv);
?>

0

我不懂PHP,所以我尝试用Java编写。希望这样可以,因为算法才是最重要的。

我的代码如下:

package stackoverflow.changecalculator;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ChangeCalculator
{
    List<Integer> coinsInTil = new ArrayList<>();

    public void setCoinsInTil(List<Integer> coinsInTil)
    {
        this.coinsInTil = coinsInTil;
    }

    public Map<String, List> getPaymentDetailsFromCoinsAvailable(final int amountOwed, List<Integer> inPocketCoins)
    {
        List<Integer> paid = new ArrayList<>();
        int remaining = amountOwed;

        // Check starting with the largest coin.
        for (Integer coin : inPocketCoins)
            if (remaining > 0 && (remaining - coin) >= 0) {
                    paid.add(coin);
                    remaining = remaining - coin;
            }

        ProcessAlternative processAlternative = new ProcessAlternative(amountOwed, inPocketCoins, paid, remaining).invoke();
        paid = processAlternative.getPaid();
        remaining = processAlternative.getRemaining();

        removeUsedCoinsFromPocket(inPocketCoins, paid);
        int changeOwed = payTheRestWithNonExactAmount(inPocketCoins, paid, remaining);
        List<Integer> change = calculateChangeOwed(changeOwed);

        Map<String, List> result = new HashMap<>();
        result.put("paid", paid);
        result.put("change", change);
        return result;
    }

    private void removeUsedCoinsFromPocket(List<Integer> inPocketCoins, List<Integer> paid)
    {
        for (int i = 0; i < inPocketCoins.size(); i++) {
            Integer coin = inPocketCoins.get(i);
            if (paid.contains(coin))
                inPocketCoins.remove(i);
        }
    }

    private List<Integer> calculateChangeOwed(int changeOwed)
    {
        List<Integer> change = new ArrayList<>();
        if (changeOwed < 0) {
            for (Integer coin : coinsInTil) {
                if (coin + changeOwed == 0) {
                    change.add(coin);
                    changeOwed = changeOwed + coin;
                }
            }
        }
        return change;
    }

    private int payTheRestWithNonExactAmount(List<Integer> inPocketCoins, List<Integer> paid, int remaining)
    {
        if (remaining > 0) {
            for (int coin : inPocketCoins) {
                while (remaining > 0) {
                    paid.add(coin);
                    remaining = remaining - coin;
                }
            }
        }
        return remaining;
    }
}

ProcessAlternative 类处理最大硬币无法让我们得到不需要找零的情况,因此我们尝试另一种方法。
package stackoverflow.changecalculator;

import java.util.ArrayList;
import java.util.List;

// if any remaining, check if we can pay with smaller coins first.
class ProcessAlternative
{
    private int amountOwed;
    private List<Integer> inPocketCoins;
    private List<Integer> paid;
    private int remaining;

    public ProcessAlternative(int amountOwed, List<Integer> inPocketCoins, List<Integer> paid, int remaining)
    {
        this.amountOwed = amountOwed;
        this.inPocketCoins = inPocketCoins;
        this.paid = paid;
        this.remaining = remaining;
    }

    public List<Integer> getPaid()
    {
        return paid;
    }

    public int getRemaining()
    {
        return remaining;
    }

    public ProcessAlternative invoke()
    {
        List<Integer> alternative = new ArrayList<>();
        int altRemaining = amountOwed;
        if (remaining > 0) {
            for (Integer coin : inPocketCoins)
                if (altRemaining > 0 && factorsOfAmountOwed(amountOwed).contains(coin)) {
                    alternative.add(coin);
                    altRemaining = altRemaining - coin;
                }
            // if alternative doesn't require change, use it.
            if (altRemaining == 0) {
                paid = alternative;
                remaining = altRemaining;
            }
        }
        return this;
    }

    private ArrayList<Integer> factorsOfAmountOwed(int num)
    {
        ArrayList<Integer> aux = new ArrayList<>();
        for (int i = 1; i <= num / 2; i++)
            if ((num % i) == 0)
                aux.add(i);
        return aux;
    }
}

我通过执行示例1,然后执行示例2,最后转到示例3来完成它。在这里添加了过程替代位,并且对于原始测试硬币的替代返回0所需更改,因此我将输入金额更新为15而不是12,以便计算所需更改。

测试如下:

package stackoverflow.changecalculator;

import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class ChangeCalculatorTest
{
    public static final int FIFTY_PENCE = 0;
    public static final int TWENTY_PENCE = 1;
    public static final int TEN_PENCE = 2;
    public static final int FIVE_PENCE = 3;
    public static final int TWO_PENCE = 4;
    public static final int PENNY = 5;

    public ChangeCalculator calculator;

    @Before
    public void setUp() throws Exception
    {
        calculator = new ChangeCalculator();
        List<Integer> inTil = new ArrayList<>();
        inTil.add(FIFTY_PENCE);
        inTil.add(TWENTY_PENCE);
        inTil.add(TEN_PENCE);
        inTil.add(FIVE_PENCE);
        inTil.add(TWO_PENCE);
        inTil.add(PENNY);
        calculator.setCoinsInTil(inTil);
    }

    @Test
    public void whenHaveExactAmount_thenNoChange() throws Exception
    {
        // $5, $3, $3, $3, $1, $1, $1, $1
        List<Integer> inPocket = new ArrayList<>();
        inPocket.add(5);
        inPocket.add(3);
        inPocket.add(3);
        inPocket.add(3);
        inPocket.add(1);
        inPocket.add(1);
        inPocket.add(1);
        inPocket.add(1);

        Map<String, List> result = calculator.getPaymentDetailsFromCoinsAvailable(12, inPocket);

        List change = result.get("change");
        assertTrue(change.size() == 0);
        List paid = result.get("paid");
        List<Integer> expected = new ArrayList<>();
        expected.add(5);
        expected.add(3);
        expected.add(3);
        expected.add(1);
        assertEquals(expected, paid);
    }

    @Test
    public void whenDoNotHaveExactAmount_thenChangeReturned() throws Exception {
        // $5, $3, $3, $3, $3
        List<Integer> inPocket = new ArrayList<>();
        inPocket.add(5);
        inPocket.add(3);
        inPocket.add(3);
        inPocket.add(3);
        inPocket.add(3);

        Map<String, List> result = calculator.getPaymentDetailsFromCoinsAvailable(15, inPocket);

        List change = result.get("change");
        Object actual = change.get(0);
        assertEquals(2, actual);
        List paid = result.get("paid");
        List<Integer> expected = new ArrayList<>();
        expected.add(5);
        expected.add(3);
        expected.add(3);
        expected.add(3);
        expected.add(3);
        assertEquals(expected, paid);
    }

    @Test
    public void whenWeHaveExactAmountButItDoesNotIncludeBiggestCoin_thenPayWithSmallerCoins() throws Exception {
        // $5, $3, $3, $3
        List<Integer> inPocket = new ArrayList<>();
        inPocket.add(5);
        inPocket.add(3);
        inPocket.add(3);
        inPocket.add(3);

        Map<String, List> result = calculator.getPaymentDetailsFromCoinsAvailable(6, inPocket);

        List change = result.get("change");
        assertTrue(change.size() == 0);
        List paid = result.get("paid");
        List<Integer> expected = new ArrayList<>();
        expected.add(3);
        expected.add(3);
        assertEquals(expected, paid);
    }
}

测试还不是最完美的,但目前它们都通过了。我可能会回头添加更多的测试用例来看看是否可以破坏它,但现在没有时间。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接