diff --git a/dynamic-programming/notes.md b/dynamic-programming/notes.md index c2821f3c1c89f1164bbce957e628bb7004047549..e70b77e912866fe1284bc5fa86bc59f1a2a8311a 100644 --- a/dynamic-programming/notes.md +++ b/dynamic-programming/notes.md @@ -115,34 +115,34 @@ Problem: Wrap a paragraph to a line length, minimizing the sum of the squares of ## DAG * Edges (actions): - * … + * Place an entire line * Vertices (situations): - * … + * The number of words placed so far * Edge weights: - * … + * The penalty for the line just placed * Topological order: - * … + * 0 → 1 → … → `wordCount` * Goal: - * … + * Find a shortest path from 0 to `wordCount` ## Backpointer Class * Information for the final result: - * … + * What was the position of the previous line break? * Information to go back: - * … + * What was the position of the previous line break? (redundant) * Information to compare quality: - * … + * What was the *total* penalty for the lines placed so far? ## Choosing a Backpointer * Exhaustive search: - * Generate …. - * Check that …. + * Generate start-of-line positions (in reverse order) as long as the character count (a running total) does not exceed the line length and the start-of-line position does not go negative. + * Check that the backpointer minimizes the *total* penalty (the total penalty of all prior lines plus the penalty of the newly added line). ## Example -> x x x x xxxx xxxx +> x x x|x xxxx|xxxx > 0 1 2 3 4 5 6 Vertex Backpointer @@ -150,13 +150,13 @@ Problem: Wrap a paragraph to a line length, minimizing the sum of the squares of Index Previous Index Total Penalty ------ -------------- ------------- 0 ⊥ 0 - 1 … … - 2 … … - 3 … … - 4 … … - 5 … … - 6 … … + 1 0 36 + 2 0 16 + 3 0 4 + 4 0 0 + 5 3 5 + 6 5 14 Reversed Path ---- - 6 ← … + 6 ← 5 ← 3 ← 0 diff --git a/dynamic-programming/src/features/wrapping/solver.js b/dynamic-programming/src/features/wrapping/solver.js index 062a7fa11ac32231da84d1930b7350c7e2707993..380f102797d83114548734f0257e61f4d6fff199 100644 --- a/dynamic-programming/src/features/wrapping/solver.js +++ b/dynamic-programming/src/features/wrapping/solver.js @@ -12,7 +12,17 @@ class Backpointer { function chooseBackpointer(wordLengths, lineLength, backpointers) { const index = backpointers.length; let best = new Backpointer(undefined, Infinity); - // TODO: stub + for (let start = index - 1, characterCount = wordLengths[start]; + start >= 0 && characterCount <= lineLength; + --start, characterCount += wordLengths[start] + 1) { + const candidate = new Backpointer( + start, + backpointers[start].totalPenalty + penalty(characterCount, lineLength), + ); + if (candidate.totalPenalty < best.totalPenalty) { + best = candidate; + } + } console.assert( best.previousIndex !== undefined, `One of the word lengths ${wordLengths} is too long to fit in a line of length ${lineLength}.`,