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 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/datastructure/linkedlist/LinkedList.java b/src/main/java/io/github/diegourban/algorithms/datastructure/linkedlist/LinkedList.java index 4e16c94..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); @@ -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/set/Set.java b/src/main/java/io/github/diegourban/algorithms/datastructure/set/WordSet.java similarity index 66% 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..e85ef4a 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 @@ -1,5 +1,6 @@ package io.github.diegourban.algorithms.datastructure.set; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -8,16 +9,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 ArrayList<>(BUCKET_COUNT); /** 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<>()); } } @@ -28,10 +29,9 @@ public Set() { * @throws IllegalArgumentException if {@code word} is empty */ 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 = 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 list = this.table.get(index); - list.remove(word); - } + bucketFor(word).remove(word); } - private boolean contains(final String word) { - final int index = calculateTableIndex(word); - return this.table.get(index).contains(word); + private List bucketFor(final String word) { + return this.buckets.get(calculateTableIndex(word)); } private int calculateTableIndex(final String word) { @@ -65,10 +60,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 buckets */ @Override public String toString() { - return this.table.toString(); + return this.buckets.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 1a94dc5..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,22 +117,17 @@ 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); } } /** - * 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/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/main/java/io/github/diegourban/algorithms/math/Geometry.java b/src/main/java/io/github/diegourban/algorithms/math/Geometry.java index 01ea5ad..bfff2c5 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); } /** @@ -111,14 +107,10 @@ 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) { - final double[] sortedSides = new double[3]; - sortedSides[0] = a; - sortedSides[1] = b; - sortedSides[2] = c; - Arrays.sort(sortedSides); + public static TriangleType classifyTriangle(final double a, final double b, final double c) { + final double[] sortedSides = sortedSides(a, b, c); final double shortest = sortedSides[0]; final double middle = sortedSides[1]; @@ -132,7 +124,7 @@ public static TriangleType checkTriangleType(final double a, final double b, fin return TriangleType.ISOSCELES; } - return TriangleType.ESCALENE; + return TriangleType.SCALENE; } /** @@ -147,12 +139,8 @@ 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) { - final double[] sortedSides = new double[3]; - sortedSides[0] = a; - sortedSides[1] = b; - sortedSides[2] = c; - Arrays.sort(sortedSides); + public static boolean isRightTriangle(final double a, final double b, final double c) { + final double[] sortedSides = sortedSides(a, b, c); final double shortest = sortedSides[0]; final double middle = sortedSides[1]; @@ -164,4 +152,10 @@ public static boolean isRectangleTriangle(final double a, final double b, final 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 36749be..54c7675 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/Numbers.java +++ b/src/main/java/io/github/diegourban/algorithms/math/Numbers.java @@ -77,62 +77,55 @@ 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 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/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/Statistics.java b/src/main/java/io/github/diegourban/algorithms/math/Statistics.java index b90c1ab..044fcd6 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; } /** @@ -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; 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/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); + } } 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/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/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/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"); + } } 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..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,34 +63,42 @@ 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 + 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/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/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); + } } 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); } }