Skip to content

Commit 965396c

Browse files
committed
Simplify reference solution for piecing-it-together practice exercise
1 parent eec11ac commit 965396c

File tree

1 file changed

+51
-246
lines changed

1 file changed

+51
-246
lines changed

exercises/practice/piecing-it-together/.meta/src/reference/java/PiecingItTogether.java

Lines changed: 51 additions & 246 deletions
Original file line numberDiff line numberDiff line change
@@ -2,130 +2,40 @@
22

33
public class PiecingItTogether {
44
private static final double DOUBLE_EQUALITY_TOLERANCE = 1e-9;
5+
private static final int MAX_DIMENSION = 1000;
56

67
public static JigsawInfo getCompleteInformation(JigsawInfo input) {
7-
Integer rows = input.getRows().isPresent() ? input.getRows().getAsInt() : null;
8-
Integer cols = input.getColumns().isPresent() ? input.getColumns().getAsInt() : null;
9-
Integer pieces = input.getPieces().isPresent() ? input.getPieces().getAsInt() : null;
10-
Integer border = input.getBorder().isPresent() ? input.getBorder().getAsInt() : null;
11-
Integer inside = input.getInside().isPresent() ? input.getInside().getAsInt() : null;
12-
Double aspect = input.getAspectRatio().isPresent() ? input.getAspectRatio().getAsDouble() : null;
13-
String format = input.getFormat().orElse(null);
14-
15-
// Final information to be compared with the original input to detect inconsistencies
16-
JigsawInfo result = null;
17-
18-
// Check format and aspect ratio don't contradict each other or set aspect if format is square
19-
if (format != null && aspect != null) {
20-
if (!format.equals(getFormatFromAspect(aspect))) {
21-
throw new IllegalArgumentException("Contradictory data");
22-
}
23-
} else if ("square".equalsIgnoreCase(format)) {
24-
aspect = 1.0;
25-
}
26-
27-
// Derive any missing value among pieces, border, and inside
28-
if (pieces != null && border != null && inside != null) {
29-
if (pieces != border + inside) {
30-
throw new IllegalArgumentException("Contradictory data");
31-
}
32-
} else if (pieces != null && border != null) {
33-
inside = pieces - border;
34-
} else if (border != null && inside != null) {
35-
pieces = border + inside;
36-
} else if (inside != null && pieces != null) {
37-
border = pieces - inside;
38-
}
39-
40-
// Try to compute missing information using the numbers of rows and cols
41-
if (rows != null && cols != null) {
42-
result = fromRowsAndCols(rows, cols);
43-
} else if (rows != null) {
44-
Optional<Integer> possibleCols = calculateOtherSide(rows, true, pieces, border, inside, aspect);
45-
46-
if (possibleCols.isPresent()) {
47-
cols = possibleCols.get();
48-
result = fromRowsAndCols(rows, cols);
49-
}
50-
} else if (cols != null) {
51-
Optional<Integer> possibleRows = calculateOtherSide(cols, false, pieces, border, inside, aspect);
52-
53-
if (possibleRows.isPresent()) {
54-
rows = possibleRows.get();
55-
result = fromRowsAndCols(rows, cols);
56-
}
57-
}
58-
59-
if (result != null) {
60-
checkConsistencyOrThrow(result, input);
61-
return result;
62-
}
63-
64-
// Try to compute missing information using the value of aspect ratio
65-
if (aspect != null) {
66-
if (pieces != null) {
67-
double actualRows = Math.sqrt(pieces / aspect);
68-
rows = roundIfClose(actualRows);
69-
70-
double actualCols = aspect * rows;
71-
cols = roundIfClose(actualCols);
72-
73-
result = fromRowsAndCols(rows, cols);
74-
checkConsistencyOrThrow(result, input);
75-
return result;
76-
} else if (inside != null && inside == 0) {
77-
List<JigsawInfo> validGuesses = new ArrayList<>();
78-
79-
for (int fixed : List.of(1, 2)) {
80-
tryGuessWithFixedSide(fixed, true, aspect, input).ifPresent(validGuesses::add); // rows = fixed
81-
tryGuessWithFixedSide(fixed, false, aspect, input).ifPresent(validGuesses::add); // cols = fixed
82-
}
83-
84-
if (validGuesses.size() == 1) {
85-
return validGuesses.getFirst();
86-
} else if (validGuesses.size() > 1) {
87-
throw new IllegalArgumentException("Insufficient data");
88-
} else {
89-
throw new IllegalArgumentException("Contradictory data");
90-
}
91-
}
92-
}
93-
94-
if (pieces == null && border == null && (inside == null || inside == 0)) {
95-
throw new IllegalArgumentException("Insufficient data");
96-
}
97-
98-
// Brute force as a last resort
998
List<JigsawInfo> validGuesses = new ArrayList<>();
1009

101-
// Brute-force using pieces
102-
if (pieces != null) {
103-
for (int r = 1; r <= pieces; r++) {
104-
if (pieces % r != 0) {
105-
continue; // cols must be integer
10+
if (input.getPieces().isPresent()) {
11+
// If pieces is known, we only test divisors of pieces
12+
int pieces = input.getPieces().getAsInt();
13+
for (int rows = 1; rows <= pieces; rows++) {
14+
if (pieces % rows != 0) {
15+
continue;
10616
}
107-
108-
int c = pieces / r;
109-
tryGuess(r, c, input).ifPresent(validGuesses::add);
17+
int columns = pieces / rows;
18+
createValidJigsaw(rows, columns, input).ifPresent(validGuesses::add);
11019
}
111-
} else if (inside != null) {
112-
for (int r = 3; r <= inside + 2; r++) {
113-
int innerRows = r - 2;
20+
} else if (input.getInside().isPresent() && input.getInside().getAsInt() > 0) {
21+
// If inside pieces is non-zero, we only test divisors of inside
22+
int inside = input.getInside().getAsInt();
23+
for (int innerRows = 1; innerRows <= inside; innerRows++) {
11424
if (inside % innerRows != 0) {
11525
continue;
11626
}
117-
118-
int innerCols = inside / innerRows;
119-
int c = innerCols + 2;
120-
121-
tryGuess(r, c, input).ifPresent(validGuesses::add);
27+
int innerColumns = inside / innerRows;
28+
createValidJigsaw(innerRows + 2, innerColumns + 2, input).ifPresent(validGuesses::add);
12229
}
12330
} else {
124-
int max = Math.min(border, 1000);
125-
126-
for (int r = 1; r <= max; r++) {
127-
for (int c = 1; c <= max; c++) {
128-
tryGuess(r, c, input).ifPresent(validGuesses::add);
31+
// Brute force using border constraint if available
32+
int maxDimension = input.getBorder().isPresent()
33+
? Math.min(input.getBorder().getAsInt(), MAX_DIMENSION)
34+
: MAX_DIMENSION;
35+
36+
for (int rows = 1; rows <= maxDimension; rows++) {
37+
for (int columns = 1; columns <= maxDimension; columns++) {
38+
createValidJigsaw(rows, columns, input).ifPresent(validGuesses::add);
12939
}
13040
}
13141
}
@@ -140,170 +50,65 @@ public static JigsawInfo getCompleteInformation(JigsawInfo input) {
14050
}
14151

14252
private static String getFormatFromAspect(double aspectRatio) {
143-
String format;
144-
14553
if (Math.abs(aspectRatio - 1.0) < DOUBLE_EQUALITY_TOLERANCE) {
146-
format = "square";
54+
return "square";
14755
} else if (aspectRatio < 1.0) {
148-
format = "portrait";
56+
return "portrait";
14957
} else {
150-
format = "landscape";
151-
}
152-
153-
return format;
154-
}
155-
156-
private static Integer roundIfClose(double value) {
157-
double rounded = Math.round(value);
158-
159-
if (Math.abs(value - rounded) < DOUBLE_EQUALITY_TOLERANCE) {
160-
return (int) rounded;
58+
return "landscape";
16159
}
162-
163-
throw new IllegalArgumentException("Contradictory data");
16460
}
16561

166-
private static int calculateOtherSideFromPieces(int knownSide, int pieces) {
167-
if (pieces <= 0 || pieces % knownSide != 0) {
168-
throw new IllegalArgumentException("Contradictory data");
169-
}
170-
171-
return pieces / knownSide;
172-
}
173-
174-
private static int calculateOtherSideFromBorder(int knownSide, int border) {
175-
if (knownSide == 1) {
176-
return border;
177-
}
178-
179-
if (knownSide == border) {
180-
return 1;
181-
}
182-
183-
if ((border + 4 <= 2 * knownSide) || border % 2 != 0) {
184-
throw new IllegalArgumentException("Contradictory data");
185-
}
186-
187-
return ((border + 4) - 2 * knownSide) / 2;
188-
}
189-
190-
private static int calculateOtherSideFromInside(int knownSide, int inside) {
191-
if (knownSide <= 2 || inside % (knownSide - 2) != 0) {
192-
throw new IllegalArgumentException("Contradictory data");
193-
}
194-
195-
return (inside / (knownSide - 2)) + 2;
196-
}
197-
198-
private static Optional<Integer> calculateOtherSide(int knownSide, boolean isRowKnown, Integer pieces, Integer border, Integer inside, Double aspect) {
199-
if (pieces != null) {
200-
return Optional.of(calculateOtherSideFromPieces(knownSide, pieces));
201-
} else if (border != null) {
202-
return Optional.of(calculateOtherSideFromBorder(knownSide, border));
203-
} else if (aspect != null) {
204-
double raw = isRowKnown ? knownSide * aspect : knownSide / aspect;
205-
return Optional.of(roundIfClose(raw));
206-
} else if (inside != null) {
207-
if (inside == 0) {
208-
// When inside is zero and all other values (otherSide, pieces, border, aspect) are unknown,
209-
// it only tells us that either rows or cols is 1 or 2, but provides no way to infer the rest.
210-
// Since we can't make any further assumptions, we stop here.
211-
throw new IllegalArgumentException("Insufficient data");
212-
}
213-
214-
return Optional.of(calculateOtherSideFromInside(knownSide, inside));
215-
}
216-
217-
return Optional.empty();
218-
}
219-
220-
private static JigsawInfo fromRowsAndCols(int rows, int cols) {
221-
int pieces = rows * cols;
222-
int border = (rows == 1 || cols == 1) ? pieces : 2 * (rows + cols - 2);
62+
private static JigsawInfo fromRowsAndCols(int rows, int columns) {
63+
int pieces = rows * columns;
64+
int border = (rows == 1 || columns == 1) ? pieces : 2 * (rows + columns - 2);
22365
int inside = pieces - border;
224-
double aspect = (double) cols / rows;
225-
String format = getFormatFromAspect(aspect);
66+
double aspectRatio = (double) columns / rows;
67+
String format = getFormatFromAspect(aspectRatio);
22668

22769
return new JigsawInfo.Builder()
22870
.pieces(pieces)
22971
.border(border)
23072
.inside(inside)
23173
.rows(rows)
232-
.columns(cols)
233-
.aspectRatio(aspect)
74+
.columns(columns)
75+
.aspectRatio(aspectRatio)
23476
.format(format)
23577
.build();
23678
}
23779

23880
/**
23981
* Verifies that all known values in the input match those in the computed result.
240-
* If any known value in the input contradicts the corresponding computed value,
241-
* an IllegalArgumentException is thrown.
82+
* Returns false if any known value conflicts, true if all values are consistent.
24283
*
24384
* @param computed the fully inferred jigsaw information
244-
* @param input the original partial input with possibly known values
245-
* @throws IllegalArgumentException if any known value in the input conflicts with the computed result
85+
* @param input the original partial input with possibly empty values
86+
* @return true if all values are consistent, false if any conflict exists
24687
*/
247-
private static void checkConsistencyOrThrow(JigsawInfo computed, JigsawInfo input) {
248-
if (!valuesMatch(computed.getPieces(), input.getPieces()) || !valuesMatch(computed.getBorder(), input.getBorder()) || !valuesMatch(computed.getInside(), input.getInside()) || !valuesMatch(computed.getRows(), input.getRows()) || !valuesMatch(computed.getColumns(), input.getColumns()) || !valuesMatch(computed.getAspectRatio(), input.getAspectRatio()) || !valuesMatch(computed.getFormat(), input.getFormat())) {
249-
throw new IllegalArgumentException("Contradictory data");
250-
}
88+
private static boolean isConsistent(JigsawInfo computed, JigsawInfo input) {
89+
return valuesMatch(computed.getPieces(), input.getPieces()) &&
90+
valuesMatch(computed.getBorder(), input.getBorder()) &&
91+
valuesMatch(computed.getInside(), input.getInside()) &&
92+
valuesMatch(computed.getRows(), input.getRows()) &&
93+
valuesMatch(computed.getColumns(), input.getColumns()) &&
94+
valuesMatch(computed.getAspectRatio(), input.getAspectRatio()) &&
95+
valuesMatch(computed.getFormat(), input.getFormat());
25196
}
25297

25398
/**
254-
* Attempts to compute a valid jigsaw configuration by fixing one dimension
255-
* (either rows or columns) and inferring the other using the aspect ratio.
256-
*
257-
* @param fixed the known size of one side (either rows or columns)
258-
* @param isRowFixed true if the fixed value represents rows, false if columns
259-
* @param aspect the desired aspect ratio (cols / rows)
260-
* @param input the original input to check for consistency
261-
* @return an Optional containing a valid inferred configuration, or empty if the guess is invalid
262-
*/
263-
private static Optional<JigsawInfo> tryGuessWithFixedSide(int fixed, boolean isRowFixed, double aspect, JigsawInfo input) {
264-
try {
265-
int other = isRowFixed ? roundIfClose(fixed * aspect) : roundIfClose(fixed / aspect);
266-
267-
int rows = isRowFixed ? fixed : other;
268-
int cols = isRowFixed ? other : fixed;
269-
270-
JigsawInfo guess = fromRowsAndCols(rows, cols);
271-
checkConsistencyOrThrow(guess, input);
272-
return Optional.of(guess);
273-
} catch (IllegalArgumentException ignored) {
274-
return Optional.empty();
275-
}
276-
}
277-
278-
/**
279-
* Attempts to construct a jigsaw configuration using the specified number of rows and columns.
99+
* Attempts to construct a valid jigsaw configuration using the specified number of rows and columns.
280100
* Returns a valid result only if the configuration is consistent with the input.
281101
*
282-
* @param rows number of rows to try
283-
* @param cols number of columns to try
284-
* @param input the original input to check for consistency
102+
* @param rows number of rows to try
103+
* @param columns number of columns to try
104+
* @param input the original input to check for consistency
285105
* @return an Optional containing a valid configuration, or empty if inconsistent
286106
*/
287-
private static Optional<JigsawInfo> tryGuess(int rows, int cols, JigsawInfo input) {
288-
try {
289-
JigsawInfo guess = fromRowsAndCols(rows, cols);
290-
checkConsistencyOrThrow(guess, input);
291-
return Optional.of(guess);
292-
} catch (IllegalArgumentException ignored) {
293-
return Optional.empty();
294-
}
107+
private static Optional<JigsawInfo> createValidJigsaw(int rows, int columns, JigsawInfo input) {
108+
JigsawInfo candidate = fromRowsAndCols(rows, columns);
109+
return isConsistent(candidate, input) ? Optional.of(candidate) : Optional.empty();
295110
}
296111

297-
/**
298-
* Compares two optional values for equality.
299-
* Returns true if either value is absent, or if both are present and equal.
300-
* Used to check consistency between inferred and input data.
301-
*
302-
* @param a the first optional value
303-
* @param b the second optional value
304-
* @param <T> the type of the contained values
305-
* @return true if the values are consistent (both missing or equal if present), false otherwise
306-
*/
307112
private static <T> boolean valuesMatch(Optional<T> a, Optional<T> b) {
308113
if (a.isPresent() && b.isPresent()) {
309114
return Objects.equals(a.get(), b.get());

0 commit comments

Comments
 (0)