人们通常会伸手去拿map、filter 和reduce,但结果通常是圆孔中的方钉。
-
map 没有意义,因为它会产生一对一的结果;如果我有 4 种硬币,我总是会收到 4 种找零,这当然不是我们想要的。使用filter 会强制您进行更多处理以达到所需的结果。
-
reduce 可以消除由map+filter 引起的中间值,但同样,我们有可能在必须分析每个硬币之前达到预期的结果。在返回[ [25, 4] ] 的fn(100) 的示例中,甚至不需要查看硬币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 ] ]
Ramda 用户可以使用R.until,但由于它是纯函数驱动的界面,可读性会受到影响。 loop 和 recur 的灵活性是非常有利的,imo -
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 ] ]