/// Generic Memoisation with HashMap cache.
///
/// Transforms overlapping-subproblem recursion from exponential to polynomial.
/// Fibonacci, coin change, edit distance.
use std::collections::HashMap;
/// Fibonacci with manual HashMap memoisation.
fn fib(n: u64, cache: &mut HashMap<u64, u64>) -> u64 {
if n <= 1 { return n; }
if let Some(&v) = cache.get(&n) { return v; }
let v = fib(n - 1, cache) + fib(n - 2, cache);
cache.insert(n, v);
v
}
/// Coin change: minimum number of coins to make `amount`.
fn coin_change(coins: &[u64], amount: u64) -> Option<u64> {
let mut cache: HashMap<u64, Option<u64>> = HashMap::new();
coin_change_rec(coins, amount, &mut cache)
}
fn coin_change_rec(coins: &[u64], amount: u64, cache: &mut HashMap<u64, Option<u64>>) -> Option<u64> {
if amount == 0 { return Some(0); }
if let Some(&v) = cache.get(&amount) { return v; }
let result = coins.iter()
.filter(|&&c| c <= amount)
.filter_map(|&c| coin_change_rec(coins, amount - c, cache).map(|n| n + 1))
.min();
cache.insert(amount, result);
result
}
/// Edit distance (Levenshtein) with memoisation.
fn edit_distance(s: &[u8], t: &[u8]) -> usize {
let mut cache: HashMap<(usize, usize), usize> = HashMap::new();
edit_rec(s, t, 0, 0, &mut cache)
}
fn edit_rec(
s: &[u8], t: &[u8],
i: usize, j: usize,
cache: &mut HashMap<(usize, usize), usize>,
) -> usize {
if i == s.len() { return t.len() - j; }
if j == t.len() { return s.len() - i; }
if let Some(&v) = cache.get(&(i, j)) { return v; }
let v = if s[i] == t[j] {
edit_rec(s, t, i + 1, j + 1, cache)
} else {
1 + edit_rec(s, t, i + 1, j, cache) // delete
.min(edit_rec(s, t, i, j + 1, cache)) // insert
.min(edit_rec(s, t, i + 1, j + 1, cache)) // replace
};
cache.insert((i, j), v);
v
}
/// Longest Common Subsequence with memoisation.
fn lcs(s: &[u8], t: &[u8]) -> usize {
let mut cache: HashMap<(usize, usize), usize> = HashMap::new();
lcs_rec(s, t, 0, 0, &mut cache)
}
fn lcs_rec(s: &[u8], t: &[u8], i: usize, j: usize, cache: &mut HashMap<(usize, usize), usize>) -> usize {
if i == s.len() || j == t.len() { return 0; }
if let Some(&v) = cache.get(&(i, j)) { return v; }
let v = if s[i] == t[j] {
1 + lcs_rec(s, t, i + 1, j + 1, cache)
} else {
lcs_rec(s, t, i + 1, j, cache).max(lcs_rec(s, t, i, j + 1, cache))
};
cache.insert((i, j), v);
v
}
fn main() {
let mut cache = HashMap::new();
println!("Fibonacci (memoised):");
for n in 0..=15u64 {
print!("fib({n})={} ", fib(n, &mut cache));
}
println!();
println!("\nCoin change:");
println!(" coins=[1,5,10,25], amount=41: {:?}", coin_change(&[1, 5, 10, 25], 41));
println!(" coins=[2], amount=3: {:?}", coin_change(&[2], 3));
println!(" coins=[1,3,4], amount=6: {:?}", coin_change(&[1, 3, 4], 6));
println!("\nEdit distance:");
println!(" 'kitten' vs 'sitting': {}", edit_distance(b"kitten", b"sitting")); // 3
println!(" 'abc' vs 'abc': {}", edit_distance(b"abc", b"abc")); // 0
println!(" '' vs 'abc': {}", edit_distance(b"", b"abc")); // 3
println!("\nLCS:");
println!(" 'ABCBDAB' vs 'BDCAB': {}", lcs(b"ABCBDAB", b"BDCAB")); // 4
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fib() {
let mut cache = HashMap::new();
assert_eq!(fib(0, &mut cache), 0);
assert_eq!(fib(1, &mut cache), 1);
assert_eq!(fib(10, &mut cache), 55);
assert_eq!(fib(20, &mut cache), 6765);
}
#[test]
fn test_coin_change_basic() {
assert_eq!(coin_change(&[1, 5, 10, 25], 41), Some(4)); // 25+10+5+1
assert_eq!(coin_change(&[1, 3, 4], 6), Some(2)); // 3+3
assert_eq!(coin_change(&[2], 3), None); // impossible
}
#[test]
fn test_coin_change_zero() {
assert_eq!(coin_change(&[1, 5], 0), Some(0));
}
#[test]
fn test_edit_distance() {
assert_eq!(edit_distance(b"kitten", b"sitting"), 3);
assert_eq!(edit_distance(b"", b""), 0);
assert_eq!(edit_distance(b"abc", b"abc"), 0);
assert_eq!(edit_distance(b"", b"abc"), 3);
assert_eq!(edit_distance(b"abc", b""), 3);
assert_eq!(edit_distance(b"a", b"b"), 1);
}
#[test]
fn test_lcs() {
assert_eq!(lcs(b"ABCBDAB", b"BDCAB"), 4);
assert_eq!(lcs(b"abc", b"abc"), 3);
assert_eq!(lcs(b"abc", b"def"), 0);
}
}
(* Generic Memoisation in OCaml *)
(* Generic memoize: wrap any function f with a hash-table cache *)
let memoize (f : ('a, 'b) Hashtbl.t -> 'a -> 'b) : ('a -> 'b) =
let cache = Hashtbl.create 64 in
let rec go arg =
match Hashtbl.find_opt cache arg with
| Some v -> v
| None ->
let v = f cache arg in
Hashtbl.add cache arg v;
v
in
ignore go; (* go is the memoised version, but we need to close over it *)
(* Better pattern: pass cache explicitly *)
let cache2 = Hashtbl.create 64 in
fun arg ->
match Hashtbl.find_opt cache2 arg with
| Some v -> v
| None ->
let v = f cache2 arg in
Hashtbl.add cache2 arg v;
v
(* Fibonacci with memoisation โ the classic example *)
let fib_memo : int -> int =
let cache = Hashtbl.create 64 in
let rec fib n =
if n <= 1 then n
else
match Hashtbl.find_opt cache n with
| Some v -> v
| None ->
let v = fib (n - 1) + fib (n - 2) in
Hashtbl.add cache n v;
v
in
fib
(* Coin change: minimum coins to make amount *)
let coin_change (coins : int list) (amount : int) : int option =
let cache = Hashtbl.create 128 in
let inf = max_int / 2 in
let rec go amt =
if amt = 0 then 0
else if amt < 0 then inf
else
match Hashtbl.find_opt cache amt with
| Some v -> v
| None ->
let best = List.fold_left (fun best c ->
min best (1 + go (amt - c))
) inf coins in
Hashtbl.add cache amt best;
best
in
let result = go amount in
if result >= inf then None else Some result
(* Edit distance with memoisation *)
let edit_distance (s : string) (t : string) : int =
let m = String.length s and n = String.length t in
let cache = Hashtbl.create 256 in
let rec go i j =
if i = m then n - j
else if j = n then m - i
else
match Hashtbl.find_opt cache (i, j) with
| Some v -> v
| None ->
let v =
if s.[i] = t.[j] then go (i+1) (j+1)
else 1 + min (go (i+1) j) (min (go i (j+1)) (go (i+1) (j+1)))
in
Hashtbl.add cache (i, j) v;
v
in
go 0 0
let () =
for n = 0 to 15 do
Printf.printf "fib(%d) = %d\n" n (fib_memo n)
done;
Printf.printf "\ncoin_change([1,5,10,25], 41) = %s\n"
(match coin_change [1;5;10;25] 41 with Some v -> string_of_int v | None -> "None");
Printf.printf "coin_change([2], 3) = %s\n"
(match coin_change [2] 3 with Some v -> string_of_int v | None -> "None");
Printf.printf "\nedit_distance('kitten', 'sitting') = %d (expected 3)\n"
(edit_distance "kitten" "sitting");
Printf.printf "edit_distance('', 'abc') = %d\n" (edit_distance "" "abc")