Skip to content

Commit 7f90fdd

Browse files
committed
feat(js): add modular solution and documentation for hard challenge 9 - Text Justification with DP
1 parent e7bb4e6 commit 7f90fdd

File tree

3 files changed

+255
-0
lines changed

3 files changed

+255
-0
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Text Justification with Dynamic Programming
2+
3+
## English
4+
5+
Text justification is a classic problem in document formatting and typesetting. Dynamic programming provides an optimal solution by minimizing the penalty of uneven spacing, improving readability and aesthetics.
6+
7+
This challenge involves implementing a dynamic programming algorithm to justify text optimally, minimizing penalties for irregular spaces.
8+
9+
### Relevant Code Snippet
10+
11+
```javascript
12+
class TextJustifier {
13+
constructor(words, maxWidth) {
14+
this.words = words;
15+
this.maxWidth = maxWidth;
16+
this.n = words.length;
17+
this.dp = new Array(this.n + 1).fill(Infinity);
18+
this.dp[this.n] = 0;
19+
this.breaks = new Array(this.n + 1).fill(-1);
20+
}
21+
22+
_cost(i, j) {
23+
const length =
24+
this.words.slice(i, j + 1).reduce((acc, w) => acc + w.length, 0) +
25+
(j - i);
26+
if (length > this.maxWidth) {
27+
return Infinity;
28+
}
29+
return Math.pow(this.maxWidth - length, 3);
30+
}
31+
32+
justify() {
33+
for (let i = this.n - 1; i >= 0; i--) {
34+
for (let j = i; j < this.n; j++) {
35+
const cost = this._cost(i, j);
36+
if (cost === Infinity) {
37+
break;
38+
}
39+
if (this.dp[j + 1] + cost < this.dp[i]) {
40+
this.dp[i] = this.dp[j + 1] + cost;
41+
this.breaks[i] = j;
42+
}
43+
}
44+
}
45+
46+
const lines = [];
47+
let i = 0;
48+
while (i < this.n) {
49+
const j = this.breaks[i];
50+
const lineWords = this.words.slice(i, j + 1);
51+
const line = this._justifyLine(lineWords);
52+
lines.push(line);
53+
i = j + 1;
54+
}
55+
return lines;
56+
}
57+
58+
_justifyLine(lineWords) {
59+
if (lineWords.length === 1) {
60+
return lineWords[0] + " ".repeat(this.maxWidth - lineWords[0].length);
61+
}
62+
63+
const totalSpaces =
64+
this.maxWidth - lineWords.reduce((acc, w) => acc + w.length, 0);
65+
const spacesBetweenWords = lineWords.length - 1;
66+
const space = Math.floor(totalSpaces / spacesBetweenWords);
67+
const extra = totalSpaces % spacesBetweenWords;
68+
69+
let line = "";
70+
for (let i = 0; i < lineWords.length - 1; i++) {
71+
line += lineWords[i] + " ".repeat(space + (i < extra ? 1 : 0));
72+
}
73+
line += lineWords[lineWords.length - 1];
74+
return line;
75+
}
76+
}
77+
```
78+
79+
### History
80+
81+
Text justification has been extensively studied in document formatting and typesetting, with dynamic programming providing an optimal solution to improve readability and aesthetics.
82+
83+
---
84+
85+
## Español
86+
87+
Justificación de Texto con Programación Dinámica
88+
89+
La justificación de texto es un problema clásico en el formateo y composición de documentos. La programación dinámica proporciona una solución óptima al minimizar la penalización por espacios desiguales, mejorando la legibilidad y estética.
90+
91+
Este reto consiste en implementar un algoritmo de programación dinámica para justificar texto de manera óptima, minimizando penalizaciones por espacios irregulares.
92+
93+
### Fragmento de Código Relevante
94+
95+
```javascript
96+
class TextJustifier {
97+
constructor(words, maxWidth) {
98+
this.words = words;
99+
this.maxWidth = maxWidth;
100+
this.n = words.length;
101+
this.dp = new Array(this.n + 1).fill(Infinity);
102+
this.dp[this.n] = 0;
103+
this.breaks = new Array(this.n + 1).fill(-1);
104+
}
105+
106+
_cost(i, j) {
107+
const length =
108+
this.words.slice(i, j + 1).reduce((acc, w) => acc + w.length, 0) +
109+
(j - i);
110+
if (length > this.maxWidth) {
111+
return Infinity;
112+
}
113+
return Math.pow(this.maxWidth - length, 3);
114+
}
115+
116+
justify() {
117+
for (let i = this.n - 1; i >= 0; i--) {
118+
for (let j = i; j < this.n; j++) {
119+
const cost = this._cost(i, j);
120+
if (cost === Infinity) {
121+
break;
122+
}
123+
if (this.dp[j + 1] + cost < this.dp[i]) {
124+
this.dp[i] = this.dp[j + 1] + cost;
125+
this.breaks[i] = j;
126+
}
127+
}
128+
}
129+
130+
const lines = [];
131+
let i = 0;
132+
while (i < this.n) {
133+
const j = this.breaks[i];
134+
const lineWords = this.words.slice(i, j + 1);
135+
const line = this._justifyLine(lineWords);
136+
lines.push(line);
137+
i = j + 1;
138+
}
139+
return lines;
140+
}
141+
142+
_justifyLine(lineWords) {
143+
if (lineWords.length === 1) {
144+
return lineWords[0] + " ".repeat(this.maxWidth - lineWords[0].length);
145+
}
146+
147+
const totalSpaces =
148+
this.maxWidth - lineWords.reduce((acc, w) => acc + w.length, 0);
149+
const spacesBetweenWords = lineWords.length - 1;
150+
const space = Math.floor(totalSpaces / spacesBetweenWords);
151+
const extra = totalSpaces % spacesBetweenWords;
152+
153+
let line = "";
154+
for (let i = 0; i < lineWords.length - 1; i++) {
155+
line += lineWords[i] + " ".repeat(space + (i < extra ? 1 : 0));
156+
}
157+
line += lineWords[lineWords.length - 1];
158+
return line;
159+
}
160+
}
161+
```
162+
163+
### Historia
164+
165+
La justificación de texto ha sido ampliamente estudiada en el formateo y composición de documentos, y la programación dinámica proporciona una solución óptima para mejorar la legibilidad y estética.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// TextJustifier.js - Text Justification using Dynamic Programming in JavaScript
2+
3+
class TextJustifier {
4+
constructor(words, maxWidth) {
5+
this.words = words;
6+
this.maxWidth = maxWidth;
7+
this.n = words.length;
8+
this.dp = new Array(this.n + 1).fill(Infinity);
9+
this.dp[this.n] = 0;
10+
this.breaks = new Array(this.n + 1).fill(-1);
11+
}
12+
13+
_cost(i, j) {
14+
const length =
15+
this.words.slice(i, j + 1).reduce((acc, w) => acc + w.length, 0) +
16+
(j - i);
17+
if (length > this.maxWidth) {
18+
return Infinity;
19+
}
20+
return Math.pow(this.maxWidth - length, 3);
21+
}
22+
23+
justify() {
24+
for (let i = this.n - 1; i >= 0; i--) {
25+
for (let j = i; j < this.n; j++) {
26+
const cost = this._cost(i, j);
27+
if (cost === Infinity) {
28+
break;
29+
}
30+
if (this.dp[j + 1] + cost < this.dp[i]) {
31+
this.dp[i] = this.dp[j + 1] + cost;
32+
this.breaks[i] = j;
33+
}
34+
}
35+
}
36+
37+
const lines = [];
38+
let i = 0;
39+
while (i < this.n) {
40+
const j = this.breaks[i];
41+
const lineWords = this.words.slice(i, j + 1);
42+
const line = this._justifyLine(lineWords);
43+
lines.push(line);
44+
i = j + 1;
45+
}
46+
return lines;
47+
}
48+
49+
_justifyLine(lineWords) {
50+
if (lineWords.length === 1) {
51+
return lineWords[0] + " ".repeat(this.maxWidth - lineWords[0].length);
52+
}
53+
54+
const totalSpaces =
55+
this.maxWidth - lineWords.reduce((acc, w) => acc + w.length, 0);
56+
const spacesBetweenWords = lineWords.length - 1;
57+
const space = Math.floor(totalSpaces / spacesBetweenWords);
58+
const extra = totalSpaces % spacesBetweenWords;
59+
60+
let line = "";
61+
for (let i = 0; i < lineWords.length - 1; i++) {
62+
line += lineWords[i] + " ".repeat(space + (i < extra ? 1 : 0));
63+
}
64+
line += lineWords[lineWords.length - 1];
65+
return line;
66+
}
67+
}
68+
69+
export default TextJustifier;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
Challenge:
3+
Given a set of words and a maximum width, implement a dynamic programming algorithm to justify text optimally, minimizing penalties for irregular spaces.
4+
5+
This solution follows DRY principles and is implemented in JavaScript.
6+
*/
7+
8+
import TextJustifier from "./TextJustifier.js";
9+
10+
function main() {
11+
const words = ["This", "is", "an", "example", "of", "text", "justification."];
12+
const maxWidth = 16;
13+
14+
const justifier = new TextJustifier(words, maxWidth);
15+
const justifiedLines = justifier.justify();
16+
17+
console.log("Justified Text:");
18+
justifiedLines.forEach((line) => console.log(`"${line}"`));
19+
}
20+
21+
main();

0 commit comments

Comments
 (0)