diff --git a/.gitignore b/.gitignore index 1611de9..4638a7c 100644 --- a/.gitignore +++ b/.gitignore @@ -112,3 +112,4 @@ triage-*.md # jqwik failure database (replays previously failing samples locally) .jqwik-database +findings.md diff --git a/src/main/java/io/github/diegourban/algorithms/datastructure/Element.java b/src/main/java/io/github/diegourban/algorithms/datastructure/Element.java index 0ecd9a6..cefff29 100644 --- a/src/main/java/io/github/diegourban/algorithms/datastructure/Element.java +++ b/src/main/java/io/github/diegourban/algorithms/datastructure/Element.java @@ -26,22 +26,30 @@ public String getName() { } /** - * Compares this element with another by name. The argument is cast without a type check, so passing an object - * that is not an {@code Element} fails with a {@code ClassCastException}. {@code hashCode} is not overridden. + * Compares this element with another by name. * - * @param obj the object to compare with; must be an {@code Element} or {@code null} - * @return {@code true} if {@code obj} is an element with an equal name, {@code false} if {@code obj} is - * {@code null} + * @param obj the object to compare with + * @return {@code true} if {@code obj} is an element with an equal name, {@code false} otherwise (including + * {@code null} and objects of other types) */ @Override public boolean equals(final Object obj) { - if (obj == null) { + if (!(obj instanceof final Element other)) { return false; } - final Element other = (Element) obj; return other.getName().equals(this.getName()); } + /** + * Returns a hash code consistent with {@link #equals(Object)}: elements with equal names hash alike. + * + * @return the hash code of the name + */ + @Override + public int hashCode() { + return this.getName().hashCode(); + } + /** * Returns the element's name. * 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 c3c2c83..682caf6 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 @@ -9,10 +9,10 @@ public final class ArrayUtils { private ArrayUtils() {} - private static final Random rand = new Random(); + private static final Random RANDOM = new Random(); /** - * Function for doing an in-place ↴ shuffle of an array. + * Function for doing an in-place (Fisher–Yates) shuffle of an array. *

* Solution: We choose a random item to move to the first index, then we choose a random other item to move to the second index, etc. * We "place" an item in an index by swapping it with the item currently at that index. @@ -22,33 +22,33 @@ private ArrayUtils() {} *

* Arrays with zero or one element are left unchanged. * - * @param theArray the array to shuffle; modified in place + * @param values the array to shuffle; modified in place */ - public static void shuffle(final int[] theArray) { + public static void shuffle(final int[] values) { // if it's 1 or 0 items, just return - if (theArray.length <= 1) { + if (values.length <= 1) { return; } // walk through from beginning to end - for (int indexWeAreChoosingFor = 0; indexWeAreChoosingFor < theArray.length - 1; indexWeAreChoosingFor++) { + for (int targetIndex = 0; targetIndex < values.length - 1; targetIndex++) { // choose a random not-yet-placed item to place there // (could also be the item currently in that spot) // must be an item AFTER the current item, because the stuff // before has all already been placed - final int randomChoiceIndex = getRandom(indexWeAreChoosingFor, theArray.length - 1); + final int randomChoiceIndex = randomBetweenInclusive(targetIndex, values.length - 1); // place our random choice in the spot by swapping - if (randomChoiceIndex != indexWeAreChoosingFor) { - final int valueAtIndexWeChoseFor = theArray[indexWeAreChoosingFor]; - theArray[indexWeAreChoosingFor] = theArray[randomChoiceIndex]; - theArray[randomChoiceIndex] = valueAtIndexWeChoseFor; + if (randomChoiceIndex != targetIndex) { + final int displacedValue = values[targetIndex]; + values[targetIndex] = values[randomChoiceIndex]; + values[randomChoiceIndex] = displacedValue; } } } - private static int getRandom(final int floor, final int ceiling) { - return rand.nextInt((ceiling - floor) + 1) + floor; + 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 9600a71..4e16c94 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 @@ -55,17 +55,16 @@ public void addAtEnd(final Element element) { /** * Inserts an element at the given position, shifting later elements one position back. *

- * If the list is empty the element is added regardless of {@code position}. If {@code position} equals - * {@link #size()} the element is appended. Otherwise the element is linked after the node at - * {@code position - 1}, so inserting at position {@code 0} of a non-empty list is not supported. + * If the list is empty the element is added regardless of {@code position}. Position {@code 0} inserts at the + * start and {@link #size()} appends; otherwise the element is linked after the node at {@code position - 1}. * - * @param position the position for the new element, from {@code 1} to {@link #size()} on a non-empty list + * @param position the position for the new element, from {@code 0} to {@link #size()} * @param element the element to insert - * @throws IllegalArgumentException if the list is not empty and {@code position - 1} is not an occupied position - * (including {@code position == 0}) + * @throws IllegalArgumentException if the list is not empty and {@code position} is negative or greater than + * {@link #size()} */ public void add(final int position, final Element element) { - if (this.totalElements == 0) { + if (this.totalElements == 0 || position == 0) { addAtStart(element); } else if (position == this.totalElements) { addAtEnd(element); @@ -88,7 +87,7 @@ private boolean positionOccupied(final int position) { private LinkedListNode getNode(final int position) { if (!positionOccupied(position)) { - throw new IllegalArgumentException("Posição inexistente"); + throw new IllegalArgumentException("Invalid position: " + position); } LinkedListNode current = this.first; for (int i = 0; i < position; i++) { @@ -124,7 +123,7 @@ public Element get(final int position) { */ public void removeFromStart() { if (this.totalElements == 0) { - throw new IllegalStateException("Lista vazia"); + throw new IllegalStateException("List is empty"); } this.first = this.first.getNext(); @@ -132,14 +131,20 @@ public void removeFromStart() { if (this.totalElements == 0) { this.last = null; + } else { + this.first.setPrevious(null); } } /** - * Removes the last element. Unlike {@link #removeFromStart()}, calling this on an empty list fails with a - * {@code NullPointerException} rather than an {@code IllegalStateException}. + * Removes the last element. + * + * @throws IllegalStateException if the list is empty */ public void removeFromEnd() { + if (this.totalElements == 0) { + throw new IllegalStateException("List is empty"); + } if (this.totalElements == 1) { this.removeFromStart(); } else { @@ -155,14 +160,10 @@ public void removeFromEnd() { *

* Position {@code 0} delegates to {@link #removeFromStart()} and position {@code size() - 1} to * {@link #removeFromEnd()}; any other position unlinks the node after {@code position - 1}. - *

- * Positions are not fully validated: on a non-empty list, {@code position == size()} fails with a - * {@code NullPointerException}, as does {@code -1} on an empty list. * * @param position the zero-based position, from {@code 0} to {@code size() - 1} - * @throws IllegalStateException if {@code position} is {@code 0} and the list is empty - * @throws IllegalArgumentException if {@code position} is negative or greater than {@link #size()}, except - * {@code -1} on an empty list + * @throws IllegalStateException if the list is empty and {@code position} is {@code 0} or {@code -1} + * @throws IllegalArgumentException if {@code position} is otherwise not an occupied position */ public void remove(final int position) { if (position == 0) { @@ -170,6 +171,9 @@ public void remove(final int position) { } else if (position == totalElements - 1) { this.removeFromEnd(); } else { + if (!positionOccupied(position)) { + throw new IllegalArgumentException("Invalid position: " + position); + } final LinkedListNode previous = this.getNode(position - 1); final LinkedListNode current = previous.getNext(); final LinkedListNode next = current.getNext(); diff --git a/src/main/java/io/github/diegourban/algorithms/datastructure/set/Set.java b/src/main/java/io/github/diegourban/algorithms/datastructure/set/Set.java index 1306ab4..674d9e5 100644 --- a/src/main/java/io/github/diegourban/algorithms/datastructure/set/Set.java +++ b/src/main/java/io/github/diegourban/algorithms/datastructure/set/Set.java @@ -10,20 +10,22 @@ */ public class Set { + private static final int BUCKET_COUNT = 26; + private final List> table = new LinkedList>(); /** Creates an empty set with 26 empty buckets. */ public Set() { - for (int i = 0; i < 26; i++) { + for (int i = 0; i < BUCKET_COUNT; i++) { this.table.add(new LinkedList()); } } /** - * Adds a word if it is not already present; duplicates are ignored. Comparison is case-sensitive. An empty - * string fails with a {@code StringIndexOutOfBoundsException}. + * Adds a word if it is not already present; duplicates are ignored. Comparison is case-sensitive. * * @param word the word to add + * @throws IllegalArgumentException if {@code word} is empty */ public void add(final String word) { if (!contains(word)) { @@ -34,10 +36,10 @@ public void add(final String word) { } /** - * Removes a word if present; otherwise does nothing. An empty string fails with a - * {@code StringIndexOutOfBoundsException}. + * Removes a word if present; otherwise does nothing. * * @param word the word to remove + * @throws IllegalArgumentException if {@code word} is empty */ public void remove(final String word) { if (contains(word)) { @@ -53,7 +55,10 @@ private boolean contains(final String word) { } private int calculateTableIndex(final String word) { - return word.toLowerCase().charAt(0) % 26; + if (word.isEmpty()) { + throw new IllegalArgumentException("word must not be empty"); + } + return word.toLowerCase().charAt(0) % BUCKET_COUNT; } /** 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 828aceb..1a94dc5 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 @@ -9,7 +9,9 @@ */ public class Vector { - private Element[] elements = new Element[100]; + private static final int INITIAL_CAPACITY = 100; + + private Element[] elements = new Element[INITIAL_CAPACITY]; private int totalElements = 0; /** Creates an empty vector with capacity 100. */ @@ -21,26 +23,26 @@ public Vector() {} * @param element the element to append */ public void add(final Element element) { - this.guaranteesSpace(); + this.ensureCapacity(); this.elements[this.totalElements] = element; this.totalElements++; } /** - * Inserts an element at an occupied position, shifting it and later elements one position back. Only occupied - * positions are accepted, so this cannot append (use {@link #add(Element)}) or insert into an empty vector. + * Inserts an element at the given position, shifting it and later elements one position back. Position + * {@link #size()} appends. * - * @param position the zero-based position, from {@code 0} to {@code size() - 1} + * @param position the zero-based position, from {@code 0} to {@link #size()} * @param element the element to insert - * @throws IllegalArgumentException if {@code position} is negative or not less than {@link #size()} + * @throws IllegalArgumentException if {@code position} is negative or greater than {@link #size()} */ public void add(final int position, final Element element) { if (!isPositionValid(position)) { throw new IllegalArgumentException("Invalid position"); } - this.guaranteesSpace(); + this.ensureCapacity(); for (int i = this.totalElements - 1; i >= position; i--) { this.elements[i + 1] = this.elements[i]; } @@ -49,7 +51,7 @@ public void add(final int position, final Element element) { } private boolean isPositionValid(final int position) { - return position >= 0 && position < totalElements; + return position >= 0 && position <= totalElements; } /** @@ -72,13 +74,14 @@ private boolean positionOccupied(final int position) { /** * Removes the element at the given position, shifting later elements one position forward. - *

- * The position is not validated: a position not less than {@link #size()} removes the last element instead, and - * a negative position, or any call on an empty vector, fails with an {@code ArrayIndexOutOfBoundsException}. * * @param position the zero-based position, from {@code 0} to {@code size() - 1} + * @throws IllegalArgumentException if {@code position} is negative or not less than {@link #size()} */ public void remove(final int position) { + if (!positionOccupied(position)) { + throw new IllegalArgumentException("Invalid position"); + } for (int i = position; i < this.totalElements - 1; i++) { this.elements[i] = this.elements[i + 1]; } @@ -112,7 +115,7 @@ public int size() { return this.totalElements; } - private void guaranteesSpace() { + 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++) { 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 a78e850..af6f308 100644 --- a/src/main/java/io/github/diegourban/algorithms/io/Reader.java +++ b/src/main/java/io/github/diegourban/algorithms/io/Reader.java @@ -20,8 +20,8 @@ */ public class Reader { - private final int BUFFER_SIZE = 1 << 16; - private DataInputStream din; + private static final int BUFFER_SIZE = 1 << 16; + private DataInputStream input; private final byte[] buffer; private int bufferPointer, bytesRead; @@ -31,7 +31,7 @@ public class Reader { * @param input the stream to read from; it is closed by {@link #close()} */ public Reader(final InputStream input) { - din = new DataInputStream(input); + this.input = new DataInputStream(input); buffer = new byte[BUFFER_SIZE]; bufferPointer = bytesRead = 0; } @@ -49,20 +49,20 @@ public Reader(final InputStream input) { * @throws IOException if the underlying stream fails */ public String readLine() throws IOException { - final byte[] buf = new byte[10000]; // line length - int cnt = 0; - int c = 0; + final byte[] line = new byte[10000]; // line length + int length = 0; + int nextByte = 0; - while ((c = read()) != -1) { - if (c == '\n' || c == '\r') { - if (cnt == 0) { + while ((nextByte = read()) != -1) { + if (nextByte == '\n' || nextByte == '\r') { + if (length == 0) { continue; } break; } - buf[cnt++] = (byte) c; + line[length++] = (byte) nextByte; } - return new String(buf, 0, cnt); + return new String(line, 0, length); } /** @@ -76,27 +76,27 @@ public String readLine() throws IOException { * @throws IOException if the underlying stream fails */ public int nextInt() throws IOException { - int ret = 0; - byte c = read(); + int result = 0; + byte nextByte = read(); - while (c <= ' ') { - c = read(); + while (nextByte <= ' ') { + nextByte = read(); } - final boolean neg = (c == '-'); - if (neg) { - c = read(); + final boolean negative = (nextByte == '-'); + if (negative) { + nextByte = read(); } do { - ret = ret * 10 + c - '0'; - } while ((c = read()) >= '0' && c <= '9'); + result = result * 10 + nextByte - '0'; + } while ((nextByte = read()) >= '0' && nextByte <= '9'); - if (neg) { - return -ret; + if (negative) { + return -result; } - return ret; + return result; } /** @@ -107,13 +107,13 @@ public int nextInt() throws IOException { * @throws IOException if the underlying stream fails */ public char nextChar() throws IOException { - byte c = read(); + byte nextByte = read(); - while (c <= ' ') { - c = read(); + while (nextByte <= ' ') { + nextByte = read(); } - return (char) c; + return (char) nextByte; } /** @@ -126,27 +126,27 @@ public char nextChar() throws IOException { * @throws IOException if the underlying stream fails */ public long nextLong() throws IOException { - long ret = 0; - byte c = read(); + long result = 0; + byte nextByte = read(); - while (c <= ' ') { - c = read(); + while (nextByte <= ' ') { + nextByte = read(); } - final boolean neg = (c == '-'); - if (neg) { - c = read(); + final boolean negative = (nextByte == '-'); + if (negative) { + nextByte = read(); } do { - ret = ret * 10 + c - '0'; - } while ((c = read()) >= '0' && c <= '9'); + result = result * 10 + nextByte - '0'; + } while ((nextByte = read()) >= '0' && nextByte <= '9'); - if (neg) { - return -ret; + if (negative) { + return -result; } - return ret; + return result; } /** @@ -160,34 +160,34 @@ public long nextLong() throws IOException { * @throws IOException if the underlying stream fails */ public double nextDouble() throws IOException { - double ret = 0; + double result = 0; double div = 1; - byte c = read(); + byte nextByte = read(); - while (c <= ' ') { - c = read(); + while (nextByte <= ' ') { + nextByte = read(); } - final boolean neg = (c == '-'); - if (neg) { - c = read(); + final boolean negative = (nextByte == '-'); + if (negative) { + nextByte = read(); } do { - ret = ret * 10 + c - '0'; - } while ((c = read()) >= '0' && c <= '9'); + result = result * 10 + nextByte - '0'; + } while ((nextByte = read()) >= '0' && nextByte <= '9'); - if (c == '.') { - while ((c = read()) >= '0' && c <= '9') { - ret += (c - '0') / (div *= 10); + if (nextByte == '.') { + while ((nextByte = read()) >= '0' && nextByte <= '9') { + result += (nextByte - '0') / (div *= 10); } } - if (neg) { - return -ret; + if (negative) { + return -result; } - return ret; + return result; } /** @@ -204,7 +204,7 @@ public void skip(final int n) throws IOException { } private void fillBuffer() throws IOException { - bytesRead = din.read(buffer, bufferPointer = 0, BUFFER_SIZE); + bytesRead = input.read(buffer, bufferPointer = 0, BUFFER_SIZE); if (isEOF()) { buffer[0] = -1; } @@ -231,9 +231,9 @@ private boolean isEOF() { * @throws IOException if closing the underlying stream fails */ public void close() throws IOException { - if (din != null) { - din.close(); - din = null; + if (input != null) { + input.close(); + input = null; } } @@ -243,6 +243,6 @@ public void close() throws IOException { * @return {@code true} if this reader is closed, {@code false} otherwise */ public boolean isClosed() { - return din == null; + return input == null; } } diff --git a/src/main/java/io/github/diegourban/algorithms/math/Combinatorics.java b/src/main/java/io/github/diegourban/algorithms/math/Combinatorics.java index 77efc07..28591fc 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/Combinatorics.java +++ b/src/main/java/io/github/diegourban/algorithms/math/Combinatorics.java @@ -31,21 +31,21 @@ private Combinatorics() {} */ public static BigInteger binomialCoefficient(final int n, final int m) { if (m < 0 || m > n) { - return new BigInteger("0"); + return BigInteger.ZERO; } if (m == 0 || m == n) { - return new BigInteger("1"); + return BigInteger.ONE; } if (m == 1 || m == n - 1) { - return new BigInteger(String.valueOf(n)); + return BigInteger.valueOf(n); } final BigInteger[] binomials = new BigInteger[n + 1]; - binomials[0] = new BigInteger("1"); + binomials[0] = BigInteger.ONE; for (int i = 1; i <= n; i++) { - binomials[i] = new BigInteger("1"); + binomials[i] = BigInteger.ONE; for (int j = i - 1; j > 0; --j) { binomials[j] = binomials[j].add(binomials[j - 1]); } 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 fd18210..01ea5ad 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/Geometry.java +++ b/src/main/java/io/github/diegourban/algorithms/math/Geometry.java @@ -23,7 +23,6 @@ private Geometry() {} * @return the distance between the two points */ public static double distance(final int x1, final int y1, final int x2, final int y2) { - // return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); return Math.hypot(x1 - x2, y1 - y2); } @@ -115,21 +114,21 @@ public static boolean isValidTriangle(final double a, final double b, final doub * two are, {@link TriangleType#ESCALENE} otherwise */ public static TriangleType checkTriangleType(final double a, final double b, final double c) { - final double[] array = new double[3]; - array[0] = a; - array[1] = b; - array[2] = c; - Arrays.sort(array); + final double[] sortedSides = new double[3]; + sortedSides[0] = a; + sortedSides[1] = b; + sortedSides[2] = c; + Arrays.sort(sortedSides); - final double min = array[0]; - final double med = array[1]; - final double max = array[2]; + final double shortest = sortedSides[0]; + final double middle = sortedSides[1]; + final double longest = sortedSides[2]; - if (max == med && med == min) { + if (longest == middle && middle == shortest) { return TriangleType.EQUILATERAL; } - if (max == med || med == min) { + if (longest == middle || middle == shortest) { return TriangleType.ISOSCELES; } @@ -149,19 +148,19 @@ public static TriangleType checkTriangleType(final double a, final double b, fin * @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[] array = new double[3]; - array[0] = a; - array[1] = b; - array[2] = c; - Arrays.sort(array); - - final double min = array[0]; - final double med = array[1]; - final double max = array[2]; - - final double maxSquared = max * max; - final double medSquared = med * med; - final double minSquared = min * min; + final double[] sortedSides = new double[3]; + sortedSides[0] = a; + sortedSides[1] = b; + sortedSides[2] = c; + Arrays.sort(sortedSides); + + final double shortest = sortedSides[0]; + final double middle = sortedSides[1]; + final double longest = sortedSides[2]; + + final double maxSquared = longest * longest; + final double medSquared = middle * middle; + final double minSquared = shortest * shortest; return maxSquared == medSquared + minSquared; } diff --git a/src/main/java/io/github/diegourban/algorithms/math/Measurement.java b/src/main/java/io/github/diegourban/algorithms/math/Measurement.java index 37f9f2a..8b04267 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/Measurement.java +++ b/src/main/java/io/github/diegourban/algorithms/math/Measurement.java @@ -7,6 +7,9 @@ */ public final class Measurement { + private static final double KILOMETERS_PER_MILE = 1.609344; + private static final double MILES_PER_KILOMETER = 0.621371; + private Measurement() {} /** @@ -16,7 +19,7 @@ private Measurement() {} * @return the amount of miles converted to kilometers */ public static double milesToKilometers(final double miles) { - return miles * 1.609344; + return miles * KILOMETERS_PER_MILE; } /** @@ -26,6 +29,6 @@ public static double milesToKilometers(final double miles) { * @return the amount of kilometers converted to miles */ public static double kilometersToMiles(final double kilometers) { - return kilometers * 0.621371; + return kilometers * MILES_PER_KILOMETER; } } 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 8297bf7..39e8348 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/NumberTheory.java +++ b/src/main/java/io/github/diegourban/algorithms/math/NumberTheory.java @@ -12,16 +12,19 @@ private NumberTheory() {} /** * Least common multiple (lcm) of two numbers. *

- * Computed as {@code a * (b / gcdLoop(a, b))}; intended for positive numbers. The result overflows silently if it - * does not fit in a {@code long}. + * Computed as {@code a * (b / gcdLoop(a, b))}; intended for non-negative numbers. If either number is {@code 0} + * the result is {@code 0}. * * @param a the first number * @param b the second number * @return The least common multiple of two numbers. - * @throws ArithmeticException if both {@code a} and {@code b} are zero + * @throws ArithmeticException if the result does not fit in a {@code long} */ public static long lcm(final long a, final long b) { - return a * (b / gcdLoop(a, b)); + if (a == 0 || b == 0) { + return 0; + } + return Math.multiplyExact(a, b / gcdLoop(a, b)); } /** @@ -39,7 +42,7 @@ public static long lcm(final long[] input) { } /** - * Greatest common divisor (gdc) of two numbers using while strategy. + * Greatest common divisor (gcd) of two numbers using while strategy. *

* Also known as greatest common factor (gcf), highest common factor (hcf), * greatest common measure (gcm) or highest common divisor @@ -52,18 +55,18 @@ public static long lcm(final long[] input) { * @return The greatest common divisor of two numbers. */ public static long gcdLoop(final long a, final long b) { - long gcd = a; - long bNumber = b; - while (bNumber > 0) { - final long temp = bNumber; - bNumber = gcd % bNumber; // % is remainder - gcd = temp; + long dividend = a; + long divisor = b; + while (divisor > 0) { + final long temp = divisor; + divisor = dividend % divisor; + dividend = temp; } - return gcd; + return dividend; } /** - * Greatest common divisor (gdc) of two numbers using recursive strategy. + * Greatest common divisor (gcd) of two numbers using recursive strategy. *

* Also known as greatest common factor (gcf), highest common factor (hcf), * greatest common measure (gcm) or highest common divisor @@ -98,6 +101,7 @@ public static boolean isPrime(final int n) { return false; } + // every prime above 3 has the form 6k ± 1, so only i and i + 2 need checking per step of 6 int i = 5; while (i <= n / i) { if (n % i == 0 || n % (i + 2) == 0) { 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 50f5268..36749be 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/Numbers.java +++ b/src/main/java/io/github/diegourban/algorithms/math/Numbers.java @@ -10,23 +10,25 @@ private Numbers() {} /** * Checks if a number contains a digit *

- * The sign of {@code number} is ignored. The number {@code 0} is treated as having no digits, so - * {@code containsDigit(0, 0)} returns {@code false}. + * The sign of {@code number} is ignored; {@code 0} contains the digit {@code 0}. * * @param number the number to check * @param digit the digit expected to find in the number * @return {@code true} if the number contains the digit, {@code false} otherwise */ public static boolean containsDigit(final long number, final long digit) { - long thisNumber = number >= 0 ? number : -number; - long thisDigit; + long remaining = number >= 0 ? number : -number; + long lastDigit; - while (thisNumber != 0) { - thisDigit = thisNumber % 10; // Always equal to the last digit of thisNumber - thisNumber = thisNumber - / 10; // Always equal to thisNumber with the last digit chopped off, or 0 if thisNumber is less than - // 10 - if (thisDigit == digit) { + if (remaining == 0) { + return digit == 0; + } + + // take the last digit, then chop it off, until no digits are left + while (remaining != 0) { + lastDigit = remaining % 10; + remaining = remaining / 10; + if (lastDigit == digit) { return true; } } @@ -46,20 +48,20 @@ public static boolean containsDigit(final long number, final long digit) { * times, {@code false} otherwise */ public static boolean containsRepeatingDigits(final long number, final int times) { - long theNumber = number; - Long old = null; - int reps = 0; + long remaining = number; + Long previousDigit = null; + int runLength = 0; - while (theNumber > 0) { - final Long n = theNumber % 10; - theNumber /= 10; - if (n.equals(old)) { - if (++reps == times) { + while (remaining > 0) { + final Long digit = remaining % 10; + remaining /= 10; + if (digit.equals(previousDigit)) { + if (++runLength == times) { return true; } } else { - old = n; - reps = 1; + previousDigit = digit; + runLength = 1; } } @@ -70,9 +72,7 @@ public static boolean containsRepeatingDigits(final long number, final int times * Converts a number to an array of its digits, least significant digit first: {@code 123} becomes * {@code [3, 2, 1]}. *

- * Intended for non-negative numbers that fit in an {@code int}: each step truncates the number to {@code int} - * before taking the last digit, so larger values give wrong digits, and a negative number gives negative digits - * plus an extra trailing {@code 0} for the minus sign. + * A negative number gives the digits of its absolute value: {@code -12} becomes {@code [2, 1]}. * * @param number the number to convert * @return an array with each digit of the number, in reverse order @@ -80,11 +80,11 @@ public static boolean containsRepeatingDigits(final long number, final int times public static int[] toArray(final long number) { long theNumber = number; - final int length = Long.toString(theNumber).length(); + final int length = Long.toString(theNumber).length() - (theNumber < 0 ? 1 : 0); final int[] array = new int[length]; for (int index = 0; index < length; index++) { - array[index] = (int) theNumber % 10; + array[index] = Math.abs((int) (theNumber % 10)); theNumber /= 10; } @@ -94,12 +94,15 @@ public static int[] toArray(final long number) { /** * Find the length of the int using divide and conquer *

- * Intended for non-negative numbers: any negative number returns 1. + * A negative number counts the digits of its absolute value: {@code length(-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) { + if (number < 0) { + return number == Integer.MIN_VALUE ? 10 : length(-number); + } if (number < 100000) { // 5 or less if (number < 100) { 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 54f4064..b293ccb 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/Sequences.java +++ b/src/main/java/io/github/diegourban/algorithms/math/Sequences.java @@ -9,6 +9,9 @@ */ public final class Sequences { + private static final double SQRT_5 = Math.sqrt(5); + private static final double GOLDEN_RATIO = (1 + SQRT_5) / 2; + private Sequences() {} /** @@ -24,9 +27,8 @@ private Sequences() {} * @throws IllegalArgumentException if {@code n} is negative */ public static int fibonacci(final int n) { - // edge cases: if (n < 0) { - throw new IllegalArgumentException("Index was negative. No such thing as a negative index in a series."); + throw new IllegalArgumentException("n must be non-negative: " + n); } else if (n == 0 || n == 1) { return n; } @@ -35,7 +37,7 @@ public static int fibonacci(final int n) { // so we'll need to track the previous 2 numbers at each step int prevPrev = 0; // 0th fibonacci int prev = 1; // 1st fibonacci - int current = 0; // Declare and initialize current + int current = 0; for (int i = 1; i < n; i++) { @@ -53,11 +55,11 @@ public static int fibonacci(final int n) { /** * Calculates the nth Fibonacci number. - * Works with n <= 67 + * Works with n <= 70 *

* Uses Binet's formula in {@code double} arithmetic, so the result is approximate (for example * {@code 55.000000000000014} for n = 10). Rounded with {@link Math#round(double)}, it equals the exact value for - * 0 <= n <= 67. Negative {@code n} is not rejected and gives a meaningless result. + * 0 <= n <= 70. Negative {@code n} is not rejected and gives a meaningless result. * * @param n the nth number in the Fibonacci sequence to calculate * @return an approximation of the nth Fibonacci number @@ -66,9 +68,7 @@ public static double fibonacciSimpleBinetFormula(final int n) { if (n == 0) { return 0; } - final double phi = (1 + Math.sqrt(5)) / 2; - final double sqrt5 = Math.sqrt(5); - return ((Math.pow(phi, n) - Math.pow(-phi, -n)) / sqrt5); + return ((Math.pow(GOLDEN_RATIO, n) - Math.pow(-GOLDEN_RATIO, -n)) / SQRT_5); } /** @@ -84,20 +84,19 @@ public static double fibonacciSimpleBinetFormula(final int n) { */ public static BigInteger fibonacciBinetFormula(final int n) { if (n == 0) { - return new BigInteger("0"); + return BigInteger.ZERO; } if (n == 1 || n == 2) { - return new BigInteger("1"); + return BigInteger.ONE; } if (n == 3) { - return new BigInteger("2"); + return BigInteger.TWO; } - final double phi = (1 + Math.sqrt(5)) / 2; - final BigDecimal sqrt5 = BigDecimal.valueOf(Math.sqrt(5)); + final BigDecimal sqrt5 = BigDecimal.valueOf(SQRT_5); - final BigDecimal pow = BigDecimal.valueOf(Math.pow(phi, n)); - final BigDecimal powInvrt = BigDecimal.valueOf(Math.pow(-phi, -n)); + 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(); } } 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 d852c62..b90c1ab 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/Statistics.java +++ b/src/main/java/io/github/diegourban/algorithms/math/Statistics.java @@ -37,9 +37,10 @@ public static double mean(final double[] data) { * * @param data the values; must not be empty, and is sorted ascending as a side effect * @return the middle value for an odd number of values, or the mean of the two middle values for an even number - * @throws ArrayIndexOutOfBoundsException if {@code data} is empty + * @throws IllegalArgumentException if {@code data} is empty */ public static double median(final double[] data) { + requireNotEmpty(data); Arrays.sort(data); final int middle = data.length / 2; @@ -84,9 +85,11 @@ public static double mode(final double[] data) { * {@linkplain #mean(double[]) mean}. * * @param data the values - * @return the Average Absolute Deviation from data; {@code NaN} if {@code data} is empty + * @return the Average Absolute Deviation from data + * @throws IllegalArgumentException if {@code data} is empty */ public static double meanAbsoluteDeviation(final double[] data) { + requireNotEmpty(data); final double mean = mean(data); double sum = 0; @@ -96,4 +99,10 @@ public static double meanAbsoluteDeviation(final double[] data) { return sum / data.length; } + + private static void requireNotEmpty(final double[] data) { + if (data.length == 0) { + throw new IllegalArgumentException("data must not be empty"); + } + } } 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 e7df295..f29711b 100644 --- a/src/main/java/io/github/diegourban/algorithms/math/TriangleType.java +++ b/src/main/java/io/github/diegourban/algorithms/math/TriangleType.java @@ -6,11 +6,11 @@ */ public enum TriangleType { /** All three sides are equal. */ - EQUILATERAL("Equilatero"), + EQUILATERAL("Equilateral"), /** No two sides are equal (scalene). */ - ESCALENE("Escaleno"), + ESCALENE("Scalene"), /** Exactly two sides are equal. */ - ISOSCELES("Isoceles"); + ISOSCELES("Isosceles"); private final String name; @@ -21,7 +21,7 @@ private TriangleType(final String name) { /** * Returns the display name of this type, in Portuguese. * - * @return the display name, such as {@code "Equilatero"} + * @return the display name, such as {@code "Equilateral"} */ public String getName() { return name; 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 7f4abe2..e95e64b 100644 --- a/src/main/java/io/github/diegourban/algorithms/search/BinarySearch.java +++ b/src/main/java/io/github/diegourban/algorithms/search/BinarySearch.java @@ -21,15 +21,15 @@ public static int indexOf(final int[] sorted, final int key) { /** * Finds an element in a list *

- * Complexity: O(log n) time and O(1) space + * Complexity: O(log n) time and O(log n) space for the recursion * * @param items an ordered list of items to find * @param from the index to find from in the list * @param to the index to find to in the list - * @param element the element to find + * @param key the element to find * @return the index of the element, -1 otherwise */ - private static int find(final int[] items, final int from, final int to, final int element) { + private static int find(final int[] items, final int from, final int to, final int key) { if (from > to) { return -1; } @@ -37,12 +37,12 @@ private static int find(final int[] items, final int from, final int to, final i final int middle = (from + to) / 2; final int middleItem = items[middle]; - if (element == middleItem) { + if (key == middleItem) { return middle; } - if (element < middleItem) { - return find(items, from, middle - 1, element); + if (key < middleItem) { + return find(items, from, middle - 1, key); } - return find(items, middle + 1, to, element); + return find(items, middle + 1, to, key); } } 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 01ccbd6..dd2475e 100644 --- a/src/main/java/io/github/diegourban/algorithms/sorting/InsertionSort.java +++ b/src/main/java/io/github/diegourban/algorithms/sorting/InsertionSort.java @@ -22,14 +22,14 @@ public void sort(final int[] items) { */ private void sort(final int[] items, final int totalElements) { for (int current = 1; current < totalElements; current++) { - int analysis = current; + int position = current; - while (analysis > 0 && items[analysis] < items[analysis - 1]) { - final Integer analysisItem = items[analysis]; - final Integer previousItem = items[analysis - 1]; - items[analysis] = previousItem; - items[analysis - 1] = analysisItem; - analysis--; + 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; + position--; } } } diff --git a/src/main/java/io/github/diegourban/algorithms/sorting/MergeSort.java b/src/main/java/io/github/diegourban/algorithms/sorting/MergeSort.java index 3ee1e38..a4824fd 100644 --- a/src/main/java/io/github/diegourban/algorithms/sorting/MergeSort.java +++ b/src/main/java/io/github/diegourban/algorithms/sorting/MergeSort.java @@ -33,40 +33,40 @@ private void sort(final int[] items, final int begin, final int end) { private void merge(final int[] items, final int begin, final int middle, final int end) { final int[] result = new int[end - begin]; - int current = 0; + int resultIndex = 0; int currentLeft = begin; int currentRight = middle; while (currentLeft < middle && currentRight < end) { - final int firstValue = items[currentLeft]; - final int secondValue = items[currentRight]; + final int leftValue = items[currentLeft]; + final int rightValue = items[currentRight]; - if (firstValue < secondValue) { - result[current] = firstValue; + if (leftValue < rightValue) { + result[resultIndex] = leftValue; currentLeft++; } else { - result[current] = secondValue; + result[resultIndex] = rightValue; currentRight++; } - current++; + resultIndex++; } // remaining values on the left side while (currentLeft < middle) { - result[current] = items[currentLeft]; + result[resultIndex] = items[currentLeft]; currentLeft++; - current++; + resultIndex++; } // remaining values on the right side while (currentRight < end) { - result[current] = items[currentRight]; + result[resultIndex] = items[currentRight]; currentRight++; - current++; + resultIndex++; } - for (int count = 0; count < current; count++) { + for (int count = 0; count < resultIndex; count++) { items[begin + count] = result[count]; } } 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 9ebf87d..ed07426 100644 --- a/src/main/java/io/github/diegourban/algorithms/sorting/QuickSort.java +++ b/src/main/java/io/github/diegourban/algorithms/sorting/QuickSort.java @@ -32,18 +32,18 @@ private void sort(final int[] items, final int start, final int end) { } private int partition(final int[] items, final int start, final int end) { - int minorsFound = start; + int boundary = start; final int pivot = items[end - 1]; for (int current = start; current < end - 1; current++) { final int currentItem = items[current]; if (currentItem <= pivot) { - swap(items, current, minorsFound); - minorsFound++; + swap(items, current, boundary); + boundary++; } } - swap(items, end - 1, minorsFound); - return minorsFound; + swap(items, end - 1, boundary); + return boundary; } private void swap(final int[] items, final int from, final int to) { 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 11f8ba8..68c1bf1 100644 --- a/src/main/java/io/github/diegourban/algorithms/sorting/SelectionSort.java +++ b/src/main/java/io/github/diegourban/algorithms/sorting/SelectionSort.java @@ -22,21 +22,21 @@ 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 smallerIndex = findSmallerIndex(items, current, totalElements); + final int smallestIndex = findSmallestIndex(items, current, totalElements); final Integer currentItem = items[current]; - final Integer smallerItem = items[smallerIndex]; - items[current] = smallerItem; - items[smallerIndex] = currentItem; + final Integer smallestItem = items[smallestIndex]; + items[current] = smallestItem; + items[smallestIndex] = currentItem; } } - private int findSmallerIndex(final int[] items, final int begin, final int end) { - int smallerIndex = begin; + private int findSmallestIndex(final int[] items, final int begin, final int end) { + int smallestIndex = begin; for (int i = begin; i < end; i++) { - if (items[i] < items[smallerIndex]) { - smallerIndex = i; + if (items[i] < items[smallestIndex]) { + smallestIndex = i; } } - return smallerIndex; + return smallestIndex; } } diff --git a/src/main/java/io/github/diegourban/algorithms/string/StringUtils.java b/src/main/java/io/github/diegourban/algorithms/string/StringUtils.java index 8db1e12..b154335 100644 --- a/src/main/java/io/github/diegourban/algorithms/string/StringUtils.java +++ b/src/main/java/io/github/diegourban/algorithms/string/StringUtils.java @@ -15,23 +15,23 @@ private StringUtils() {} * The comparison is exact, character by character: case, spaces and punctuation all count, so {@code "Aba"} is * not a palindrome. Runs in O(n) time. * - * @param theString the string to check + * @param text the string to check * @return {@code true} if the string is a palindrome; {@code false} otherwise, including for {@code null} and the * empty string */ - public static boolean isPalindrome(final String theString) { - if (theString == null || theString.isEmpty()) { + public static boolean isPalindrome(final String text) { + if (text == null || text.isEmpty()) { return false; } - if (theString.length() == 1) { + if (text.length() == 1) { return true; } - for (int i = 0; i < theString.length() / 2; i++) { - final char c1 = theString.charAt(i); - final char c2 = theString.charAt(theString.length() - i - 1); - if (c1 != c2) { + for (int i = 0; i < text.length() / 2; i++) { + final char fromStart = text.charAt(i); + final char fromEnd = text.charAt(text.length() - i - 1); + if (fromStart != fromEnd) { return false; } } @@ -41,8 +41,8 @@ public static boolean isPalindrome(final String theString) { /** * Checks if two strings are anagrams: the same characters, each the same number of times, in any order. *

- * The comparison ignores case (both strings are lower-cased first) but counts spaces and punctuation. Runs in - * O(n log n) time by sorting the characters of both strings. + * The comparison ignores case (both strings are lower-cased first, independently of the default locale) but + * counts spaces and punctuation. Runs in O(n log n) time by sorting the characters of both strings. * * @param a the first string; must not be {@code null} * @param b the second string; must not be {@code null} @@ -54,13 +54,13 @@ public static boolean isAnagram(final String a, final String b) { return false; } - final char[] arrA = sort(a); - final char[] arrB = sort(b); - return Arrays.equals(arrA, arrB); + final char[] sortedA = sortedLowerCaseChars(a); + final char[] sortedB = sortedLowerCaseChars(b); + return Arrays.equals(sortedA, sortedB); } - private static char[] sort(final String original) { - final char[] chars = original.toLowerCase().toCharArray(); + private static char[] sortedLowerCaseChars(final String text) { + final char[] chars = text.toLowerCase(Locale.ROOT).toCharArray(); Arrays.sort(chars); // Dual-Pivot Quicksort, O(n log(n)) return chars; } diff --git a/src/test/java/io/github/diegourban/algorithms/datastructure/ElementTest.java b/src/test/java/io/github/diegourban/algorithms/datastructure/ElementTest.java index bd7c4ea..369551e 100644 --- a/src/test/java/io/github/diegourban/algorithms/datastructure/ElementTest.java +++ b/src/test/java/io/github/diegourban/algorithms/datastructure/ElementTest.java @@ -21,4 +21,16 @@ void shouldNotEqualNull() { void shouldPrintItsName() { assertThat(new Element("diego")).hasToString("diego"); } + + @Test + void shouldNotEqualAnObjectOfAnotherType() { + assertThat(new Element("diego")).isNotEqualTo("diego"); + } + + @Test + void shouldHashEqualElementsAlike() { + assertThat(new Element("diego")).hasSameHashCodeAs(new Element("diego")); + assertThat(new java.util.HashSet<>(java.util.List.of(new Element("diego")))) + .contains(new Element("diego")); + } } 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 e2a6192..ba006fa 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 @@ -195,4 +195,41 @@ void shouldRemoveFirstAndLastByPosition() { void shouldPrintEmptyList() { assertThat(new LinkedList()).hasToString("[]"); } + + @Test + void shouldAddAtPositionZeroOfNonEmptyList() { + final LinkedList linkedList = new LinkedList(); + linkedList.addAtEnd(new Element("paulo")); + linkedList.addAtEnd(new Element("josé")); + + linkedList.add(0, new Element("diego")); + + assertThat(linkedList).hasToString("[diego,paulo,josé,]"); + assertThat(linkedList.getFirst().getNext().getPrevious().getElement()).isEqualTo(new Element("diego")); + } + + @Test + void shouldRejectRemovingFromEndOfEmptyList() { + assertThatIllegalStateException().isThrownBy(() -> new LinkedList().removeFromEnd()); + } + + @Test + void shouldRejectRemovingAMissingPosition() { + final LinkedList linkedList = new LinkedList(); + linkedList.addAtEnd(new Element("diego")); + + assertThatIllegalArgumentException().isThrownBy(() -> linkedList.remove(1)); + assertThat(linkedList.size()).isEqualTo(1); + } + + @Test + void shouldUnlinkTheRemovedNodeFromTheNewFirst() { + final LinkedList linkedList = new LinkedList(); + linkedList.addAtEnd(new Element("diego")); + linkedList.addAtEnd(new Element("paulo")); + + linkedList.removeFromStart(); + + assertThat(linkedList.getFirst().getPrevious()).isNull(); + } } diff --git a/src/test/java/io/github/diegourban/algorithms/datastructure/set/SetTest.java b/src/test/java/io/github/diegourban/algorithms/datastructure/set/SetTest.java index 001dc4d..0e22333 100644 --- a/src/test/java/io/github/diegourban/algorithms/datastructure/set/SetTest.java +++ b/src/test/java/io/github/diegourban/algorithms/datastructure/set/SetTest.java @@ -1,6 +1,7 @@ package io.github.diegourban.algorithms.datastructure.set; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import org.junit.jupiter.api.Test; @@ -53,4 +54,12 @@ void shouldIgnoreRemovingAMissingWord() { .hasToString( "[[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [Diego], [], [], []]"); } + + @Test + void shouldRejectAnEmptyWord() { + final Set set = new Set(); + + 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 e5a21e4..bd883a6 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 @@ -73,4 +73,44 @@ void shouldRemoveFromAFullVector() { assertThat(vector.size()).isEqualTo(99); assertThat(vector.get(0)).isEqualTo(new Element("Element 1")); } + + @Test + void shouldAddAtPositionEqualToSizeAsLast() { + final Vector vector = new Vector(); + vector.add(new Element("Joao")); + + vector.add(1, new Element("Jose")); + + assertThat(vector.size()).isEqualTo(2); + assertThat(vector.get(1)).isEqualTo(new Element("Jose")); + } + + @Test + void shouldAddAtPositionZeroOfEmptyVector() { + final Vector vector = new Vector(); + + vector.add(0, new Element("Joao")); + + assertThat(vector.size()).isEqualTo(1); + assertThat(vector.get(0)).isEqualTo(new Element("Joao")); + } + + @Test + void shouldRejectRemovingAMissingPosition() { + final Vector vector = new Vector(); + vector.add(new Element("Joao")); + vector.add(new Element("Jose")); + + assertThatIllegalArgumentException().isThrownBy(() -> vector.remove(5)); + assertThatIllegalArgumentException().isThrownBy(() -> vector.remove(-1)); + assertThat(vector.size()).isEqualTo(2); + } + + @Test + void shouldRejectRemovingFromEmptyVector() { + final Vector vector = new Vector(); + + assertThatIllegalArgumentException().isThrownBy(() -> vector.remove(0)); + assertThat(vector.size()).isZero(); + } } 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 380ff49..ebaaa9b 100644 --- a/src/test/java/io/github/diegourban/algorithms/io/ReaderTest.java +++ b/src/test/java/io/github/diegourban/algorithms/io/ReaderTest.java @@ -499,4 +499,13 @@ void shouldSkipNChars() throws Exception { assertThat(day).isEqualTo(5); } + + @Test + void shouldEndLinesAtCarriageReturnsAndSkipBlankOnes() throws Exception { + final Reader reader = new Reader(new java.io.ByteArrayInputStream("first\r\nsecond\r\r\nthird".getBytes())); + + assertThat(reader.readLine()).isEqualTo("first"); + assertThat(reader.readLine()).isEqualTo("second"); + assertThat(reader.readLine()).isEqualTo("third"); + } } diff --git a/src/test/java/io/github/diegourban/algorithms/math/NumberTheoryTest.java b/src/test/java/io/github/diegourban/algorithms/math/NumberTheoryTest.java index 6bb46af..db2d170 100644 --- a/src/test/java/io/github/diegourban/algorithms/math/NumberTheoryTest.java +++ b/src/test/java/io/github/diegourban/algorithms/math/NumberTheoryTest.java @@ -1,6 +1,7 @@ package io.github.diegourban.algorithms.math; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import java.util.List; import java.util.Set; @@ -162,4 +163,17 @@ void shouldCreateSetOfPrimeFactorsOfPrimeNumber() { final Set primeFactors = NumberTheory.primeFactorsSet(7); assertThat(primeFactors).hasSize(1).contains(7); } + + @Test + void shouldReturnZeroForTheLeastCommonMultipleWithZero() { + assertThat(NumberTheory.lcm(0, 0)).isZero(); + assertThat(NumberTheory.lcm(0, 5)).isZero(); + assertThat(NumberTheory.lcm(5, 0)).isZero(); + } + + @Test + void shouldFailInsteadOfOverflowingTheLeastCommonMultiple() { + assertThatExceptionOfType(ArithmeticException.class) + .isThrownBy(() -> NumberTheory.lcm(4_000_000_007L, 4_000_000_009L)); + } } 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 fc8aba5..38ab033 100644 --- a/src/test/java/io/github/diegourban/algorithms/math/NumbersTest.java +++ b/src/test/java/io/github/diegourban/algorithms/math/NumbersTest.java @@ -38,4 +38,29 @@ void shouldCalculateNumberLength() { assertThat(Numbers.length(123456789)).isEqualTo(9); assertThat(Numbers.length(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); + } + + @Test + void shouldSplitNegativeNumbersIntoTheDigitsOfTheirAbsoluteValue() { + assertThat(Numbers.toArray(-12)).containsExactly(2, 1); + assertThat(Numbers.toArray(Long.MIN_VALUE)).hasSize(19).startsWith(8, 0, 8); + } + + @Test + void shouldFindDigitZeroInZero() { + assertThat(Numbers.containsDigit(0, 0)).isTrue(); + assertThat(Numbers.containsDigit(0, 1)).isFalse(); + } + + @Test + void shouldCountDigitsOfNegativeNumbers() { + assertThat(Numbers.length(-123)).isEqualTo(3); + assertThat(Numbers.length(-7)).isEqualTo(1); + assertThat(Numbers.length(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 9cb25d3..ce3cf8f 100644 --- a/src/test/java/io/github/diegourban/algorithms/math/SequencesPropertyTest.java +++ b/src/test/java/io/github/diegourban/algorithms/math/SequencesPropertyTest.java @@ -19,7 +19,7 @@ void fibonacciIsExactUpTo46(@ForAll @IntRange(min = 0, max = 46) final int n) { } @Property(generation = GenerationMode.EXHAUSTIVE) - void simpleBinetFormulaRoundsToTheExactValueUpTo67(@ForAll @IntRange(min = 0, max = 67) final int n) { + void simpleBinetFormulaRoundsToTheExactValueUpTo70(@ForAll @IntRange(min = 0, max = 70) final int n) { assertThat(BigInteger.valueOf(Math.round(Sequences.fibonacciSimpleBinetFormula(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 b1d893a..62dcf76 100644 --- a/src/test/java/io/github/diegourban/algorithms/math/SequencesTest.java +++ b/src/test/java/io/github/diegourban/algorithms/math/SequencesTest.java @@ -51,6 +51,6 @@ void shouldCalculateFibonacciWithBinetFormula() { void shouldRejectNegativeFibonacciIndex() { assertThatIllegalArgumentException() .isThrownBy(() -> Sequences.fibonacci(-1)) - .withMessage("Index was negative. No such thing as a negative index in a series."); + .withMessage("n must be non-negative: -1"); } } 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 69556e3..25da7c0 100644 --- a/src/test/java/io/github/diegourban/algorithms/math/StatisticsTest.java +++ b/src/test/java/io/github/diegourban/algorithms/math/StatisticsTest.java @@ -1,6 +1,7 @@ package io.github.diegourban.algorithms.math; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.within; import org.junit.jupiter.api.Test; @@ -47,4 +48,14 @@ void shouldCalculateAverageAbsoluteDeviation() { assertThat(Statistics.meanAbsoluteDeviation(new double[] {2, 2, 3, 4, 14})) .isCloseTo(3.6, within(0.1)); } + + @Test + void shouldRejectEmptyDataForMedianAndDeviation() { + assertThatIllegalArgumentException() + .isThrownBy(() -> Statistics.median(new double[] {})) + .withMessage("data must not be empty"); + assertThatIllegalArgumentException() + .isThrownBy(() -> Statistics.meanAbsoluteDeviation(new double[] {})) + .withMessage("data must not be empty"); + } } 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 078a567..ceb7649 100644 --- a/src/test/java/io/github/diegourban/algorithms/math/TriangleTypeTest.java +++ b/src/test/java/io/github/diegourban/algorithms/math/TriangleTypeTest.java @@ -8,7 +8,7 @@ class TriangleTypeTest { @ParameterizedTest - @CsvSource({"EQUILATERAL, Equilatero", "ISOSCELES, Isoceles", "ESCALENE, Escaleno"}) + @CsvSource({"EQUILATERAL, Equilateral", "ISOSCELES, Isosceles", "ESCALENE, Scalene"}) void exposesDisplayName(final TriangleType type, final String expectedName) { assertThat(type.getName()).isEqualTo(expectedName); } diff --git a/src/test/java/io/github/diegourban/algorithms/string/StringUtilsTest.java b/src/test/java/io/github/diegourban/algorithms/string/StringUtilsTest.java index 2f4ff4f..9a628bd 100644 --- a/src/test/java/io/github/diegourban/algorithms/string/StringUtilsTest.java +++ b/src/test/java/io/github/diegourban/algorithms/string/StringUtilsTest.java @@ -24,4 +24,15 @@ void shouldNotConsiderPalindrome() { assertThat(StringUtils.isPalindrome("ab")).isFalse(); assertThat(StringUtils.isPalindrome("abb")).isFalse(); } + + @Test + void shouldCompareAnagramsIndependentlyOfTheDefaultLocale() { + final java.util.Locale original = java.util.Locale.getDefault(); + java.util.Locale.setDefault(java.util.Locale.forLanguageTag("tr-TR")); + try { + assertThat(StringUtils.isAnagram("TITLE", "title")).isTrue(); + } finally { + java.util.Locale.setDefault(original); + } + } }