我正在编写使用命令式将数字数组转换为新数据列表的代码,但我想使用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]]
答案 0 :(得分:2)
我认为您已经有了一个不错的开端,但是为了使它更具功能性,我需要进行一些更改:
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>
答案 1 :(得分:2)
user633183给出的答案的这种变体将找到最小数量的硬币,它没有使用通常的技术来选择每个较大面额的最大数量,而可能以牺牲总体上选择更多硬币为代价。
请注意,与原始答案或来自customcommander的初始答案相比,这可能涉及更多的计算。
change
在这里返回硬币值的列表,因此对于58
,它将返回[25, 25, 5, 1, 1, 1]
。 makeChange
将其转换为[ [25, 2], [5, 1], [1, 3] ]
格式。如果将minLength
函数从<=
更改为<
,则将生成[ [25, 1], [20, 1], [10, 1], [1, 3] ]
。这是相同数量的硬币,但是使用不同的面额。
如果退货顺序对您来说无关紧要,您也可以删除sort
行。
这里的样式混合有点不幸。如果我们尝试过的话,可以用makeChange
之类的其他change
替代Ramda管道版本。但是我认为在拉姆达这是最容易想到的。用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>
答案 2 :(得分:1)
人们通常会碰到map
,filter
和reduce
,但通常结果是在圆孔中有点方形钉。
map
毫无意义,因为它会产生一对一的结果。如果我有4种硬币,我将总是收到4种找零,这当然不是我们想要的。使用filter
会迫使您进行更多处理以获得所需的结果。reduce
可以消除由map
+ filter
引起的中间值,但是同样,我们有可能在必须分析每个硬币之前达到期望的结果。在返回fn(100)
的{{1}}的示例中,甚至不需要查看硬币[ [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 ] ]
Ramda用户可以使用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
和loop
的灵活性非常有利,imo-
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 ]