From 927faa0a5892c355d03b5e378b1b3339b578ce52 Mon Sep 17 00:00:00 2001 From: Diego Leonardo Urban Date: Fri, 6 Nov 2026 17:29:35 +0100 Subject: [PATCH 1/6] Fix toString output, median side effect and pi approximation (B5, B10, B20, B25) - B5: LinkedList.toString separates names with commas ("[a,b]") instead of ending each with one ("[a,b,]") - B10: Vector.toString shows only the stored elements instead of the whole backing array with 99 nulls - B20: Statistics.median sorts a copy, leaving the caller's array as it was - B25: Geometry uses Math.PI instead of 3.14 for circles and cylinders Each change has a test that failed before it. Co-Authored-By: Claude Opus 5.5 --- .../datastructure/linkedlist/LinkedList.java | 8 +++++--- .../datastructure/vector/Vector.java | 5 ++--- .../diegourban/algorithms/math/Geometry.java | 18 +++++++----------- .../diegourban/algorithms/math/Statistics.java | 16 ++++++++-------- .../linkedlist/LinkedListTest.java | 10 +++++----- .../datastructure/vector/VectorTest.java | 10 ++++++++++ .../algorithms/math/GeometryTest.java | 8 ++++++++ .../algorithms/math/StatisticsTest.java | 8 ++++++++ 8 files changed, 53 insertions(+), 30 deletions(-) diff --git a/src/main/java/io/github/diegourban/algorithms/datastructure/linkedlist/LinkedList.java b/src/main/java/io/github/diegourban/algorithms/datastructure/linkedlist/LinkedList.java index 4e16c94..20ba77e 100644 --- a/src/main/java/io/github/diegourban/algorithms/datastructure/linkedlist/LinkedList.java +++ b/src/main/java/io/github/diegourban/algorithms/datastructure/linkedlist/LinkedList.java @@ -211,8 +211,8 @@ public boolean contains(final Element element) { } /** - * Returns the element names in order, each followed by a comma, inside brackets, for example {@code "[a,b,]"}; - * an empty list gives {@code "[]"}. + * Returns the element names in order, separated by commas, inside brackets, for example {@code "[a,b]"}; an + * empty list gives {@code "[]"}. * * @return the string form of the list */ @@ -225,8 +225,10 @@ public String toString() { final StringBuilder sb = new StringBuilder("["); for (int i = 0; i < this.totalElements; i++) { + if (i > 0) { + sb.append(","); + } sb.append(current.getElement().getName()); - sb.append(","); current = current.getNext(); } sb.append("]"); diff --git a/src/main/java/io/github/diegourban/algorithms/datastructure/vector/Vector.java b/src/main/java/io/github/diegourban/algorithms/datastructure/vector/Vector.java index 1a94dc5..f24f4aa 100644 --- a/src/main/java/io/github/diegourban/algorithms/datastructure/vector/Vector.java +++ b/src/main/java/io/github/diegourban/algorithms/datastructure/vector/Vector.java @@ -126,13 +126,12 @@ private void ensureCapacity() { } /** - * Returns the whole backing array, including the unused {@code null} slots, in the form - * {@code "Vector{elements=[a, b, null, ...]}"}. + * Returns the stored elements in order, in the form {@code "Vector{elements=[a, b]}"}. * * @return the string form of the vector */ @Override public String toString() { - return "Vector{" + "elements=" + Arrays.toString(elements) + '}'; + return "Vector{" + "elements=" + Arrays.toString(Arrays.copyOf(elements, totalElements)) + '}'; } } diff --git a/src/main/java/io/github/diegourban/algorithms/math/Geometry.java b/src/main/java/io/github/diegourban/algorithms/math/Geometry.java index 01ea5ad..fb11462 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/Geometry.java +++ b/src/main/java/io/github/diegourban/algorithms/math/Geometry.java @@ -4,15 +4,11 @@ /** * Plane and solid geometry: distances, circles, polygons, cylinders and triangles. - *

- * The circle and cylinder methods approximate pi as {@code 3.14}, not {@link Math#PI}. */ public final class Geometry { private Geometry() {} - private static final double SIMPLE_PI = 3.14; - /** * Calculates the distance between two bi-dimensional points using the Pythagorean theorem. * @@ -27,17 +23,17 @@ public static double distance(final int x1, final int y1, final int x2, final in } /** - * Calculates the area of a circle from its radius, using pi = 3.14 + * Calculates the area of a circle from its radius * * @param radius the radius of the circle * @return the area of the circle */ public static double areaOfCircleFromRadius(final double radius) { - return SIMPLE_PI * (radius * radius); + return Math.PI * (radius * radius); } /** - * Calculates the area of a circle from its diameter, using pi = 3.14 + * Calculates the area of a circle from its diameter * * @param diameter the diameter of the circle * @return the area of the circle @@ -58,13 +54,13 @@ public static double radiusOfCircleFromDiameter(final double diameter) { } /** - * Calculates the radius of a circle from its area, using pi = 3.14 + * Calculates the radius of a circle from its area * * @param area the area of the circle * @return the radius of the circle */ public static double radiusOfCircleFromArea(final double area) { - return Math.sqrt(area / SIMPLE_PI); + return Math.sqrt(area / Math.PI); } /** @@ -78,14 +74,14 @@ public static long diagonalsOfPolygonWithSides(final long sides) { } /** - * Calculates the height of a cylinder based on its volume and base radius, using pi = 3.14 + * Calculates the height of a cylinder based on its volume and base radius * * @param volume the cylinder volume * @param baseRadius the cylinder base radius * @return the height of the cylinder */ public static double heightOfCylinder(final double volume, final double baseRadius) { - return volume / (SIMPLE_PI * baseRadius * baseRadius); + return volume / (Math.PI * baseRadius * baseRadius); } /** diff --git a/src/main/java/io/github/diegourban/algorithms/math/Statistics.java b/src/main/java/io/github/diegourban/algorithms/math/Statistics.java index b90c1ab..9401ba1 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/Statistics.java +++ b/src/main/java/io/github/diegourban/algorithms/math/Statistics.java @@ -32,24 +32,24 @@ public static double mean(final double[] data) { /** * The median is the value separating the higher half of a data sample from the lower half. *

- * Note: {@code data} is sorted in place, so the caller's array is modified. Pass a copy to keep the original - * order. + * Sorts a copy of {@code data}, so the caller's array is left unchanged (O(n) extra space). * - * @param data the values; must not be empty, and is sorted ascending as a side effect + * @param data the values; must not be empty * @return the middle value for an odd number of values, or the mean of the two middle values for an even number * @throws IllegalArgumentException if {@code data} is empty */ public static double median(final double[] data) { requireNotEmpty(data); - Arrays.sort(data); + final double[] sorted = data.clone(); + Arrays.sort(sorted); - final int middle = data.length / 2; + final int middle = sorted.length / 2; - if (data.length % 2 == 1) { - return data[middle]; + if (sorted.length % 2 == 1) { + return sorted[middle]; } - return (data[middle - 1] + data[middle]) / 2.0; + return (sorted[middle - 1] + sorted[middle]) / 2.0; } /** diff --git a/src/test/java/io/github/diegourban/algorithms/datastructure/linkedlist/LinkedListTest.java b/src/test/java/io/github/diegourban/algorithms/datastructure/linkedlist/LinkedListTest.java index ba006fa..44bf61c 100644 --- a/src/test/java/io/github/diegourban/algorithms/datastructure/linkedlist/LinkedListTest.java +++ b/src/test/java/io/github/diegourban/algorithms/datastructure/linkedlist/LinkedListTest.java @@ -121,7 +121,7 @@ void shouldPrint() { linkedList.addAtEnd(new Element("paulo")); linkedList.addAtEnd(new Element("josé")); - assertThat(linkedList).hasToString("[diego,paulo,josé,]"); + assertThat(linkedList).hasToString("[diego,paulo,josé]"); } @Test @@ -139,7 +139,7 @@ void shouldAddAtPositionEqualToSizeAsLast() { linkedList.addAtEnd(new Element("diego")); linkedList.add(1, new Element("paulo")); - assertThat(linkedList).hasToString("[diego,paulo,]"); + assertThat(linkedList).hasToString("[diego,paulo]"); } @Test @@ -184,10 +184,10 @@ void shouldRemoveFirstAndLastByPosition() { linkedList.addAtEnd(new Element("josé")); linkedList.remove(0); - assertThat(linkedList).hasToString("[paulo,josé,]"); + assertThat(linkedList).hasToString("[paulo,josé]"); linkedList.remove(1); - assertThat(linkedList).hasToString("[paulo,]"); + assertThat(linkedList).hasToString("[paulo]"); assertThat(linkedList.size()).isEqualTo(1); } @@ -204,7 +204,7 @@ void shouldAddAtPositionZeroOfNonEmptyList() { linkedList.add(0, new Element("diego")); - assertThat(linkedList).hasToString("[diego,paulo,josé,]"); + assertThat(linkedList).hasToString("[diego,paulo,josé]"); assertThat(linkedList.getFirst().getNext().getPrevious().getElement()).isEqualTo(new Element("diego")); } diff --git a/src/test/java/io/github/diegourban/algorithms/datastructure/vector/VectorTest.java b/src/test/java/io/github/diegourban/algorithms/datastructure/vector/VectorTest.java index bd883a6..4644c06 100644 --- a/src/test/java/io/github/diegourban/algorithms/datastructure/vector/VectorTest.java +++ b/src/test/java/io/github/diegourban/algorithms/datastructure/vector/VectorTest.java @@ -113,4 +113,14 @@ void shouldRejectRemovingFromEmptyVector() { assertThatIllegalArgumentException().isThrownBy(() -> vector.remove(0)); assertThat(vector.size()).isZero(); } + + @Test + void shouldPrintOnlyTheStoredElements() { + final Vector vector = new Vector(); + vector.add(new Element("Joao")); + vector.add(new Element("Jose")); + + assertThat(vector).hasToString("Vector{elements=[Joao, Jose]}"); + assertThat(new Vector()).hasToString("Vector{elements=[]}"); + } } diff --git a/src/test/java/io/github/diegourban/algorithms/math/GeometryTest.java b/src/test/java/io/github/diegourban/algorithms/math/GeometryTest.java index be7b6d2..2e8ed40 100644 --- a/src/test/java/io/github/diegourban/algorithms/math/GeometryTest.java +++ b/src/test/java/io/github/diegourban/algorithms/math/GeometryTest.java @@ -93,4 +93,12 @@ void shouldConsiderInvalidRectangleTriangle() { assertThat(Geometry.isRectangleTriangle(6, 9, 10)).isFalse(); assertThat(Geometry.isRectangleTriangle(6, 8, 11)).isFalse(); } + + @Test + void shouldUseThePreciseValueOfPi() { + assertThat(Geometry.areaOfCircleFromRadius(1)).isCloseTo(Math.PI, within(1e-12)); + assertThat(Geometry.areaOfCircleFromDiameter(2)).isCloseTo(Math.PI, within(1e-12)); + assertThat(Geometry.radiusOfCircleFromArea(Math.PI)).isCloseTo(1, within(1e-12)); + assertThat(Geometry.heightOfCylinder(Math.PI, 1)).isCloseTo(1, within(1e-12)); + } } diff --git a/src/test/java/io/github/diegourban/algorithms/math/StatisticsTest.java b/src/test/java/io/github/diegourban/algorithms/math/StatisticsTest.java index 25da7c0..4f8b4c4 100644 --- a/src/test/java/io/github/diegourban/algorithms/math/StatisticsTest.java +++ b/src/test/java/io/github/diegourban/algorithms/math/StatisticsTest.java @@ -58,4 +58,12 @@ void shouldRejectEmptyDataForMedianAndDeviation() { .isThrownBy(() -> Statistics.meanAbsoluteDeviation(new double[] {})) .withMessage("data must not be empty"); } + + @Test + void shouldNotReorderTheDataPassedToMedian() { + final double[] data = {3, 1, 2}; + + assertThat(Statistics.median(data)).isEqualTo(2.0); + assertThat(data).containsExactly(3, 1, 2); + } } From c7449a941aaa2edbb426f357bd2910d88f075aa2 Mon Sep 17 00:00:00 2001 From: Diego Leonardo Urban Date: Fri, 6 Nov 2026 17:30:34 +0100 Subject: [PATCH 2/6] Rename public API names (N12, N13, N15-N17, N19, N26, N28) - Numbers.toArray -> toReversedDigits, Numbers.length -> digitCount - Geometry.isRectangleTriangle -> isRightTriangle, checkTriangleType -> classifyTriangle - TriangleType.ESCALENE -> SCALENE, getName() -> getDisplayName() - datastructure.set.Set -> WordSet (no longer shadows java.util.Set); its table/list -> buckets/bucket - Sequences.fibonacciSimpleBinetFormula -> fibonacciBinetApproximate; powInvrt -> negativePhiPower Callers (tests only) updated in the same commit. Co-Authored-By: Claude Opus 5.5 --- .../set/{Set.java => WordSet.java} | 22 +++++------ .../diegourban/algorithms/math/Geometry.java | 8 ++-- .../diegourban/algorithms/math/Numbers.java | 20 +++++----- .../diegourban/algorithms/math/Sequences.java | 8 ++-- .../algorithms/math/TriangleType.java | 16 ++++---- .../set/{SetTest.java => WordSetTest.java} | 12 +++--- .../algorithms/math/GeometryTest.java | 26 ++++++------- .../algorithms/math/NumbersTest.java | 38 +++++++++---------- .../math/SequencesPropertyTest.java | 2 +- .../algorithms/math/SequencesTest.java | 18 ++++----- .../algorithms/math/TriangleTypeTest.java | 4 +- 11 files changed, 88 insertions(+), 86 deletions(-) rename src/main/java/io/github/diegourban/algorithms/datastructure/set/{Set.java => WordSet.java} (77%) rename src/test/java/io/github/diegourban/algorithms/datastructure/set/{SetTest.java => WordSetTest.java} (89%) diff --git a/src/main/java/io/github/diegourban/algorithms/datastructure/set/Set.java b/src/main/java/io/github/diegourban/algorithms/datastructure/set/WordSet.java similarity index 77% rename from src/main/java/io/github/diegourban/algorithms/datastructure/set/Set.java rename to src/main/java/io/github/diegourban/algorithms/datastructure/set/WordSet.java index 674d9e5..bf24813 100644 --- a/src/main/java/io/github/diegourban/algorithms/datastructure/set/Set.java +++ b/src/main/java/io/github/diegourban/algorithms/datastructure/set/WordSet.java @@ -8,16 +8,16 @@ * the character code of its lower-cased first character modulo 26, so buckets do not line up with alphabetical * order ({@code 'a'} goes to bucket 19). */ -public class Set { +public class WordSet { private static final int BUCKET_COUNT = 26; - private final List> table = new LinkedList>(); + private final List> buckets = new LinkedList>(); /** Creates an empty set with 26 empty buckets. */ - public Set() { + public WordSet() { for (int i = 0; i < BUCKET_COUNT; i++) { - this.table.add(new LinkedList()); + this.buckets.add(new LinkedList()); } } @@ -30,8 +30,8 @@ public Set() { public void add(final String word) { if (!contains(word)) { final int index = calculateTableIndex(word); - final List list = this.table.get(index); - list.add(word); + final List bucket = this.buckets.get(index); + bucket.add(word); } } @@ -44,14 +44,14 @@ public void add(final String word) { public void remove(final String word) { if (contains(word)) { final int index = calculateTableIndex(word); - final List list = this.table.get(index); - list.remove(word); + final List bucket = this.buckets.get(index); + bucket.remove(word); } } private boolean contains(final String word) { final int index = calculateTableIndex(word); - return this.table.get(index).contains(word); + return this.buckets.get(index).contains(word); } private int calculateTableIndex(final String word) { @@ -65,10 +65,10 @@ private int calculateTableIndex(final String word) { * Returns all 26 buckets in index order, each in {@link java.util.AbstractCollection#toString()} format, for * example {@code "[[], [], ..., [apple], ...]"}. * - * @return the string form of the bucket table + * @return the string form of the bucket buckets */ @Override public String toString() { - return this.table.toString(); + return this.buckets.toString(); } } diff --git a/src/main/java/io/github/diegourban/algorithms/math/Geometry.java b/src/main/java/io/github/diegourban/algorithms/math/Geometry.java index fb11462..d9fc79d 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/Geometry.java +++ b/src/main/java/io/github/diegourban/algorithms/math/Geometry.java @@ -107,9 +107,9 @@ public static boolean isValidTriangle(final double a, final double b, final doub * @param b side b * @param c side c * @return {@link TriangleType#EQUILATERAL} if all three sides are equal, {@link TriangleType#ISOSCELES} if exactly - * two are, {@link TriangleType#ESCALENE} otherwise + * two are, {@link TriangleType#SCALENE} otherwise */ - public static TriangleType checkTriangleType(final double a, final double b, final double c) { + public static TriangleType classifyTriangle(final double a, final double b, final double c) { final double[] sortedSides = new double[3]; sortedSides[0] = a; sortedSides[1] = b; @@ -128,7 +128,7 @@ public static TriangleType checkTriangleType(final double a, final double b, fin return TriangleType.ISOSCELES; } - return TriangleType.ESCALENE; + return TriangleType.SCALENE; } /** @@ -143,7 +143,7 @@ public static TriangleType checkTriangleType(final double a, final double b, fin * @param c side c * @return {@code true} if the sides satisfy the Pythagorean theorem, {@code false} otherwise */ - public static boolean isRectangleTriangle(final double a, final double b, final double c) { + public static boolean isRightTriangle(final double a, final double b, final double c) { final double[] sortedSides = new double[3]; sortedSides[0] = a; sortedSides[1] = b; diff --git a/src/main/java/io/github/diegourban/algorithms/math/Numbers.java b/src/main/java/io/github/diegourban/algorithms/math/Numbers.java index 36749be..c15efed 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/Numbers.java +++ b/src/main/java/io/github/diegourban/algorithms/math/Numbers.java @@ -77,31 +77,31 @@ public static boolean containsRepeatingDigits(final long number, final int times * @param number the number to convert * @return an array with each digit of the number, in reverse order */ - public static int[] toArray(final long number) { - long theNumber = number; + public static int[] toReversedDigits(final long number) { + long remaining = number; - final int length = Long.toString(theNumber).length() - (theNumber < 0 ? 1 : 0); - final int[] array = new int[length]; + final int length = Long.toString(remaining).length() - (remaining < 0 ? 1 : 0); + final int[] digits = new int[length]; for (int index = 0; index < length; index++) { - array[index] = Math.abs((int) (theNumber % 10)); - theNumber /= 10; + digits[index] = Math.abs((int) (remaining % 10)); + remaining /= 10; } - return array; + return digits; } /** * Find the length of the int using divide and conquer *

- * A negative number counts the digits of its absolute value: {@code length(-123)} is {@code 3}. + * A negative number counts the digits of its absolute value: {@code digitCount(-123)} is {@code 3}. * * @param number some number * @return the number of decimal digits of the number, from 1 to 10 */ - public static int length(final int number) { + public static int digitCount(final int number) { if (number < 0) { - return number == Integer.MIN_VALUE ? 10 : length(-number); + return number == Integer.MIN_VALUE ? 10 : digitCount(-number); } if (number < 100000) { // 5 or less diff --git a/src/main/java/io/github/diegourban/algorithms/math/Sequences.java b/src/main/java/io/github/diegourban/algorithms/math/Sequences.java index b293ccb..1cbf190 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/Sequences.java +++ b/src/main/java/io/github/diegourban/algorithms/math/Sequences.java @@ -64,7 +64,7 @@ public static int fibonacci(final int n) { * @param n the nth number in the Fibonacci sequence to calculate * @return an approximation of the nth Fibonacci number */ - public static double fibonacciSimpleBinetFormula(final int n) { + public static double fibonacciBinetApproximate(final int n) { if (n == 0) { return 0; } @@ -96,7 +96,9 @@ public static BigInteger fibonacciBinetFormula(final int n) { final BigDecimal sqrt5 = BigDecimal.valueOf(SQRT_5); final BigDecimal pow = BigDecimal.valueOf(Math.pow(GOLDEN_RATIO, n)); - final BigDecimal powInvrt = BigDecimal.valueOf(Math.pow(-GOLDEN_RATIO, -n)); - return pow.subtract(powInvrt).divide(sqrt5, RoundingMode.HALF_UP).toBigInteger(); + final BigDecimal negativePhiPower = BigDecimal.valueOf(Math.pow(-GOLDEN_RATIO, -n)); + return pow.subtract(negativePhiPower) + .divide(sqrt5, RoundingMode.HALF_UP) + .toBigInteger(); } } diff --git a/src/main/java/io/github/diegourban/algorithms/math/TriangleType.java b/src/main/java/io/github/diegourban/algorithms/math/TriangleType.java index f29711b..2e3d854 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/TriangleType.java +++ b/src/main/java/io/github/diegourban/algorithms/math/TriangleType.java @@ -2,28 +2,28 @@ /** * Classification of a triangle by how many of its sides are equal, as returned by - * {@link Geometry#checkTriangleType(double, double, double)}. + * {@link Geometry#classifyTriangle(double, double, double)}. */ public enum TriangleType { /** All three sides are equal. */ EQUILATERAL("Equilateral"), /** No two sides are equal (scalene). */ - ESCALENE("Scalene"), + SCALENE("Scalene"), /** Exactly two sides are equal. */ ISOSCELES("Isosceles"); - private final String name; + private final String displayName; - private TriangleType(final String name) { - this.name = name; + private TriangleType(final String displayName) { + this.displayName = displayName; } /** - * Returns the display name of this type, in Portuguese. + * Returns the display name of this type, in English. * * @return the display name, such as {@code "Equilateral"} */ - public String getName() { - return name; + public String getDisplayName() { + return displayName; } } diff --git a/src/test/java/io/github/diegourban/algorithms/datastructure/set/SetTest.java b/src/test/java/io/github/diegourban/algorithms/datastructure/set/WordSetTest.java similarity index 89% rename from src/test/java/io/github/diegourban/algorithms/datastructure/set/SetTest.java rename to src/test/java/io/github/diegourban/algorithms/datastructure/set/WordSetTest.java index 0e22333..5b837a3 100644 --- a/src/test/java/io/github/diegourban/algorithms/datastructure/set/SetTest.java +++ b/src/test/java/io/github/diegourban/algorithms/datastructure/set/WordSetTest.java @@ -5,11 +5,11 @@ import org.junit.jupiter.api.Test; -class SetTest { +class WordSetTest { @Test void shouldAdd() { - final Set set = new Set(); + final WordSet set = new WordSet(); set.add("Diego"); set.add("André"); set.add("Zuleika"); @@ -20,7 +20,7 @@ void shouldAdd() { @Test void shouldRemove() { - final Set set = new Set(); + final WordSet set = new WordSet(); set.add("Diego"); set.add("André"); set.add("Zuleika"); @@ -35,7 +35,7 @@ void shouldRemove() { @Test void shouldIgnoreDuplicates() { - final Set set = new Set(); + final WordSet set = new WordSet(); set.add("Diego"); set.add("Diego"); @@ -46,7 +46,7 @@ void shouldIgnoreDuplicates() { @Test void shouldIgnoreRemovingAMissingWord() { - final Set set = new Set(); + final WordSet set = new WordSet(); set.add("Diego"); set.remove("Daniel"); @@ -57,7 +57,7 @@ void shouldIgnoreRemovingAMissingWord() { @Test void shouldRejectAnEmptyWord() { - final Set set = new Set(); + final WordSet set = new WordSet(); assertThatIllegalArgumentException().isThrownBy(() -> set.add("")).withMessage("word must not be empty"); assertThatIllegalArgumentException().isThrownBy(() -> set.remove("")).withMessage("word must not be empty"); diff --git a/src/test/java/io/github/diegourban/algorithms/math/GeometryTest.java b/src/test/java/io/github/diegourban/algorithms/math/GeometryTest.java index 2e8ed40..ecab404 100644 --- a/src/test/java/io/github/diegourban/algorithms/math/GeometryTest.java +++ b/src/test/java/io/github/diegourban/algorithms/math/GeometryTest.java @@ -63,35 +63,35 @@ void shouldConsiderInvalidTriangles() { @Test void shouldConsiderAsEquilateralTriangle() { - assertThat(Geometry.checkTriangleType(6, 6, 6)).isEqualTo(TriangleType.EQUILATERAL); - assertThat(Geometry.checkTriangleType(2.2, 2.2, 2.2)).isEqualTo(TriangleType.EQUILATERAL); - assertThat(Geometry.checkTriangleType(1, 1, 1)).isEqualTo(TriangleType.EQUILATERAL); + assertThat(Geometry.classifyTriangle(6, 6, 6)).isEqualTo(TriangleType.EQUILATERAL); + assertThat(Geometry.classifyTriangle(2.2, 2.2, 2.2)).isEqualTo(TriangleType.EQUILATERAL); + assertThat(Geometry.classifyTriangle(1, 1, 1)).isEqualTo(TriangleType.EQUILATERAL); } @Test void shouldConsiderAsIsoscelesTriangle() { - assertThat(Geometry.checkTriangleType(6, 6, 7)).isEqualTo(TriangleType.ISOSCELES); - assertThat(Geometry.checkTriangleType(2.2, 2.1, 2.2)).isEqualTo(TriangleType.ISOSCELES); - assertThat(Geometry.checkTriangleType(2, 1, 1)).isEqualTo(TriangleType.ISOSCELES); + assertThat(Geometry.classifyTriangle(6, 6, 7)).isEqualTo(TriangleType.ISOSCELES); + assertThat(Geometry.classifyTriangle(2.2, 2.1, 2.2)).isEqualTo(TriangleType.ISOSCELES); + assertThat(Geometry.classifyTriangle(2, 1, 1)).isEqualTo(TriangleType.ISOSCELES); } @Test void shouldConsiderAsEscaleneTriangle() { - assertThat(Geometry.checkTriangleType(6, 7, 8)).isEqualTo(TriangleType.ESCALENE); - assertThat(Geometry.checkTriangleType(2.2, 2.3, 2.4)).isEqualTo(TriangleType.ESCALENE); - assertThat(Geometry.checkTriangleType(3, 2, 1)).isEqualTo(TriangleType.ESCALENE); + assertThat(Geometry.classifyTriangle(6, 7, 8)).isEqualTo(TriangleType.SCALENE); + assertThat(Geometry.classifyTriangle(2.2, 2.3, 2.4)).isEqualTo(TriangleType.SCALENE); + assertThat(Geometry.classifyTriangle(3, 2, 1)).isEqualTo(TriangleType.SCALENE); } @Test void shouldConsiderValidRectangleTriangle() { - assertThat(Geometry.isRectangleTriangle(6, 8, 10)).isTrue(); + assertThat(Geometry.isRightTriangle(6, 8, 10)).isTrue(); } @Test void shouldConsiderInvalidRectangleTriangle() { - assertThat(Geometry.isRectangleTriangle(7, 8, 10)).isFalse(); - assertThat(Geometry.isRectangleTriangle(6, 9, 10)).isFalse(); - assertThat(Geometry.isRectangleTriangle(6, 8, 11)).isFalse(); + assertThat(Geometry.isRightTriangle(7, 8, 10)).isFalse(); + assertThat(Geometry.isRightTriangle(6, 9, 10)).isFalse(); + assertThat(Geometry.isRightTriangle(6, 8, 11)).isFalse(); } @Test diff --git a/src/test/java/io/github/diegourban/algorithms/math/NumbersTest.java b/src/test/java/io/github/diegourban/algorithms/math/NumbersTest.java index 38ab033..e3b86cc 100644 --- a/src/test/java/io/github/diegourban/algorithms/math/NumbersTest.java +++ b/src/test/java/io/github/diegourban/algorithms/math/NumbersTest.java @@ -21,34 +21,34 @@ void shouldHandleContainsRepatingDigit() { @Test void shouldConvertNumberToArray() { - assertThat(Numbers.toArray(123)).containsExactly(new int[] {3, 2, 1}); - assertThat(Numbers.toArray(321)).containsExactly(new int[] {1, 2, 3}); + assertThat(Numbers.toReversedDigits(123)).containsExactly(new int[] {3, 2, 1}); + assertThat(Numbers.toReversedDigits(321)).containsExactly(new int[] {1, 2, 3}); } @Test void shouldCalculateNumberLength() { - assertThat(Numbers.length(1)).isEqualTo(1); - assertThat(Numbers.length(12)).isEqualTo(2); - assertThat(Numbers.length(123)).isEqualTo(3); - assertThat(Numbers.length(1234)).isEqualTo(4); - assertThat(Numbers.length(12345)).isEqualTo(5); - assertThat(Numbers.length(123456)).isEqualTo(6); - assertThat(Numbers.length(1234567)).isEqualTo(7); - assertThat(Numbers.length(12345678)).isEqualTo(8); - assertThat(Numbers.length(123456789)).isEqualTo(9); - assertThat(Numbers.length(1234567890)).isEqualTo(10); + assertThat(Numbers.digitCount(1)).isEqualTo(1); + assertThat(Numbers.digitCount(12)).isEqualTo(2); + assertThat(Numbers.digitCount(123)).isEqualTo(3); + assertThat(Numbers.digitCount(1234)).isEqualTo(4); + assertThat(Numbers.digitCount(12345)).isEqualTo(5); + assertThat(Numbers.digitCount(123456)).isEqualTo(6); + assertThat(Numbers.digitCount(1234567)).isEqualTo(7); + assertThat(Numbers.digitCount(12345678)).isEqualTo(8); + assertThat(Numbers.digitCount(123456789)).isEqualTo(9); + assertThat(Numbers.digitCount(1234567890)).isEqualTo(10); } @Test void shouldSplitNumbersBeyondTheIntRangeIntoDigits() { - assertThat(Numbers.toArray(12345678901L)).containsExactly(1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1); - assertThat(Numbers.toArray(Long.MAX_VALUE)).hasSize(19).startsWith(7, 0, 8); + assertThat(Numbers.toReversedDigits(12345678901L)).containsExactly(1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1); + assertThat(Numbers.toReversedDigits(Long.MAX_VALUE)).hasSize(19).startsWith(7, 0, 8); } @Test void shouldSplitNegativeNumbersIntoTheDigitsOfTheirAbsoluteValue() { - assertThat(Numbers.toArray(-12)).containsExactly(2, 1); - assertThat(Numbers.toArray(Long.MIN_VALUE)).hasSize(19).startsWith(8, 0, 8); + assertThat(Numbers.toReversedDigits(-12)).containsExactly(2, 1); + assertThat(Numbers.toReversedDigits(Long.MIN_VALUE)).hasSize(19).startsWith(8, 0, 8); } @Test @@ -59,8 +59,8 @@ void shouldFindDigitZeroInZero() { @Test void shouldCountDigitsOfNegativeNumbers() { - assertThat(Numbers.length(-123)).isEqualTo(3); - assertThat(Numbers.length(-7)).isEqualTo(1); - assertThat(Numbers.length(Integer.MIN_VALUE)).isEqualTo(10); + assertThat(Numbers.digitCount(-123)).isEqualTo(3); + assertThat(Numbers.digitCount(-7)).isEqualTo(1); + assertThat(Numbers.digitCount(Integer.MIN_VALUE)).isEqualTo(10); } } diff --git a/src/test/java/io/github/diegourban/algorithms/math/SequencesPropertyTest.java b/src/test/java/io/github/diegourban/algorithms/math/SequencesPropertyTest.java index ce3cf8f..b4aaf3e 100644 --- a/src/test/java/io/github/diegourban/algorithms/math/SequencesPropertyTest.java +++ b/src/test/java/io/github/diegourban/algorithms/math/SequencesPropertyTest.java @@ -20,7 +20,7 @@ void fibonacciIsExactUpTo46(@ForAll @IntRange(min = 0, max = 46) final int n) { @Property(generation = GenerationMode.EXHAUSTIVE) void simpleBinetFormulaRoundsToTheExactValueUpTo70(@ForAll @IntRange(min = 0, max = 70) final int n) { - assertThat(BigInteger.valueOf(Math.round(Sequences.fibonacciSimpleBinetFormula(n)))) + assertThat(BigInteger.valueOf(Math.round(Sequences.fibonacciBinetApproximate(n)))) .isEqualTo(exactFibonacci(n)); } diff --git a/src/test/java/io/github/diegourban/algorithms/math/SequencesTest.java b/src/test/java/io/github/diegourban/algorithms/math/SequencesTest.java index 62dcf76..5b386c9 100644 --- a/src/test/java/io/github/diegourban/algorithms/math/SequencesTest.java +++ b/src/test/java/io/github/diegourban/algorithms/math/SequencesTest.java @@ -23,15 +23,15 @@ void shouldCalculateFibonacci() { @Test void shouldCalculateFibonacciWithSimpleBinetFormula() { - assertThat(Sequences.fibonacciSimpleBinetFormula(0)).isCloseTo(0, within(0.1)); - assertThat(Sequences.fibonacciSimpleBinetFormula(1)).isCloseTo(1, within(0.1)); - assertThat(Sequences.fibonacciSimpleBinetFormula(2)).isCloseTo(1, within(0.1)); - assertThat(Sequences.fibonacciSimpleBinetFormula(3)).isCloseTo(2, within(0.1)); - assertThat(Sequences.fibonacciSimpleBinetFormula(4)).isCloseTo(3, within(0.1)); - assertThat(Sequences.fibonacciSimpleBinetFormula(5)).isCloseTo(5, within(0.1)); - assertThat(Sequences.fibonacciSimpleBinetFormula(11)).isCloseTo(89, within(0.1)); - assertThat(Sequences.fibonacciSimpleBinetFormula(50)).isCloseTo(12586269025d, within(0.1)); - assertThat(Sequences.fibonacciSimpleBinetFormula(67)).isCloseTo(44945570212853d, within(0.1)); + assertThat(Sequences.fibonacciBinetApproximate(0)).isCloseTo(0, within(0.1)); + assertThat(Sequences.fibonacciBinetApproximate(1)).isCloseTo(1, within(0.1)); + assertThat(Sequences.fibonacciBinetApproximate(2)).isCloseTo(1, within(0.1)); + assertThat(Sequences.fibonacciBinetApproximate(3)).isCloseTo(2, within(0.1)); + assertThat(Sequences.fibonacciBinetApproximate(4)).isCloseTo(3, within(0.1)); + assertThat(Sequences.fibonacciBinetApproximate(5)).isCloseTo(5, within(0.1)); + assertThat(Sequences.fibonacciBinetApproximate(11)).isCloseTo(89, within(0.1)); + assertThat(Sequences.fibonacciBinetApproximate(50)).isCloseTo(12586269025d, within(0.1)); + assertThat(Sequences.fibonacciBinetApproximate(67)).isCloseTo(44945570212853d, within(0.1)); } @Test diff --git a/src/test/java/io/github/diegourban/algorithms/math/TriangleTypeTest.java b/src/test/java/io/github/diegourban/algorithms/math/TriangleTypeTest.java index ceb7649..8f1cd39 100644 --- a/src/test/java/io/github/diegourban/algorithms/math/TriangleTypeTest.java +++ b/src/test/java/io/github/diegourban/algorithms/math/TriangleTypeTest.java @@ -8,8 +8,8 @@ class TriangleTypeTest { @ParameterizedTest - @CsvSource({"EQUILATERAL, Equilateral", "ISOSCELES, Isosceles", "ESCALENE, Scalene"}) + @CsvSource({"EQUILATERAL, Equilateral", "ISOSCELES, Isosceles", "SCALENE, Scalene"}) void exposesDisplayName(final TriangleType type, final String expectedName) { - assertThat(type.getName()).isEqualTo(expectedName); + assertThat(type.getDisplayName()).isEqualTo(expectedName); } } From 45e685c8f62d76a5f06725a485afcc080f781a58 Mon Sep 17 00:00:00 2001 From: Diego Leonardo Urban Date: Fri, 6 Nov 2026 17:31:33 +0100 Subject: [PATCH 3/6] Small structural cleanups (L2, L5, O4, O5, O6, O12, O14) - L2: Geometry.sortedSides(a, b, c) replaces the block duplicated in classifyTriangle and isRightTriangle - L5/O12: WordSet looks a word's bucket up once (bucketFor) and keeps its buckets in an ArrayList, so get(i) is O(1) - O4: Vector.isPositionValid -> isInsertablePosition; since B6 it accepts size() and no longer duplicates positionOccupied - O5: Vector grows with Arrays.copyOf - O6: LinkedList.add reuses the next node it already looked up - O14: braces and early returns in Numbers.digitCount, NumberTheory.lcm(long[]) and Statistics.mode; same comparisons in the same order Co-Authored-By: Claude Opus 5.5 --- .../datastructure/linkedlist/LinkedList.java | 2 +- .../algorithms/datastructure/set/WordSet.java | 23 ++++------- .../datastructure/vector/Vector.java | 10 ++--- .../diegourban/algorithms/math/Geometry.java | 18 ++++---- .../algorithms/math/NumberTheory.java | 4 +- .../diegourban/algorithms/math/Numbers.java | 41 ++++++++----------- .../algorithms/math/Statistics.java | 4 +- 7 files changed, 44 insertions(+), 58 deletions(-) diff --git a/src/main/java/io/github/diegourban/algorithms/datastructure/linkedlist/LinkedList.java b/src/main/java/io/github/diegourban/algorithms/datastructure/linkedlist/LinkedList.java index 20ba77e..aabde42 100644 --- a/src/main/java/io/github/diegourban/algorithms/datastructure/linkedlist/LinkedList.java +++ b/src/main/java/io/github/diegourban/algorithms/datastructure/linkedlist/LinkedList.java @@ -72,7 +72,7 @@ public void add(final int position, final Element element) { final LinkedListNode previous = this.getNode(position - 1); final LinkedListNode next = previous.getNext(); - final LinkedListNode newNode = new LinkedListNode(element, previous.getNext()); + final LinkedListNode newNode = new LinkedListNode(element, next); newNode.setPrevious(previous); previous.setNext(newNode); next.setPrevious(newNode); diff --git a/src/main/java/io/github/diegourban/algorithms/datastructure/set/WordSet.java b/src/main/java/io/github/diegourban/algorithms/datastructure/set/WordSet.java index bf24813..e85ef4a 100644 --- a/src/main/java/io/github/diegourban/algorithms/datastructure/set/WordSet.java +++ b/src/main/java/io/github/diegourban/algorithms/datastructure/set/WordSet.java @@ -1,5 +1,6 @@ package io.github.diegourban.algorithms.datastructure.set; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -12,12 +13,12 @@ public class WordSet { private static final int BUCKET_COUNT = 26; - private final List> buckets = new LinkedList>(); + private final List> buckets = new ArrayList<>(BUCKET_COUNT); /** Creates an empty set with 26 empty buckets. */ public WordSet() { for (int i = 0; i < BUCKET_COUNT; i++) { - this.buckets.add(new LinkedList()); + this.buckets.add(new LinkedList<>()); } } @@ -28,9 +29,8 @@ public WordSet() { * @throws IllegalArgumentException if {@code word} is empty */ public void add(final String word) { - if (!contains(word)) { - final int index = calculateTableIndex(word); - final List bucket = this.buckets.get(index); + final List bucket = bucketFor(word); + if (!bucket.contains(word)) { bucket.add(word); } } @@ -42,16 +42,11 @@ public void add(final String word) { * @throws IllegalArgumentException if {@code word} is empty */ public void remove(final String word) { - if (contains(word)) { - final int index = calculateTableIndex(word); - final List bucket = this.buckets.get(index); - bucket.remove(word); - } + bucketFor(word).remove(word); } - private boolean contains(final String word) { - final int index = calculateTableIndex(word); - return this.buckets.get(index).contains(word); + private List bucketFor(final String word) { + return this.buckets.get(calculateTableIndex(word)); } private int calculateTableIndex(final String word) { @@ -65,7 +60,7 @@ private int calculateTableIndex(final String word) { * Returns all 26 buckets in index order, each in {@link java.util.AbstractCollection#toString()} format, for * example {@code "[[], [], ..., [apple], ...]"}. * - * @return the string form of the bucket buckets + * @return the string form of the buckets */ @Override public String toString() { diff --git a/src/main/java/io/github/diegourban/algorithms/datastructure/vector/Vector.java b/src/main/java/io/github/diegourban/algorithms/datastructure/vector/Vector.java index f24f4aa..7937ceb 100644 --- a/src/main/java/io/github/diegourban/algorithms/datastructure/vector/Vector.java +++ b/src/main/java/io/github/diegourban/algorithms/datastructure/vector/Vector.java @@ -38,7 +38,7 @@ public void add(final Element element) { * @throws IllegalArgumentException if {@code position} is negative or greater than {@link #size()} */ public void add(final int position, final Element element) { - if (!isPositionValid(position)) { + if (!isInsertablePosition(position)) { throw new IllegalArgumentException("Invalid position"); } @@ -50,7 +50,7 @@ public void add(final int position, final Element element) { this.totalElements++; } - private boolean isPositionValid(final int position) { + private boolean isInsertablePosition(final int position) { return position >= 0 && position <= totalElements; } @@ -117,11 +117,7 @@ public int size() { private void ensureCapacity() { if (this.totalElements == this.elements.length) { - final Element[] newElements = new Element[this.elements.length * 2]; - for (int i = 0; i < this.elements.length; i++) { - newElements[i] = this.elements[i]; - } - this.elements = newElements; + this.elements = Arrays.copyOf(this.elements, this.elements.length * 2); } } diff --git a/src/main/java/io/github/diegourban/algorithms/math/Geometry.java b/src/main/java/io/github/diegourban/algorithms/math/Geometry.java index d9fc79d..bfff2c5 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/Geometry.java +++ b/src/main/java/io/github/diegourban/algorithms/math/Geometry.java @@ -110,11 +110,7 @@ public static boolean isValidTriangle(final double a, final double b, final doub * two are, {@link TriangleType#SCALENE} otherwise */ public static TriangleType classifyTriangle(final double a, final double b, final double c) { - final double[] sortedSides = new double[3]; - sortedSides[0] = a; - sortedSides[1] = b; - sortedSides[2] = c; - Arrays.sort(sortedSides); + final double[] sortedSides = sortedSides(a, b, c); final double shortest = sortedSides[0]; final double middle = sortedSides[1]; @@ -144,11 +140,7 @@ public static TriangleType classifyTriangle(final double a, final double b, fina * @return {@code true} if the sides satisfy the Pythagorean theorem, {@code false} otherwise */ public static boolean isRightTriangle(final double a, final double b, final double c) { - final double[] sortedSides = new double[3]; - sortedSides[0] = a; - sortedSides[1] = b; - sortedSides[2] = c; - Arrays.sort(sortedSides); + final double[] sortedSides = sortedSides(a, b, c); final double shortest = sortedSides[0]; final double middle = sortedSides[1]; @@ -160,4 +152,10 @@ public static boolean isRightTriangle(final double a, final double b, final doub return maxSquared == medSquared + minSquared; } + + private static double[] sortedSides(final double a, final double b, final double c) { + final double[] sides = {a, b, c}; + Arrays.sort(sides); + return sides; + } } diff --git a/src/main/java/io/github/diegourban/algorithms/math/NumberTheory.java b/src/main/java/io/github/diegourban/algorithms/math/NumberTheory.java index 39e8348..08eee16 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/NumberTheory.java +++ b/src/main/java/io/github/diegourban/algorithms/math/NumberTheory.java @@ -37,7 +37,9 @@ public static long lcm(final long a, final long b) { */ public static long lcm(final long[] input) { long result = input[0]; - for (int i = 1; i < input.length; i++) result = lcm(result, input[i]); + for (int i = 1; i < input.length; i++) { + result = lcm(result, input[i]); + } return result; } diff --git a/src/main/java/io/github/diegourban/algorithms/math/Numbers.java b/src/main/java/io/github/diegourban/algorithms/math/Numbers.java index c15efed..54c7675 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/Numbers.java +++ b/src/main/java/io/github/diegourban/algorithms/math/Numbers.java @@ -107,32 +107,25 @@ public static int digitCount(final int number) { // 5 or less if (number < 100) { // 1 or 2 - if (number < 10) return 1; - else return 2; - } else { - // 3 or 4 or 5 - if (number < 1000) return 3; - else { - // 4 or 5 - if (number < 10000) return 4; - else return 5; - } + return number < 10 ? 1 : 2; } - } else { - // 6 or more - if (number < 10000000) { - // 6 or 7 - if (number < 1000000) return 6; - else return 7; - } else { - // 8 to 10 - if (number < 100000000) return 8; - else { - // 9 or 10 - if (number < 1000000000) return 9; - else return 10; - } + // 3, 4 or 5 + if (number < 1000) { + return 3; } + // 4 or 5 + return number < 10000 ? 4 : 5; + } + // 6 or more + if (number < 10000000) { + // 6 or 7 + return number < 1000000 ? 6 : 7; + } + // 8 to 10 + if (number < 100000000) { + return 8; } + // 9 or 10 + return number < 1000000000 ? 9 : 10; } } diff --git a/src/main/java/io/github/diegourban/algorithms/math/Statistics.java b/src/main/java/io/github/diegourban/algorithms/math/Statistics.java index 9401ba1..044fcd6 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/Statistics.java +++ b/src/main/java/io/github/diegourban/algorithms/math/Statistics.java @@ -68,7 +68,9 @@ public static double mode(final double[] data) { for (int i = 0; i < data.length; ++i) { int count = 0; for (int j = 0; j < data.length; ++j) { - if (data[j] == data[i]) ++count; + if (data[j] == data[i]) { + ++count; + } } if (count > maxCount) { maxCount = count; From ae95c79da4fd98da82e7c216b1553e210ebc1a0b Mon Sep 17 00:00:00 2001 From: Diego Leonardo Urban Date: Fri, 6 Nov 2026 17:32:29 +0100 Subject: [PATCH 4/6] Reader: unlimited line length, shared whitespace skipping (B29, L1, O13) - B29: readLine grows its buffer (Arrays.copyOf, doubling from 64 bytes) instead of failing with ArrayIndexOutOfBoundsException on lines over 10,000 bytes; new test reads a 20,000-byte line - L1: skipWhitespace() replaces the loop copied into nextInt, nextLong, nextDouble and nextChar. The sign check stays inline: it yields both the sign and the next byte, which would need a holder object - O13: no chained assignment in the constructor (fields default to 0) and no assignment inside the read(...) argument Co-Authored-By: Claude Opus 5.5 --- .../diegourban/algorithms/io/Reader.java | 50 +++++++++---------- .../diegourban/algorithms/io/ReaderTest.java | 9 ++++ 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/main/java/io/github/diegourban/algorithms/io/Reader.java b/src/main/java/io/github/diegourban/algorithms/io/Reader.java index af6f308..cb730de 100644 --- a/src/main/java/io/github/diegourban/algorithms/io/Reader.java +++ b/src/main/java/io/github/diegourban/algorithms/io/Reader.java @@ -4,6 +4,7 @@ import java.io.EOFException; import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; /** * Fast, buffered reader of ASCII tokens and lines from an {@link InputStream}, for competitive programming. @@ -21,9 +22,11 @@ public class Reader { private static final int BUFFER_SIZE = 1 << 16; + private static final int INITIAL_LINE_CAPACITY = 64; private DataInputStream input; private final byte[] buffer; - private int bufferPointer, bytesRead; + private int bufferPointer; + private int bytesRead; /** * Creates a reader over the given stream. Nothing is read until the first read method is called. @@ -33,23 +36,20 @@ public class Reader { public Reader(final InputStream input) { this.input = new DataInputStream(input); buffer = new byte[BUFFER_SIZE]; - bufferPointer = bytesRead = 0; } /** * Reads the next non-empty line. *

* Leading {@code '\n'} and {@code '\r'} bytes are skipped, so blank lines are never returned; the line ends at - * the next {@code '\n'} or {@code '\r'}, which is consumed but not included. Lines longer than 10,000 bytes are - * not supported. + * the next {@code '\n'} or {@code '\r'}, which is consumed but not included. * * @return the line without its terminator; the remaining text, or an empty string, if the input ends first * @throws EOFException if a previous read already reached the end of the input - * @throws ArrayIndexOutOfBoundsException if the line is longer than 10,000 bytes * @throws IOException if the underlying stream fails */ public String readLine() throws IOException { - final byte[] line = new byte[10000]; // line length + byte[] line = new byte[INITIAL_LINE_CAPACITY]; int length = 0; int nextByte = 0; @@ -60,6 +60,9 @@ public String readLine() throws IOException { } break; } + if (length == line.length) { + line = Arrays.copyOf(line, line.length * 2); + } line[length++] = (byte) nextByte; } return new String(line, 0, length); @@ -77,11 +80,7 @@ public String readLine() throws IOException { */ public int nextInt() throws IOException { int result = 0; - byte nextByte = read(); - - while (nextByte <= ' ') { - nextByte = read(); - } + byte nextByte = skipWhitespace(); final boolean negative = (nextByte == '-'); if (negative) { @@ -107,11 +106,7 @@ public int nextInt() throws IOException { * @throws IOException if the underlying stream fails */ public char nextChar() throws IOException { - byte nextByte = read(); - - while (nextByte <= ' ') { - nextByte = read(); - } + final byte nextByte = skipWhitespace(); return (char) nextByte; } @@ -127,11 +122,7 @@ public char nextChar() throws IOException { */ public long nextLong() throws IOException { long result = 0; - byte nextByte = read(); - - while (nextByte <= ' ') { - nextByte = read(); - } + byte nextByte = skipWhitespace(); final boolean negative = (nextByte == '-'); if (negative) { @@ -162,11 +153,7 @@ public long nextLong() throws IOException { public double nextDouble() throws IOException { double result = 0; double div = 1; - byte nextByte = read(); - - while (nextByte <= ' ') { - nextByte = read(); - } + byte nextByte = skipWhitespace(); final boolean negative = (nextByte == '-'); if (negative) { @@ -204,12 +191,21 @@ public void skip(final int n) throws IOException { } private void fillBuffer() throws IOException { - bytesRead = input.read(buffer, bufferPointer = 0, BUFFER_SIZE); + bufferPointer = 0; + bytesRead = input.read(buffer, 0, BUFFER_SIZE); if (isEOF()) { buffer[0] = -1; } } + private byte skipWhitespace() throws IOException { + byte nextByte = read(); + while (nextByte <= ' ') { + nextByte = read(); + } + return nextByte; + } + private byte read() throws IOException { if (isEOF()) { throw new EOFException(); diff --git a/src/test/java/io/github/diegourban/algorithms/io/ReaderTest.java b/src/test/java/io/github/diegourban/algorithms/io/ReaderTest.java index ebaaa9b..2a4997f 100644 --- a/src/test/java/io/github/diegourban/algorithms/io/ReaderTest.java +++ b/src/test/java/io/github/diegourban/algorithms/io/ReaderTest.java @@ -508,4 +508,13 @@ void shouldEndLinesAtCarriageReturnsAndSkipBlankOnes() throws Exception { assertThat(reader.readLine()).isEqualTo("second"); assertThat(reader.readLine()).isEqualTo("third"); } + + @Test + void shouldReadLinesLongerThanTheInitialBuffer() throws Exception { + final String longLine = "x".repeat(20_000); + final Reader reader = new Reader(new java.io.ByteArrayInputStream((longLine + "\nnext").getBytes())); + + assertThat(reader.readLine()).isEqualTo(longLine); + assertThat(reader.readLine()).isEqualTo("next"); + } } From 749be5888d172956e2ccfb8205d9513b32dfc671 Mon Sep 17 00:00:00 2001 From: Diego Leonardo Urban Date: Fri, 6 Nov 2026 17:33:02 +0100 Subject: [PATCH 5/6] Shared int swap for the sorts and overflow-safe midpoint (L3, B27) - L3: public ArrayUtils.swap(int[], i, j) (documented and tested) is used by InsertionSort and SelectionSort, which swapped through boxed Integer temporaries, by QuickSort instead of its private copy, and by shuffle. The Sorter property tests cover all four sorts - B27: BinarySearch.find computes the midpoint as from + (to - from) / 2; (from + to) / 2 overflows for arrays above 2^30 elements. The recursion is unchanged Co-Authored-By: Claude Opus 5.5 --- .../datastructure/array/ArrayUtils.java | 18 +++++++++++++++--- .../algorithms/search/BinarySearch.java | 2 +- .../algorithms/sorting/InsertionSort.java | 7 +++---- .../algorithms/sorting/QuickSort.java | 12 ++++-------- .../algorithms/sorting/SelectionSort.java | 7 +++---- .../datastructure/array/ArrayUtilsTest.java | 11 +++++++++++ 6 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/main/java/io/github/diegourban/algorithms/datastructure/array/ArrayUtils.java b/src/main/java/io/github/diegourban/algorithms/datastructure/array/ArrayUtils.java index 682caf6..a685ab0 100644 --- a/src/main/java/io/github/diegourban/algorithms/datastructure/array/ArrayUtils.java +++ b/src/main/java/io/github/diegourban/algorithms/datastructure/array/ArrayUtils.java @@ -41,13 +41,25 @@ public static void shuffle(final int[] values) { // place our random choice in the spot by swapping if (randomChoiceIndex != targetIndex) { - final int displacedValue = values[targetIndex]; - values[targetIndex] = values[randomChoiceIndex]; - values[randomChoiceIndex] = displacedValue; + swap(values, targetIndex, randomChoiceIndex); } } } + /** + * Swaps two positions of an array in place. + * + * @param values the array + * @param i the first position + * @param j the second position + * @throws ArrayIndexOutOfBoundsException if either position is outside the array + */ + public static void swap(final int[] values, final int i, final int j) { + final int value = values[i]; + values[i] = values[j]; + values[j] = value; + } + private static int randomBetweenInclusive(final int min, final int max) { return RANDOM.nextInt((max - min) + 1) + min; } diff --git a/src/main/java/io/github/diegourban/algorithms/search/BinarySearch.java b/src/main/java/io/github/diegourban/algorithms/search/BinarySearch.java index e95e64b..139712b 100644 --- a/src/main/java/io/github/diegourban/algorithms/search/BinarySearch.java +++ b/src/main/java/io/github/diegourban/algorithms/search/BinarySearch.java @@ -34,7 +34,7 @@ private static int find(final int[] items, final int from, final int to, final i return -1; } - final int middle = (from + to) / 2; + final int middle = from + (to - from) / 2; // (from + to) / 2 could overflow final int middleItem = items[middle]; if (key == middleItem) { diff --git a/src/main/java/io/github/diegourban/algorithms/sorting/InsertionSort.java b/src/main/java/io/github/diegourban/algorithms/sorting/InsertionSort.java index dd2475e..e3228a3 100644 --- a/src/main/java/io/github/diegourban/algorithms/sorting/InsertionSort.java +++ b/src/main/java/io/github/diegourban/algorithms/sorting/InsertionSort.java @@ -1,5 +1,7 @@ package io.github.diegourban.algorithms.sorting; +import io.github.diegourban.algorithms.datastructure.array.ArrayUtils; + /** * Insertion Sort Algorithm: moves each element left past larger ones until it is in place; * O(n²) time (O(n) on already sorted input) and O(1) extra space. @@ -25,10 +27,7 @@ private void sort(final int[] items, final int totalElements) { int position = current; while (position > 0 && items[position] < items[position - 1]) { - final Integer itemBeingInserted = items[position]; - final Integer previousItem = items[position - 1]; - items[position] = previousItem; - items[position - 1] = itemBeingInserted; + ArrayUtils.swap(items, position, position - 1); position--; } } diff --git a/src/main/java/io/github/diegourban/algorithms/sorting/QuickSort.java b/src/main/java/io/github/diegourban/algorithms/sorting/QuickSort.java index ed07426..1e3e014 100644 --- a/src/main/java/io/github/diegourban/algorithms/sorting/QuickSort.java +++ b/src/main/java/io/github/diegourban/algorithms/sorting/QuickSort.java @@ -1,5 +1,7 @@ package io.github.diegourban.algorithms.sorting; +import io.github.diegourban.algorithms.datastructure.array.ArrayUtils; + /** * Quick sort algorithm: partitions around the last element (Lomuto scheme) and recurses on both sides; * O(n log n) average and O(n²) worst-case time (for example on already sorted input), O(log n) average and O(n) @@ -38,17 +40,11 @@ private int partition(final int[] items, final int start, final int end) { for (int current = start; current < end - 1; current++) { final int currentItem = items[current]; if (currentItem <= pivot) { - swap(items, current, boundary); + ArrayUtils.swap(items, current, boundary); boundary++; } } - swap(items, end - 1, boundary); + ArrayUtils.swap(items, end - 1, boundary); return boundary; } - - private void swap(final int[] items, final int from, final int to) { - final int temp = items[from]; - items[from] = items[to]; - items[to] = temp; - } } diff --git a/src/main/java/io/github/diegourban/algorithms/sorting/SelectionSort.java b/src/main/java/io/github/diegourban/algorithms/sorting/SelectionSort.java index 68c1bf1..fa804d9 100644 --- a/src/main/java/io/github/diegourban/algorithms/sorting/SelectionSort.java +++ b/src/main/java/io/github/diegourban/algorithms/sorting/SelectionSort.java @@ -1,5 +1,7 @@ package io.github.diegourban.algorithms.sorting; +import io.github.diegourban.algorithms.datastructure.array.ArrayUtils; + /** * Selection Sort algorithm: repeatedly swaps the smallest remaining element into place; O(n²) time in * every case and O(1) extra space. @@ -23,10 +25,7 @@ public void sort(final int[] items) { private void sort(final int[] items, final int totalElements) { for (int current = 0; current < totalElements - 1; current++) { final int smallestIndex = findSmallestIndex(items, current, totalElements); - final Integer currentItem = items[current]; - final Integer smallestItem = items[smallestIndex]; - items[current] = smallestItem; - items[smallestIndex] = currentItem; + ArrayUtils.swap(items, current, smallestIndex); } } diff --git a/src/test/java/io/github/diegourban/algorithms/datastructure/array/ArrayUtilsTest.java b/src/test/java/io/github/diegourban/algorithms/datastructure/array/ArrayUtilsTest.java index 45d4b1f..c029d24 100644 --- a/src/test/java/io/github/diegourban/algorithms/datastructure/array/ArrayUtilsTest.java +++ b/src/test/java/io/github/diegourban/algorithms/datastructure/array/ArrayUtilsTest.java @@ -26,4 +26,15 @@ void shouldShuffle() { final Integer[] what = Arrays.stream(array).boxed().toArray(Integer[]::new); assertThat(what).containsExactlyInAnyOrder(1, 2, 3, 4, 5); } + + @Test + void shouldSwapTwoPositions() { + final int[] values = {1, 2, 3}; + + ArrayUtils.swap(values, 0, 2); + assertThat(values).containsExactly(3, 2, 1); + + ArrayUtils.swap(values, 1, 1); + assertThat(values).containsExactly(3, 2, 1); + } } From 18ce95ebe82d545699c3c3a0b8b97d47bf663bd9 Mon Sep 17 00:00:00 2001 From: Diego Leonardo Urban Date: Fri, 6 Nov 2026 17:33:02 +0100 Subject: [PATCH 6/6] README: list ArrayUtils.swap and WordSet in the library layout Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b71007..c1d423e 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Requires Java 25. Maven comes with the wrapper. ``` src/main/java/io/github/diegourban/algorithms/ - datastructure/ array (shuffle), binarytree, linkedlist, queue, set, stack, vector + datastructure/ array (shuffle, swap), binarytree, linkedlist, queue, set (WordSet), stack, vector io/ Reader: fast tokenising stdin reader for judge-style input math/ NumberTheory (gcd, lcm, primes, factorisation), Sequences (Fibonacci), Combinatorics, Geometry, Measurement, Numbers, Statistics search/ BinarySearch.indexOf