Duh, me siento estúpido ahora mismo. A continuación hay una solución demasiado complicada, que preservaré porque es una solución, después de todo. Una solución simple sería esta:
// Generate a pretty string
val coinNames = List(("quarter", "quarters"),
("dime", "dimes"),
("nickel", "nickels"),
("penny", "pennies"))
def coinsString =
Function.tupled((quarters: Int, dimes: Int, nickels:Int, pennies: Int) => (
List(quarters, dimes, nickels, pennies)
zip coinNames // join with names
map (t => (if (t._1 != 1) (t._1, t._2._2) else (t._1, t._2._1))) // correct for number
map (t => t._1 + " " + t._2) // qty name
mkString " "
))
def allCombinations(amount: Int) =
(for{quarters <- 0 to (amount / 25)
dimes <- 0 to ((amount - 25*quarters) / 10)
nickels <- 0 to ((amount - 25*quarters - 10*dimes) / 5)
} yield (quarters, dimes, nickels, amount - 25*quarters - 10*dimes - 5*nickels)
) map coinsString mkString "\n"
Aquí está la otra solución. Esta solución se basa en la observación de que cada moneda es un múltiplo de las demás, por lo que se pueden representar en términos de ellas.
// Just to make things a bit more readable, as these routines will access
// arrays a lot
val coinValues = List(25, 10, 5, 1)
val coinNames = List(("quarter", "quarters"),
("dime", "dimes"),
("nickel", "nickels"),
("penny", "pennies"))
val List(quarter, dime, nickel, penny) = coinValues.indices.toList
// Find the combination that uses the least amount of coins
def leastCoins(amount: Int): Array[Int] =
((List(amount) /: coinValues) {(list, coinValue) =>
val currentAmount = list.head
val numberOfCoins = currentAmount / coinValue
val remainingAmount = currentAmount % coinValue
remainingAmount :: numberOfCoins :: list.tail
}).tail.reverse.toArray
// Helper function. Adjust a certain amount of coins by
// adding or subtracting coins of each type; this could
// be made to receive a list of adjustments, but for so
// few types of coins, it's not worth it.
def adjust(base: Array[Int],
quarters: Int,
dimes: Int,
nickels: Int,
pennies: Int): Array[Int] =
Array(base(quarter) + quarters,
base(dime) + dimes,
base(nickel) + nickels,
base(penny) + pennies)
// We decrease the amount of quarters by one this way
def decreaseQuarter(base: Array[Int]): Array[Int] =
adjust(base, -1, +2, +1, 0)
// Dimes are decreased this way
def decreaseDime(base: Array[Int]): Array[Int] =
adjust(base, 0, -1, +2, 0)
// And here is how we decrease Nickels
def decreaseNickel(base: Array[Int]): Array[Int] =
adjust(base, 0, 0, -1, +5)
// This will help us find the proper decrease function
val decrease = Map(quarter -> decreaseQuarter _,
dime -> decreaseDime _,
nickel -> decreaseNickel _)
// Given a base amount of coins of each type, and the type of coin,
// we'll produce a list of coin amounts for each quantity of that particular
// coin type, up to the "base" amount
def coinSpan(base: Array[Int], whichCoin: Int) =
(List(base) /: (0 until base(whichCoin)).toList) { (list, _) =>
decrease(whichCoin)(list.head) :: list
}
// Generate a pretty string
def coinsString(base: Array[Int]) = (
base
zip coinNames // join with names
map (t => (if (t._1 != 1) (t._1, t._2._2) else (t._1, t._2._1))) // correct for number
map (t => t._1 + " " + t._2)
mkString " "
)
// So, get a base amount, compute a list for all quarters variations of that base,
// then, for each combination, compute all variations of dimes, and then repeat
// for all variations of nickels.
def allCombinations(amount: Int) = {
val base = leastCoins(amount)
val allQuarters = coinSpan(base, quarter)
val allDimes = allQuarters flatMap (base => coinSpan(base, dime))
val allNickels = allDimes flatMap (base => coinSpan(base, nickel))
allNickels map coinsString mkString "\n"
}
Entonces, para 37 monedas, por ejemplo:
scala> println(allCombinations(37))
0 quarter 0 dimes 0 nickels 37 pennies
0 quarter 0 dimes 1 nickel 32 pennies
0 quarter 0 dimes 2 nickels 27 pennies
0 quarter 0 dimes 3 nickels 22 pennies
0 quarter 0 dimes 4 nickels 17 pennies
0 quarter 0 dimes 5 nickels 12 pennies
0 quarter 0 dimes 6 nickels 7 pennies
0 quarter 0 dimes 7 nickels 2 pennies
0 quarter 1 dime 0 nickels 27 pennies
0 quarter 1 dime 1 nickel 22 pennies
0 quarter 1 dime 2 nickels 17 pennies
0 quarter 1 dime 3 nickels 12 pennies
0 quarter 1 dime 4 nickels 7 pennies
0 quarter 1 dime 5 nickels 2 pennies
0 quarter 2 dimes 0 nickels 17 pennies
0 quarter 2 dimes 1 nickel 12 pennies
0 quarter 2 dimes 2 nickels 7 pennies
0 quarter 2 dimes 3 nickels 2 pennies
0 quarter 3 dimes 0 nickels 7 pennies
0 quarter 3 dimes 1 nickel 2 pennies
1 quarter 0 dimes 0 nickels 12 pennies
1 quarter 0 dimes 1 nickel 7 pennies
1 quarter 0 dimes 2 nickels 2 pennies
1 quarter 1 dime 0 nickels 2 pennies