Люди обычно достигают значений map
, filter
и reduce
, но часто результатом является небольшой квадратный колышек в круглой дыре.
map
не имеет смысла, поскольку дает результат 1 к 1;если у меня есть 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 ] ]
Пользователи 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 ] ]