How to build a retaining wall

Jun 24, 2015

When we moved into our first house we inherited a half finished retaining wall.

We had posts already built,eventually we where kindly gifted some wood planks represented as a list of lengths wood_lengthsto finish the retaining wall we had to cut the planks to get planks of all the lengths we require,so we had another list of lengths required_lengths.

We want to use as few cuts as possible because we are lazy programmers.

Task: write an algorithm to find the list of cuts you should make.

A cut can be described as:

wood_num is the index of the length of wood to cut from in wood_lengthscut_amount the length of wood to cut from this wood_num

Finding an optimal solution means generating all valid possibilities and taking the one with optimal output (with the smallest amount of cuts in this case).

One strategy for trying all possibilities is to imagine: for required_lengths[0] we could take the cut from any length in wood_lengths for required_lengths[1] we could take the cut from any length in wood_lengths …

This recursive algorithm would run generate a recursive call tree with len(wood_lengths) branches at each step and len(required_lengths) deep so it would take atleast $O( len(wood\_lengths) ^ {len(required\_lengths)} )$ time.

There are some conditions allowing you to prune this recursive call tree:

You can’t take a cut of size N from a wood_length less than N.

Only consider non duplicate wood lengths (If you have planks A and B both of size N then you could take a cut from either, it would be equivalent)

Notice how wood_lengths=[4, 6, 8] required_lengths=[2] is computed twice,when inputs get large this can mean the algorithm spends a massive amount of time solving problems that it has already solved.

If the lengths in wood_lengths (regardless of order) is the same and required_length_idx is the same then retaining_wall_recursive should return the same thing.

One way to improve a recursive algorithm to not compute the same thing twice is called memoization (caching the function call),We could store a hashtable (a dict) mapping the given arguments to the output of the function.

One way to implement this in python is with decorators, its quite nice because our caching logic is in one place.

123456789101112131415161718192021222324252627282930313233343536373839

import copyimport functoolsclassunordered_memoized(object):'''Decorator. Caches a function's return value each time it is called. If called later with the same arguments, the cached value is returned (not reevaluated). list arguments are hashed irrespective of the order of items. a deepcopy is cached to avoid changing contents of the cache by operating on a functions return value. '''def__init__(self, func): self.func = func self.cache = {}def__call__(self, *args): cache_key = []for arg in args:if isinstance(arg, list): cache_key.append(tuple(sorted(arg)))else: cache_key.append(arg) cache_key = tuple(cache_key)if cache_key in self.cache:return self.cache[cache_key]else: value = self.func(*args) self.cache[cache_key] = copy.deepcopy(value)return valuedef__repr__(self):'''Return the function's docstring.'''return self.func.__doc__def__get__(self, obj, objtype):'''Support instance methods.'''return functools.partial(self.__call__, obj)

This runs a lot faster, but does use more memory, could we be better about our memory usage by smartly evicting keys?

TODO: finish dynamic programming example

Extra follow on problems:

Maximise reuse/Minimise waste

What if there where multiple solutions with the fewest cuts, then how should we decide the best?

That is first and foremost we want to make the fewest cuts

Secondly we want to maximize reuse (we want nice long offcuts we can use for other things).

How should we define reuse?Well we know we want to build another retaining wall which will have similar distances between posts (and thus similar required_lengths)As a heuristic we know we will only be able to use a leftover plank if its at least the smallest length in required_lengths

This means we can define reuse as the amount of times you can cut the smallest required_length out of your remaining planks.

Waste is how much reuse a solution doesn’t get (The total length of all the offcuts that can’t be reused).Another way of wording this is that we want to minimize waste.