dtonhofer

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);
    }

}

Where Next?

Popular Pragmatic Bookshelf topics Top

jon
Some minor things in the paper edition that says “3 2020” on the title page verso, not mentioned in the book’s errata online: p. 186 But...
New
johnp
Running the examples in chapter 5 c under pytest 5.4.1 causes an AttributeError: ‘module’ object has no attribute ‘config’. In particula...
New
belgoros
Following the steps described in Chapter 6 of the book, I’m stuck with running the migration as described on page 84: bundle exec sequel...
New
rmurray10127
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
brian-m-ops
#book-python-testing-with-pytest-second-edition Hi. Thanks for writing the book. I am just learning so this might just of been an issue ...
New
patoncrispy
I’m new to Rust and am using this book to learn more as well as to feed my interest in game dev. I’ve just finished the flappy dragon exa...
New
jskubick
I’m under the impression that when the reader gets to page 136 (“View Data with the Database Inspector”), the code SHOULD be able to buil...
New
tkhobbes
After some hassle, I was able to finally run bin/setup, now I have started the rails server but I get this error message right when I vis...
New
dtonhofer
@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
SlowburnAZ
Getting an error when installing the dependencies at the start of this chapter: could not compile dependency :exla, "mix compile" failed...
New

Other popular topics Top

Devtalk
Reading something? Working on something? Planning something? Changing jobs even!? If you’re up for sharing, please let us know what you’...
1045 20596 392
New
brentjanderson
Bought the Moonlander mechanical keyboard. Cherry Brown MX switches. Arms and wrists have been hurting enough that it’s time I did someth...
New
AstonJ
Curious to know which languages and frameworks you’re all thinking about learning next :upside_down_face: Perhaps if there’s enough peop...
New
AstonJ
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
AstonJ
Thanks to @foxtrottwist’s and @Tomas’s posts in this thread: Poll: Which code editor do you use? I bought Onivim! :nerd_face: https://on...
New
dimitarvp
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
AstonJ
Saw this on TikTok of all places! :lol: Anyone heard of them before? Lite:
New
New
RobertRichards
Hair Salon Games for Girls Fun Girls Hair Saloon game is mainly developed for kids. This game allows users to select virtual avatars to ...
New
PragmaticBookshelf
A concise guide to MySQL 9 database administration, covering fundamental concepts, techniques, and best practices. Neil Smyth MySQL...
New

Latest in Functional Programming in Java, Second Edition

Functional Programming in Java, Second Edition Portal

Sub Categories: