[英]Convert imperative to functional style using ramda.js
我正在编写使用命令式样式将数字数组转换为新数据列表的代码,但我想使用ramdajs之类的JavaScript库将其转换为函数样式
代码背景假设美元价值总共有5个硬币,分别是25美元,20美元,... 1美元。 我们将不得不用钱兑换美元硬币。 用最少的硬币
const data = [25, 20, 10, 5, 1];
const fn = n => data.map((v) => {
const numberOfCoin = Number.parseInt(n / v, 10);
const result = [v, numberOfCoin];
n %= v;
return numberOfCoin ? result : [];
}).filter(i => i.length > 0);
该代码的结果应为
fn(48) => [[25, 1], [20, 1], [1, 3]]
fn(100) => [[25, 4]]
我认为您已经有了一个不错的开始,但是为了使它更具功能性,我需要进行一些更改:
return
) n %= v
) 您不必为此需要Ramda:
const coins = value => [25, 20, 10, 5, 1].reduce(([acc, val], cur) => val < cur ? [acc, val] : [[...acc, [cur, Math.floor(val / cur)]], val % cur], [[], value] )[0]; console.log(coins(48)); console.log(coins(100));
如果发现自己使用map
然后进行filter
,则很可能需要reduce
。 在上面的函数coins
,迭代器返回一个数组,该数组包含钱币对和钱币数量以及每个步骤的折减值的数组。
请注意,在每个步骤中,我都使用解构分配来捕获对的数组以及各个参数中的减小值。
现在,当然也可以使用Ramda了:
const {compose, filter, last, mapAccum, flip} = R; const mapIterator = (a, b) => [a % b, [b, Math.floor(a / b)]]; const withCoins = ([coins, number]) => number > 0; const coins = compose(filter(withCoins), last, flip(mapAccum(mapIterator))([25, 20, 10, 5, 1])); console.log(coins(48)); console.log(coins(100));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
编辑 :正如Scott正确指出的那样,我上面的任何解决方案都将为您带来最少的更改。
事实证明,这比我预期的要复杂得多,我确定了一个可以肯定的解决方案:
我定义了5套硬币:
我计算每组产生多少变化,并仅保留产生最少变化的变化。
例如更改30
:
const {compose, pipe, sum, map, last, head, mapAccum, curry, flip, applyTo, sortBy, reject, not} = R; const numCoins = compose(sum, map(last)); const changeFn = curry((coins, num) => mapAccum((cur, coin) => [cur % coin, [coin, Math.floor(cur / coin)]], num, coins)[1]); const change1 = changeFn([1]); const change2 = changeFn([5, 1]); const change3 = changeFn([10, 5, 1]); const change4 = changeFn([20, 10, 5, 1]); const change5 = changeFn([25, 20, 10, 5, 1]); const change = pipe( applyTo, flip(map)([ change1, change2, change3, change4, change5]), sortBy(numCoins), head, reject(compose(not, last))); console.log(change(30)); console.log(change(40)); console.log(change(48));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
user633183的答案的这种变体将找到最小数量的硬币,它没有使用选择每个较大面额的最大数量的通用技术,而这可能会牺牲总体上选择更多硬币的可能性。
请注意,与原始答案或来自customcommander的初始答案相比,这可能涉及更多的计算。
这里的change
返回硬币值的列表,因此对于58
,它将返回[25, 25, 5, 1, 1, 1]
。 makeChange
将其转换为[ [25, 2], [5, 1], [1, 3] ]
makeChange
[ [25, 2], [5, 1], [1, 3] ]
格式。 如果将minLength
函数从<=
更改为<
,则将生成[ [25, 1], [20, 1], [10, 1], [1, 3] ]
。 这是相同数量的硬币,但使用不同的面额。
如果退货顺序对您来说无关紧要,您也可以删除sort
行。
这里的样式混合有点不幸。 我们可以更换的Ramda管道版本makeChange
一个更像change
,如果我们的努力。 但是我认为在拉姆达 这是最容易想到的。 用Ramda管道替换change
并不容易,因为以这种方式进行递归比较困难。
感谢 customcommander指出此答案的早期版本中的一个缺陷。
const minLength = (as, bs) => as.length <= bs.length ? as : bs const change = ([ c, ...rest ], amount = 0) => amount === 0 ? [] : c === 1 ? Array(amount).fill(1) : c <= amount ? minLength ( [ c, ...change ([c, ...rest], amount - c)], change (rest, amount) ) : change (rest, amount) const makeChange = pipe( change, countBy(identity), toPairs, map(map(Number)), sort(descend(head)) // if desired ) const coins = [ 25, 20, 10, 5, 1 ] console.log (makeChange (coins, 40)) //=> [ [ 20, 2 ] ] console.log (makeChange (coins, 45)) //=> [ [ 25, 1 ], [ 20, 1 ] ] console.log (change (coins, 48)) //=> [ [ 25, 1 ], [ 20, 1 ], [ 1, 3 ] ] console.log (makeChange (coins, 100)) //=> [ [ 25, 4 ] ]
<script src = "https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script> <script> const { pipe, countBy, identity, toPairs, map, sort, descend, head } = R </script>
人们通常会伸手去拿map
, filter
和reduce
但结果通常是在圆孔中有点方形钉。
map
没有意义,因为它会产生一对一的结果; 如果我有4种硬币,我将总是收到4种找零,这当然不是我们想要的。 使用filter
迫使您进行更多处理以获得所需的结果。 reduce
可以消除由map
+ filter
引起的中间值,但是同样, 在分析每个硬币之前 ,我们有可能达到期望的结果。 在的示例fn(100)
其返回[ [25, 4] ]
就没有必要对连看硬币20
, 10
, 5
,或1
因为结果已经达到; 进一步减少将是浪费。 对我来说,函数式编程是为了方便。 如果我没有满足我需要的功能,那么我就简单地做到了,因为重要的是我的程序必须清楚地传达其意图。 有时,这意味着使用更适合我正在处理的数据的构造-
const change = (coins = [], amount = 0) =>
loop // begin a loop, initializing:
( ( acc = [] // an empty accumulator, acc
, r = amount // the remaining amount to make change for, r
, [ c, ...rest ] = coins // the first coin, c, and the rest of coins
) =>
r === 0 // if the remainder is zero
? acc // return the accumulator
: c <= r // if the coin is small enough
? recur // recur with
( [ ...acc, [ c, div (r, c) ] ] // updated acc
, mod (r, c) // updated remainder
, rest // rest of coins
)
// otherwise (inductive) coin is too large
: recur // recur with
( acc // unmodified acc
, r // unmodified remainder
, rest // rest of coins
)
)
与map
, filter
和reduce
,我们的解决方案在确定结果之后将不会继续对输入进行迭代。 使用它看起来像这样-
const coins =
[ 25, 20, 10, 5, 1 ]
console.log (change (coins, 48))
// [ [ 25, 1 ], [ 20, 1 ], [ 1, 3 ] ]
console.log (change (coins, 100))
// [ [ 25, 4 ] ]
在下面的浏览器中验证结果-
const div = (x, y) => Math .round (x / y) const mod = (x, y) => x % y const recur = (...values) => ({ recur, values }) const loop = f => { let acc = f () while (acc && acc.recur === recur) acc = f (...acc.values) return acc } const change = (coins = [], amount = 0) => loop ( ( acc = [] , r = amount , [ c, ...rest ] = coins ) => r === 0 ? acc : c <= r ? recur ( [ ...acc, [ c, div (r, c) ] ] , mod (r, c) , rest ) : recur ( acc , r , rest ) ) const coins = [ 25, 20, 10, 5, 1 ] console.log (change (coins, 48)) // [ [ 25, 1 ], [ 20, 1 ], [ 1, 3 ] ] console.log (change (coins, 100)) // [ [ 25, 4 ] ]
R.until
用户可以使用R.until
,但由于它是纯函数驱动的界面,因此可读性受到影响。 IMO非常喜欢loop
和recur
灵活性-
const change = (coins = [], amount = 0) =>
R.until
( ([ acc, r, coins ]) => r === 0
, ([ acc, r, [ c, ...rest ] ]) =>
c <= r
? [ [ ...acc
, [ c, Math.floor (R.divide (r, c)) ]
]
, R.modulo (r, c)
, rest
]
: [ acc
, r
, rest
]
, [ [], amount, coins ]
)
[ 0 ]
另一种选择是将其编写为递归函数-
const div = (x, y) => Math .round (x / y) const mod = (x, y) => x % y const change = ([ c, ...rest ], amount = 0) => amount === 0 ? [] : c <= amount ? [ [ c, div (amount, c) ] , ...change (rest, mod (amount, c)) ] : change (rest, amount) const coins = [ 25, 20, 10, 5, 1 ] console.log (change (coins, 48)) // [ [ 25, 1 ], [ 20, 1 ], [ 1, 3 ] ] console.log (change (coins, 100)) // [ [ 25, 4 ] ]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.