简体   繁体   English

Javascript-引导现金支付 function

[英]Javascript- Guided cash payment function

I'm stuck on a project where we are making financial exercises for children.我被困在一个项目中,我们正在为孩子们做财务练习。 Each exercise has a specific objective (Screenshot of an exercise where students match a bill to a numeric amount):每个练习都有一个特定的目标(学生将账单与数字金额匹配的练习的屏幕截图):

截图在这里

One one of the exercises, students add bills to their wallet.其中一项练习是,学生将账单添加到他们的钱包中。 Once these bills are added, each user has a wallet array with bills in it as so:添加这些账单后,每个用户都有一个包含账单的钱包数组,如下所示:

var walletArray = [
  {
    code: aed,
    denomination: 0.5,
    image: "/assets/images/coin50",
    type: "coin",
  },
  { code: aed, denomination: 1, image: "/assets/images/coin1", type: "coin" },
  { code: aed, denomination: 5, image: "/assets/images/bill5", type: "bill" },
  { code: aed, denomination: 5, image: "/assets/images/bill5", type: "bill" },
  { code: aed, denomination: 10, image: "/assets/images/bill10", type: "bill" },
  { code: aed, denomination: 50, image: "/assets/images/bill50", type: "bill" },
];

I want to write a function that takes an integer input from the user (example: 17) and outputs which bills to pay with.我想编写一个 function,它接受用户输入的 integer(例如:17)并输出要支付的账单。 So for this example, if the user inputs 17, the function should output the bills with denominations 5, 5, and 10. I will then calculate the change for them.所以对于这个例子,如果用户输入 17,function 应该是 output 面额为 5、5 和 10 的钞票。然后我将为它们计算找零。 The website tells the users which bills to pay and how much change they can expect.该网站告诉用户要支付哪些账单以及他们可以期待多少零钱。

I have a function that calculates the total of the wallet array and notifies me if the input amount is greater than the total wallet balance:我有一个 function 计算钱包数组的总数并在输入金额大于钱包总余额时通知我:

function calculateTotal(walletArray) {
  total = 0;

  for (var i = 0; i < walletArray.length; i++) {
    total = total + walletArray[i].denomination;
  }

  if (payAmount > total) {
    console.log("ABOVE LIMIT");
  } else {
    return total;
  }
}

However, I cannot figure out how to pick the right bills from the array.但是,我无法弄清楚如何从数组中选择正确的账单。 I'm running a for loop on the array, and its easy enough if the amount input by the user perfectly matches a bill (20 or 50 for example).我在数组上运行一个 for 循环,如果用户输入的金额与帐单完全匹配(例如 20 或 50),这就足够简单了。 I just don't know how to select the different bills that the user needs to pay.就是不知道select 用户需要支付的不同账单。

for (var i = 0; i < walletArray.length; i++) {
  if (payAmount == walletArray[i].denomination) {
    console.log(
      "perfect match! pay with " + currency + walletArray[i].denomination
    );
  } else {
    //code to select the bills goes here
  }
}

Any help or even pseudocode to solve this problem would be greatly appreciated.任何解决此问题的帮助甚至伪代码都将不胜感激。 The solution can use vanilla js or any library you like!该解决方案可以使用 vanilla js 或您喜欢的任何库!

Important note : this problem requires a lot of computation (an unsimplified solution implies analyzing 2^wallet.length combinations of wallet coins).重要说明:这个问题需要大量计算(一个简单的解决方案意味着分析 2^wallet.length 钱包硬币的组合)。 Using large wallets for tests might result in large run times or even unresponsive javascript environments .使用大型钱包进行测试可能会导致运行时间较长,甚至javascript 环境无响应 That is not the case for the default example included in the snippet below, which runs typically in just a few hundredth of a second.以下代码段中包含的默认示例并非如此,它通常仅需百分之几秒即可运行。

There are several optimizations still to be performed, the most obvious one being simplifying the data structures used in code.还有一些优化有待执行,最明显的优化是简化代码中使用的数据结构。 I decided to stick for this first version to the original format, that results in the need to deep-clone objects that could be avoided.我决定坚持第一个版本的原始格式,这导致需要深度克隆可以避免的对象。

 // utility functions const cloneSelection = wallet => wallet?.map(o => ({...o})); const cloneSolution = solution => ({selection: cloneSelection(solution.selection), diff: solution.diff}); const generateUniqueId = function(selection, payAmount){ const a = selection.map(o=>o.denomination); let nr = 0, nv = -1, uniqueId = ''; a.forEach( function(ai, i){ if(ai === nv){ nr++; } else{ if(nv > 0){ uniqueId += (uniqueId? '+': '') + nr + 'x' + nv; } nv = ai; nr = 1; } } ); return uniqueId + (uniqueId? '+': '') + nr + 'x' + nv + '=' + payAmount; } const memoizationData = {}; const selectAmount = function(payAmount, wallet){ const uniqueId = generateUniqueId(wallet, payAmount); let solution = memoizationData[uniqueId]; if(solution){ return cloneSolution(solution); } if(wallet.length === 1){ const diff = wallet[0].denomination - payAmount; return diff >= 0? {selection: cloneSelection(wallet), diff}: {selection: null, diff: 1/0}; } const diff = wallet.reduce((s, o) => s + o.denomination, 0) - payAmount; if(diff < 0){ solution = {selection: null, diff: 1/0}; // "non-solution" } else{ solution = {selection: wallet, diff}; for(let iOut = 0; iOut < wallet.length; iOut++){ if(solution.diff < 1e-3){ break; } const wallet1 = cloneSelection(wallet); wallet1.splice(iOut, 1); // extract i-th "coin" from the wallet const solution1 = selectAmount(payAmount, wallet1); if(solution1.diff < solution.diff){ solution = solution1; } } } memoizationData[uniqueId] = cloneSolution(solution); return solution; } const aed = 'aed'; const walletArray = [ { code: aed, denomination: 0.5, image: "/assets/images/coin50", type: "coin" }, { code: aed, denomination: 0.5, image: "/assets/images/coin50", type: "coin" }, { code: aed, denomination: 0.5, image: "/assets/images/coin50", type: "coin" }, { code: aed, denomination: 1, image: "/assets/images/coin1", type: "coin" }, { code: aed, denomination: 1, image: "/assets/images/coin1", type: "coin" }, { code: aed, denomination: 1, image: "/assets/images/coin1", type: "coin" }, { code: aed, denomination: 2, image: "/assets/images/coin2", type: "coin" }, { code: aed, denomination: 5, image: "/assets/images/bill5", type: "bill" }, { code: aed, denomination: 5, image: "/assets/images/bill5", type: "bill" }, { code: aed, denomination: 5, image: "/assets/images/bill5", type: "bill" }, { code: aed, denomination: 10, image: "/assets/images/bill10", type: "bill" }, { code: aed, denomination: 50, image: "/assets/images/bill50", type: "bill" }, ]; // if not sorted walletArray.sort((o1, o2)=>o1.denomination - o2.denomination); const t0 = Date.now(); const ret = selectAmount(16.5, walletArray); const dt = (Date.now() - t0)/1000; console.log('solution = ', ret); console.log(`dt = ${dt.toFixed(2)}s`);

Description of the algorithm算法说明

We start with a wallet W , which is a set of n coins C 1 , C 2 , ..., C n , and a pay amount A : problem P( W , A ).我们从钱包W开始,它是一组n 个硬币C 1 , C 2 , ..., C n和支付金额A :问题 P( W , A )。

The solution to the problem is a selection of the coins in the wallet, a subset of W .该问题的解决方案是选择钱包中的硬币,即W的子集。 A non-solution (that is there are not enough funds to cover the amount) is denoted by a void selection.非解决方案(即没有足够的资金来支付金额)由无效选择表示。

The solution is recursive.解决方案是递归的。

Start solution启动解决方案

The default solution for P( W , A ) is W (the selection is the whole wallet) if the sum of coin values in the wallet is >= A , or ∅ (non-solution) otherwise.如果钱包中硬币价值的总和 >= A ,则 P( W , A ) 的默认解为W (选择是整个钱包),否则为 ∅(无解)。

Recursion递归

For each coin C i , we build a new wallet W i = W ∖ { C i }, that is we extract that coin from the original wallet W and solve the problem P( W i , A ) ie, we try to find if there's a better selection of coins from the smaller wallet.对于每个硬币C i ,我们构建一个新的钱包W i = W ∖ { C i },也就是说我们从原始钱包W中提取该硬币并解决问题 P( Wi , A ) 即,我们试图找到是否较小的钱包中有更好的硬币选择。

The solution of P( W , A ) is the best one from the start solution and the n partial wallet solutions. P( W , A ) 的解是起始解和n 个部分钱包解中最好的一个。

Obviously, a selection is better if the sum of its coin values minus A is smaller (but positive).显然,如果硬币值减去A的总和较小(但为正),则选择更好。

Bottom of recursion递归底部

We can directly solve a problem (that is without dividing it) in three cases:我们可以在三种情况下直接解决一个问题(即不划分):

  • the sum of the coin values is smaller than the total amount, in which case we return a non-solution硬币价值的总和小于总量,在这种情况下我们返回一个非解决方案
  • the sum of the coin values is equal to the total amount, in which case we found a solution to the final problem硬币价值的总和等于总量,在这种情况下我们找到了最终问题的解决方案
  • there is only one coin left: if its value is larger than the amount we return it as the selection, otherwise we return a non-solution.只剩下一个硬币:如果它的价值大于我们返回它作为选择的数量,否则我们返回一个非解决方案。

Memoization记忆化

Memoization is essential to reduce the run-time for this type of algorithms.记忆化对于减少此类算法的运行时间至关重要。 It stores the results of previous evaluations of the main function, in order to avoid recomputing the same cases, very advantageous since the amount of overlaps close to the bottom of recursion is huge.它存储主要 function 的先前评估结果,以避免重新计算相同的情况,非常有利,因为接近递归底部的重叠量很大。 I used memoization is a brute-force alternative to a dynamic programming solution, that would make memoization unnecessary (or implicit).我使用 memoization 是动态编程解决方案的强力替代方案,这将使 memoization 变得不必要(或隐含)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM