
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 Prag Prog topics

As per the title, thanks.
New

page 37
ANTLRInputStream input = new ANTLRInputStream(is);
as of ANTLR 4 .8 should be:
CharStream stream = CharStreams.fromStream(i...
New

Hi everyone!
There is an error on the page 71 in the book “Programming machine learning from coding to depp learning” P. Perrotta. You c...
New

Title: Hands-On Rust (Chap 8 (Adding a Heads Up Display)
It looks like
.with_simple_console_no_bg(SCREEN_WIDTH*2, SCREEN_HEIGHT*2...
New

The book has the same “Problem space/Solution space” diagram on page 18 as is on page 17. The correct Problem/Solution space diagrams ar...
New

Title: Agile Web Development with Rails 7: (page 70)
I am running windows 11 pro with rails 7.0.3 and ruby 3.1.2p20 (2022-04-12 revision...
New

When running the program in chapter 8, “Implementing Combat”, the printout Health before attack was never printed so I assumed something ...
New

@mfazio23
I’m following the indications of the book and arriver ad chapter 10, but the app cannot be compiled due to an error in the Bas...
New

@parrt
In the context of Chapter 4.3, the grammar Java.g4, meant to parse Java 6 compilation units, no longer passes ANTLR (currently 4....
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

New

Small essay with thoughts on macOS vs. Linux:
I know @Exadra37 is just waiting around the corner to scream at me “I TOLD YOU SO!!!” but I...
New

I am asking for any distro that only has the bare-bones to be able to get a shell in the server and then just install the packages as we ...
New

Oh just spent so much time on this to discover now that RancherOS is in end of life but Rancher is refusing to mark the Github repo as su...
New

API 4
Path:
/user/following/
Method:
GET
Description:
Returns the list of all names of people whom the user follows
Response
[
{ ...
New

The File System Access API with Origin Private File System.
WebKit supports new API that makes it possible for web apps to create, open,...
New

Author Spotlight
Rebecca Skinner
@RebeccaSkinner
Welcome to our latest author spotlight, where we sit down with Rebecca Skinner, auth...
New

Author Spotlight:
Sophie DeBenedetto
@SophieDeBenedetto
The days of the traditional request-response web application are long gone, b...
New

zig/http.zig at 7cf2cbb33ef34c1d211135f56d30fe23b6cacd42 · ziglang/zig.
General-purpose programming language and toolchain for maintaini...
New

Author Spotlight:
Bruce Tate
@redrapids
Programming languages always emerge out of need, and if that’s not always true, they’re defin...
New
Latest in PragProg
Latest (all)
Categories:
Popular Portals
- /elixir
- /rust
- /wasm
- /ruby
- /erlang
- /phoenix
- /keyboards
- /js
- /rails
- /python
- /security
- /go
- /swift
- /vim
- /clojure
- /java
- /haskell
- /emacs
- /svelte
- /onivim
- /typescript
- /crystal
- /c-plus-plus
- /tailwind
- /kotlin
- /gleam
- /react
- /flutter
- /elm
- /ocaml
- /vscode
- /opensuse
- /ash
- /centos
- /php
- /deepseek
- /scala
- /zig
- /html
- /debian
- /nixos
- /lisp
- /agda
- /textmate
- /sublime-text
- /react-native
- /kubuntu
- /arch-linux
- /revery
- /ubuntu
- /manjaro
- /django
- /spring
- /diversity
- /nodejs
- /lua
- /c
- /slackware
- /julia
- /neovim