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
                        
                      
                      
                As per the title, thanks.
              
            
            
          
              New
                Title: Web Development with Clojure, Third Edition, pg 116 
Hi - I just started chapter 5 and I am stuck on page 116 while trying to star...
              
            
            
          
              New
                The following is cross-posted from the original Ray Tracer Challenge forum, from a post by garfieldnate. I’m cross-posting it so that the...
              
            
            
          
              New
                Title: Intuitive Python: docker run… denied error (page 2) 
Attempted to run the docker command in both CLI and Powershell 
PS C:\Users\r...
              
            
            
          
              New
                Hi, I have just acquired Michael Fazio’s “Kotlin and Android Development” to learn about game programming for Android. I have a game in p...
              
            
            
          
              New
                I found an issue in Chapter 7 regarding android:backgroundTint vs app:backgroundTint. 
How to replicate: 
load chapter-7 from zipfile i...
              
            
            
          
              New
                Title: Build a Weather Station with Elixir and Nerves: Problem connecting to Postgres with Grafana on  (page 64) 
If you follow the defau...
              
            
            
          
              New
                The allprojects block listed on page 245 produces the following error when syncing gradle: 
“org.gradle.api.GradleScriptException: A prob...
              
            
            
          
              New
                When running the program in chapter 8, “Implementing Combat”, the printout Health before attack was never printed so I assumed something ...
              
            
            
          
              New
                From page 13: 
On Python 3.7, you can install the libraries with pip by running these commands inside a Python venv  using Visual Studio ...
              
            
            
          
              New
Other popular topics
                        
                      
                      
                Bought the Moonlander mechanical keyboard. Cherry Brown MX switches. Arms and wrists have been hurting enough that it’s time I did someth...
              
            
            
          
              New
                Biggest jackpot ever apparently! :upside_down_face: 
I don’t (usually) gamble/play the lottery, but working on a program to predict the...
              
            
            
          
              New
                Hi folks, 
I don’t know if I saw this here but, here’s a new programming language, called Roc 
Reminds me a bit of Elm and thus Haskell. ...
              
            
            
          
              New
                Author Spotlight 
Jamis Buck 
@jamis 
This month, we have the pleasure of spotlighting author Jamis Buck, who has written Mazes for Prog...
              
            
            
          
              New
                Inside our android webview app, we are trying to paste the copied content from another app eg (notes) using navigator.clipboard.readtext ...
              
            
            
          
              New
                Author Spotlight: 
VM Brasseur 
@vmbrasseur 
We have a treat for you today! We turn the spotlight onto Open Source as we sit down with V...
              
            
            
          
              New
                I have always used antique keyboards like Cherry MX 1800 or Cherry MX 8100 and almost always have modified the switches in some way, like...
              
            
            
          
              New
                zig/http.zig at 7cf2cbb33ef34c1d211135f56d30fe23b6cacd42 · ziglang/zig. 
General-purpose programming language and toolchain for maintaini...
              
            
            
              
          
              New
                Big O Notation can make your code faster by orders of magnitude. Get the hands-on info you need to master data structures and algorithms ...
              
            
            
              
          
              New
                This is cool! 
DEEPSEEK-V3 ON M4 MAC: BLAZING FAST INFERENCE ON APPLE SILICON
We just witnessed something incredible: the largest open-s...
              
            
            
          
              New
Categories:
Sub Categories:
Popular Portals
- /elixir
 - /rust
 - /ruby
 - /wasm
 - /erlang
 - /phoenix
 - /keyboards
 - /python
 - /rails
 - /js
 - /security
 - /go
 - /swift
 - /vim
 - /clojure
 - /emacs
 - /haskell
 - /java
 - /svelte
 - /onivim
 - /typescript
 - /kotlin
 - /c-plus-plus
 - /crystal
 - /tailwind
 - /react
 - /gleam
 - /ocaml
 - /elm
 - /flutter
 - /vscode
 - /ash
 - /html
 - /opensuse
 - /centos
 - /php
 - /zig
 - /deepseek
 - /scala
 - /lisp
 - /sublime-text
 - /textmate
 - /react-native
 - /nixos
 - /debian
 - /agda
 - /kubuntu
 - /arch-linux
 - /django
 - /deno
 - /ubuntu
 - /revery
 - /manjaro
 - /nodejs
 - /spring
 - /diversity
 - /lua
 - /julia
 - /slackware
 - /c
 
    





