dtonhofer

dtonhofer

Functional Programming in Java, Second Edition: p 148 "An optimization problem" & "Plain-Vanilla Recursion" problems

On page 148, “An optimization problem” we read:

We’ll employ a solution for a company that buys rods at wholesale and sells them at retail. They figured that by cutting the rods into different sizes, they could maximize profits. The price that the company can charge for different lengths of rod changes often, so the company wants us to write a program to reveal what the maximum profit would be for a given size of rod. 

The above is not computing the profit, but computing the revenue. The profit is revenue - expenses, but we don’t know the expenses, it might include manpower costs, machine costs etc.

The problem would also be more interesting if a rod of length 1 could only be sold at price 0 (i.e. it is wastage), at price 2 it’s too easy.

More seriously, on page 149, “Plain-Vanilla Recursion”, we read:

Continuing with this approach, we find that the maximum profit [revenue] for an arbitrary length n is the maximum of the profits [revenues] from each of the possible 2^(n-1) cuts length. That is, max(no cut, cut(1, n - 1), cut(2, n - 2), …), for a given length n.

It don’t understand the max() notation here, there should probably at least be revenue(.) of a cut schedule in there :thinking:

In any case, the 2^(n-1) is imprecise Not considering symmetries, each cut point at marginal width 1 of which there are n-1, for example for width = 6:

≣|≣|≣|≣|≣|≣

can be switched on or off, giving us indeed 2^(n-1) “cut schedules.”

But considering all symmetries (to collapse similar “cut schedules”, consider only “cut schedules” where the width of a cut is monotonically (but not strictly) increasing from left to right), the number of possible “cut schedules” for width = n is then given by

[A000041 - OEIS] - the number of partitions of n (the partition numbers)

(I didn’t find this by myself, I first wrote the program to list the schedules, then duckduckgoed the sequence)

For example for width = 6, there are only 11 distinct ways to cut:

Number of ways of cutting for width = 6: 11
≣≣≣≣≣≣
≣|≣≣≣≣≣
≣≣|≣≣≣≣
≣≣≣|≣≣≣
≣|≣|≣≣≣≣
≣|≣≣|≣≣≣
≣≣|≣≣|≣≣
≣|≣|≣|≣≣≣
≣|≣|≣≣|≣≣
≣|≣|≣|≣|≣≣
≣|≣|≣|≣|≣|≣

Only increasing slowly:

|Width|Schedules|2^(n-1)|
|---|---|---|
|1|1|1|
|2|2|2|
|3|3|4|
|4|5|8|
|5|7|16|
|6|11|32|
|7|15|64|
|8|22|128|
|9|30|256|
|10|42|512|
|11|56|1024|
|12|77|2048|
|13|101|4096|
|14|135|8192|
|15|176|16384|
|16|231|32768|
|17|297|65536|
|18|385|131072|
|19|490|262144|
|20|627|524288|

Code to compute the above (unabashedly recursive, not memoizing/caching, slows down quickly with larger n. The SortedSet could be replaced by an array and “insertion sorting” if one wants “efficiency”)

import org.junit.jupiter.api.Test;

import java.util.*;
import java.util.stream.IntStream;

import static java.util.stream.Collectors.joining;

class CutSchedule implements Comparable<CutSchedule> {

    public List<Integer> increasingWidths = new ArrayList<>();

    public boolean verify() {
        if (increasingWidths.isEmpty()) {
            return false;
        }
        if (increasingWidths.get(0) <= 0) {
            return false;
        }
        for (int i = 1; i < increasingWidths.size(); i++) {
            if (increasingWidths.get(i - 1) > increasingWidths.get(i)) {
                return false;
            }
        }
        return true;
    }

    private static String toRodString(int width, char ch) {
        StringBuilder buf = new StringBuilder();
        IntStream.range(0, width).forEach(i -> buf.append(ch));
        return buf.toString();
    }

    public String toString(boolean numeric) {
        if (numeric) {
            return increasingWidths.stream().map(width -> Integer.toString(width)).collect(joining(","));
        } else {
            return increasingWidths.stream().map(width -> toRodString(width, '≣')).collect(joining("|"));
        }
    }

    public String toString() {
        return toString(false);
    }

    public int totalWidth() {
        return increasingWidths.stream().mapToInt(width -> width).sum();
    }

    public int cutCount() {
        return increasingWidths.size() - 1;
    }

    @Override
    public boolean equals(Object o) {
        if (o == null || !(o instanceof CutSchedule)) {
            return false;
        }
        return this.compareTo((CutSchedule) o) == 0;
    }

    @Override
    public int compareTo(CutSchedule o) {
        assert o != null;
        int widthDelta = this.totalWidth() - o.totalWidth();
        if (widthDelta != 0) {
            // if total width is smaller, the CutSchedule is "smaller"
            return widthDelta;
        }
        int cutCountDelta = this.cutCount() - o.cutCount();
        if (cutCountDelta != 0) {
            // if cut count is smaller, the CutSchedule is "smaller"
            return cutCountDelta;
        }
        for (int i = 0; i < cutCount(); i++) {
            int deltaCutWidth = this.increasingWidths.get(i) - o.increasingWidths.get(i);
            if (deltaCutWidth != 0) {
                // the first having a smaller cut at position i is "smaller"
                return deltaCutWidth;
            }
        }
        return 0;
    }
}

public class RodCuttingOptimization {

    private static void extendToFullWidthAndCollect(final SortedSet<CutSchedule> csSetForSmallerWidth, final int width, final int firstCutWidth, final Set<CutSchedule> res) {
        for (CutSchedule subCs : csSetForSmallerWidth) {
            assert subCs.verify();
            assert subCs.totalWidth() == width - firstCutWidth;
            CutSchedule cs = new CutSchedule();
            cs.increasingWidths.add(firstCutWidth);
            cs.increasingWidths.addAll(subCs.increasingWidths);
            res.add(cs);
        }
    }

    private static SortedSet<CutSchedule> generateAllCutsSchedulesForGivenNumCutsAndWidth(final int numCuts, final int width, final int minCutWidth) {
        assert numCuts >= 0;
        assert width > 0;
        assert minCutWidth > 0;
        SortedSet<CutSchedule> res = new TreeSet<>();
        if (numCuts == 0) {
            CutSchedule cs = new CutSchedule();
            cs.increasingWidths.add(width);
            res.add(cs);
        } else {
            // Make the first cut at increasingly larger positions. It must be the smallest cut made!
            IntStream.rangeClosed(minCutWidth, width / 2).forEach(firstCutWidth -> {
                SortedSet<CutSchedule> csSetForSmallerWidth =
                        Collections.unmodifiableSortedSet(
                                generateAllCutsSchedulesForGivenNumCutsAndWidth(
                                        numCuts - 1,
                                        width - firstCutWidth,
                                        firstCutWidth
                                ));
                extendToFullWidthAndCollect(csSetForSmallerWidth, width, firstCutWidth, res);
            });
        }
        return res;
    }

    private static void verifyAll(final Set<CutSchedule> csSet, int width, final Set<CutSchedule> mustNotContain) {
        csSet.stream().forEach(cs -> {
            assert cs.verify();
            assert cs.totalWidth() == width;
            assert !mustNotContain.contains(cs);
        });
    }

    private static SortedSet<CutSchedule> tryingAllCutsForWidth(final int width) {
        final int minNumCuts = 0;
        final int maxNumCuts = width - 1;
        SortedSet<CutSchedule> res = new TreeSet<>();
        IntStream.rangeClosed(minNumCuts, maxNumCuts).forEach(numCuts -> {
            Set<CutSchedule> csSetForWidth = generateAllCutsSchedulesForGivenNumCutsAndWidth(numCuts, width, 1);
            verifyAll(csSetForWidth, width, res);
            res.addAll(csSetForWidth);
        });
        return res;
    }

    private final static boolean withPrintout = false;

    @Test
    public void loopOverWidths() {
        final int minWidth = 1;
        final int maxWidth = 100;
        IntStream.rangeClosed(minWidth, maxWidth).forEach(width -> {
            SortedSet<CutSchedule> all = tryingAllCutsForWidth(width);
            System.out.println("Number of ways of cutting for width = " + width + ": " + all.size());
            if (withPrintout) {
                all.stream().forEach(cs -> System.out.println(cs.toString(false)));
            }
        });
    }

}

Popular Prag Prog topics Top

sdmoralesma
Title: Web Development with Clojure, Third Edition - migrations/create not working: p159 When I execute the command: user=&gt; (create-...
New
simonpeter
When I try the command to create a pair of migration files I get an error. user=&gt; (create-migration "guestbook") Execution error (Ill...
New
lirux
Hi Jamis, I think there’s an issue with a test on chapter 6. I own the ebook, version P1.0 Feb. 2019. This test doesn’t pass for me: ...
New
herminiotorres
Hi! I know not the intentions behind this narrative when called, on page XI: mount() |&gt; handle_event() |&gt; render() but the correc...
New
conradwt
First, the code resources: Page 237: rumbl_umbrella/apps/rumbl/mix.exs Note: That this file is missing. Page 238: rumbl_umbrella/app...
New
taguniversalmachine
It seems the second code snippet is missing the code to set the current_user: current_user: Accounts.get_user_by_session_token(session["...
New
a.zampa
@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
New
davetron5000
Hello faithful readers! If you have tried to follow along in the book, you are asked to start up the dev environment via dx/build and ar...
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

AstonJ
If it’s a mechanical keyboard, which switches do you have? Would you recommend it? Why? What will your next keyboard be? Pics always w...
New
AstonJ
Or looking forward to? :nerd_face:
New
dasdom
No chair. I have a standing desk. This post was split into a dedicated thread from our thread about chairs :slight_smile:
New
DevotionGeo
I know that -t flag is used along with -i flag for getting an interactive shell. But I cannot digest what the man page for docker run com...
New
Rainer
My first contact with Erlang was about 2 years ago when I used RabbitMQ, which is written in Erlang, for my job. This made me curious and...
New
AstonJ
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
AstonJ
If you are experiencing Rails console using 100% CPU on your dev machine, then updating your development and test gems might fix the issu...
New
PragmaticBookshelf
Rails 7 completely redefines what it means to produce fantastic user experiences and provides a way to achieve all the benefits of single...
New
PragmaticBookshelf
Author Spotlight: Karl Stolley @karlstolley Logic! Rhetoric! Prag! Wow, what a combination. In this spotlight, we sit down with Karl ...
New
First poster: bot
Large Language Models like ChatGPT say The Darnedest Things. The Errors They MakeWhy We Need to Document Them, and What We Have Decided ...
New

Latest in PragProg

View all threads ❯