dtonhofer
Functional Programming in Java, Second Edition: Chapter 8: Hard-to-understand "Memoizer" can be made easy-to-understand by adding an "intermediate step" explainer
I had real trouble understanding the “memoizer”, I suppose Java syntax does not help in thinking about what should be a one-liner in Lambda calculus.
But after a couple of hours of thinking, it occurred to me that the “memoizing” code is just the end result of four simple transformations of the non-memoized code.
Suggesting to extend the text to explain it that way.
Here they are, based on the book’s code with some renaming of methods and parameters to make them more meaningful (at least to me):
The code below does not come with runnable code, which I will post separately.
RodCuttingOptimizer.java
package chapter8.rodcutting.book;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.IntStream;
class RodCuttingOptimizer {
private final Map<Integer, Integer> pricingMap;
public RodCuttingOptimizer(final Map<Integer, Integer> pricingMap) {
this.pricingMap = Collections.unmodifiableMap(pricingMap);
}
// STEP 0:
// The initial solution as per the book.
public int maxProfitNaive(final int length) {
final int profitIfNotCut = pricingMap.getOrDefault(length, 0);
// dual recursive call!
final int maxProfitIfCut = IntStream.rangeClosed(1, length / 2)
.map(left -> maxProfitNaive(left) + maxProfitNaive(length - left))
.max()
.orElse(0); // if there is no value because the original IntStream is empty, use 0
return Math.max(profitIfNotCut, maxProfitIfCut);
}
// STEP 1:
// As above, but indirect, with the recursive descent in
// maxProfitIndirectInner() calling the function passed as argument #1.
// In this case, the topmost function.
// The call basically means "go do your work and call me with a smaller length on recursive descent"
public int maxProfitIndirect(final int length) {
return maxProfitIndirectInner(this::maxProfitIndirect, length);
}
// STEP 2:
// As above, but we do not want the *topmost* function to
// be called on recursive descent, but instead *another function* that we create locally.
public int maxProfitIndirectDetachedFromTop(final int length) {
final Function<Integer, Integer> shimFunction = new Function<>() {
public Integer apply(final Integer length2) {
// "this" is exactly the "shimFunction"
return maxProfitIndirectInner(this, length2);
}
};
// kickstart the recursive descent
return shimFunction.apply(length);
}
// STEP 2 WHICH WE CAN'T HAVE
// We cannot write the above like this in Java as there is no way to
// put anything into the $MYSELF$ hole, we would need a "Y Combinator" for that (I think)
/*
public int maxProfitDoublyIndirect2(final int length) {
Function<Integer, Integer> shimFunction = (Integer input) -> maxProfitIndirectInner($MYSELF$, length);
return shimFunction.apply(length);
}
*/
// STEP 3:
// As above, but now we are memoizing with a HashMap local to the "shimFunction".
// Note that if stream processing actually parallelizes its processing, we are
// in trouble as the access to the HasMap is not synchronized. So beware!
public int maxProfitIndirectMemoizing(final int length) {
final Function<Integer, Integer> shimFunction = new Function<>() {
private final Map<Integer, Integer> store = new HashMap<>();
public Integer apply(final Integer length2) {
if (!store.containsKey(length2)) {
int value = maxProfitIndirectInner(this, length2);
store.put(length2, value);
}
return store.get(length2);
}
};
// kickstart the recursive descent
return shimFunction.apply(length);
}
// STEP 4:
// As per the book, we can "factor out" the memoizing shim function into an (inner) class.
// In the book, this is called maxProfit().
private static class Memoizer {
public static <T, R> R memoize(final BiFunction<Function<T, R>, T, R> innerFunction, final T input) {
// An anonymous class implementing an interface!
// Containing a cache ("store") as a Map<T,R>
Function<T, R> memoizedFunction = new Function<>() {
private final Map<T, R> store = new HashMap<>();
public R apply(final T input) {
if (!store.containsKey(input)) {
store.put(input, innerFunction.apply(this, input));
}
return store.get(input);
}
};
return memoizedFunction.apply(input);
}
}
public int maxProfitIndirectMemoizingUsingMemoizer(final int length) {
// https://docs.oracle.com/javase/8/docs/api/java/util/function/BiFunction.html
// BiFunction<Function<Integer, Integer>, Integer, Integer> biFunction = this::maxProfitIndirectInner;
return Memoizer.memoize(this::maxProfitIndirectInner, length);
}
// The method that uses the "indirect" function.
//
// In the book, it is called "computeMaxProfit()"
// and "indirect" is called "memoizedFunction" (which is not entirely true as this is not
// properly the memoized function)
//
// "maxProfitIndirectInner" can be mapped to a java.util.function.BiFunction
// that maps the following types and roles:
//
// ( <Function<Integer, Integer> , Integer ) -> Integer
//
// ( [the "indirect function"] , [rod length] ) -> [max profit]
//
// In ML notation this would be simpler:
//
// ( Integer -> Integer ) -> Integer -> Integer
//
// This function is only "not static" in this example because its context (i.e. "this")
// contains the "pricingMap", which could also be passed as a separate parameter instead.
private int maxProfitIndirectInner(final Function<Integer, Integer> indirect, final int length) {
final int profitIfNotCut = pricingMap.getOrDefault(length, 0);
// dual recursive call!
final int maxProfitIfCut = IntStream.rangeClosed(1, length / 2)
.map(left -> indirect.apply(left) + indirect.apply(length - left))
.max()
.orElse(0); // if there is no value because the original IntStream is empty, use 0
return Math.max(profitIfNotCut, maxProfitIfCut);
}
}
Popular Pragmatic Bookshelf topics
This test is broken right out of the box…
— FAIL: TestAgent (7.82s)
agent_test.go:77:
Error Trace: agent_test.go:77
agent_test.go:...
New
Title: Hands-On Rust (Chapter 11: prefab)
Just played a couple of amulet-less games. With a bit of debugging, I believe that your can_p...
New
Hi! I know not the intentions behind this narrative when called, on page XI:
mount() |> handle_event() |> render()
but the correc...
New
On the page xv there is an instruction to run bin/setup from the main folder. I downloaded the source code today (12/03/21) and can’t see...
New
The generated iex result below should list products instead of product for the metadata. (page 67)
iex> product = %Product{}
%Pento....
New
I ran this command after installing the sample application:
$ cards add do something --owner Brian
And got a file not found error:
Fil...
New
I am using Android Studio Chipmunk | 2021.2.1 Patch 2
Build #AI-212.5712.43.2112.8815526, built on July 10, 2022
Runtime version: 11.0....
New
@mfazio23
Android Studio will not accept anything I do when trying to use the Transformations class, as described on pp. 140-141. Googl...
New
Is there any plan for volume 2? :slight_smile:
New
I’ve got to the end of Ch. 11, and the app runs, with all tabs displaying what they should – at first. After switching around between St...
New
Other popular topics
A thread that every forum needs!
Simply post a link to a track on YouTube (or SoundCloud or Vimeo amongst others!) on a separate line an...
New
Which, if any, games do you play? On what platform?
I just bought (and completed) Minecraft Dungeons for my Nintendo Switch. Other than ...
New
You might be thinking we should just ask who’s not using VSCode :joy: however there are some new additions in the space that might give V...
New
Just done a fresh install of macOS Big Sur and on installing Erlang I am getting:
asdf install erlang 23.1.2
Configure failed.
checking ...
New
Do the test and post your score :nerd_face:
:keyboard:
If possible, please add info such as the keyboard you’re using, the layout (Qw...
New
Create efficient, elegant software tests in pytest, Python's most powerful testing framework.
Brian Okken @brianokken
Edited by Kat...
New
zig/http.zig at 7cf2cbb33ef34c1d211135f56d30fe23b6cacd42 · ziglang/zig.
General-purpose programming language and toolchain for maintaini...
New
I’m able to do the “artistic” part of game-development; character designing/modeling, music, environment modeling, etc.
However, I don’t...
New
Hello,
I’m a beginner in Android development and I’m facing an issue with my project setup. In my build.gradle.kts file, I have the foll...
New
A concise guide to MySQL 9 database administration, covering fundamental concepts, techniques, and best practices.
Neil Smyth
MySQL...
New
Categories:
Sub Categories:
Popular Portals
- /elixir
- /rust
- /wasm
- /ruby
- /erlang
- /phoenix
- /keyboards
- /python
- /js
- /rails
- /security
- /go
- /swift
- /vim
- /clojure
- /emacs
- /haskell
- /java
- /svelte
- /onivim
- /typescript
- /kotlin
- /crystal
- /c-plus-plus
- /tailwind
- /react
- /gleam
- /ocaml
- /elm
- /flutter
- /vscode
- /ash
- /html
- /opensuse
- /zig
- /centos
- /deepseek
- /php
- /scala
- /react-native
- /lisp
- /sublime-text
- /textmate
- /nixos
- /debian
- /agda
- /django
- /deno
- /kubuntu
- /arch-linux
- /nodejs
- /revery
- /ubuntu
- /spring
- /manjaro
- /lua
- /diversity
- /julia
- /markdown
- /v








