diff --git a/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java b/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java index d60b95110fc2..8c4204e1239f 100644 --- a/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java +++ b/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java @@ -1,18 +1,20 @@ package com.thealgorithms.datastructures.bloomfilter; +import java.util.Arrays; import java.util.BitSet; /** * A generic BloomFilter implementation for probabilistic membership checking. *

- * Bloom filters are space-efficient data structures that provide a fast way to test whether an - * element is a member of a set. They may produce false positives, indicating an element is + * Bloom filters are space-efficient data structures that provide a fast way to + * test whether an + * element is a member of a set. They may produce false positives, indicating an + * element is * in the set when it is not, but they will never produce false negatives. *

* * @param The type of elements to be stored in the Bloom filter. */ -@SuppressWarnings("rawtypes") public class BloomFilter { private final int numberOfHashFunctions; @@ -20,11 +22,14 @@ public class BloomFilter { private final Hash[] hashFunctions; /** - * Constructs a BloomFilter with a specified number of hash functions and bit array size. + * Constructs a BloomFilter with a specified number of hash functions and bit + * array size. * * @param numberOfHashFunctions the number of hash functions to use - * @param bitArraySize the size of the bit array, which determines the capacity of the filter - * @throws IllegalArgumentException if numberOfHashFunctions or bitArraySize is less than 1 + * @param bitArraySize the size of the bit array, which determines the + * capacity of the filter + * @throws IllegalArgumentException if numberOfHashFunctions or bitArraySize is + * less than 1 */ @SuppressWarnings("unchecked") public BloomFilter(int numberOfHashFunctions, int bitArraySize) { @@ -33,12 +38,13 @@ public BloomFilter(int numberOfHashFunctions, int bitArraySize) { } this.numberOfHashFunctions = numberOfHashFunctions; this.bitArray = new BitSet(bitArraySize); - this.hashFunctions = new Hash[numberOfHashFunctions]; + this.hashFunctions = (Hash[]) new Hash[numberOfHashFunctions]; initializeHashFunctions(); } /** - * Initializes the hash functions with unique indices to ensure different hashing. + * Initializes the hash functions with unique indices to ensure different + * hashing. */ private void initializeHashFunctions() { for (int i = 0; i < numberOfHashFunctions; i++) { @@ -49,7 +55,8 @@ private void initializeHashFunctions() { /** * Inserts an element into the Bloom filter. *

- * This method hashes the element using all defined hash functions and sets the corresponding + * This method hashes the element using all defined hash functions and sets the + * corresponding * bits in the bit array. *

* @@ -65,13 +72,16 @@ public void insert(T key) { /** * Checks if an element might be in the Bloom filter. *

- * This method checks the bits at the positions computed by each hash function. If any of these - * bits are not set, the element is definitely not in the filter. If all bits are set, the element + * This method checks the bits at the positions computed by each hash function. + * If any of these + * bits are not set, the element is definitely not in the filter. If all bits + * are set, the element * might be in the filter. *

* * @param key the element to check for membership in the Bloom filter - * @return {@code true} if the element might be in the Bloom filter, {@code false} if it is definitely not + * @return {@code true} if the element might be in the Bloom filter, + * {@code false} if it is definitely not */ public boolean contains(T key) { for (Hash hash : hashFunctions) { @@ -86,7 +96,8 @@ public boolean contains(T key) { /** * Inner class representing a hash function used by the Bloom filter. *

- * Each instance of this class represents a different hash function based on its index. + * Each instance of this class represents a different hash function based on its + * index. *

* * @param The type of elements to be hashed. @@ -109,13 +120,27 @@ private static class Hash { *

* The hash value is calculated by multiplying the index of the hash function * with the ASCII sum of the string representation of the key. + * For array types, the content of the array is used instead of the default + * toString. *

* * @param key the element to hash * @return the computed hash value */ public int compute(T key) { - return index * asciiString(String.valueOf(key)); + String keyString = switch (key) { + case Object[] arr -> Arrays.deepToString(arr); + case int[] arr -> Arrays.toString(arr); + case long[] arr -> Arrays.toString(arr); + case double[] arr -> Arrays.toString(arr); + case float[] arr -> Arrays.toString(arr); + case boolean[] arr -> Arrays.toString(arr); + case byte[] arr -> Arrays.toString(arr); + case char[] arr -> Arrays.toString(arr); + case short[] arr -> Arrays.toString(arr); + case null, default -> String.valueOf(key); + }; + return index * asciiString(keyString); } /** @@ -131,9 +156,9 @@ public int compute(T key) { private int asciiString(String word) { int sum = 0; for (char c : word.toCharArray()) { - sum += c; - } - return sum; + sum += c; + } + return sum; + } } } -} diff --git a/src/main/java/com/thealgorithms/others/SkylineProblem.java b/src/main/java/com/thealgorithms/others/SkylineProblem.java index e84a5c5b585b..d9e2a8cc2547 100644 --- a/src/main/java/com/thealgorithms/others/SkylineProblem.java +++ b/src/main/java/com/thealgorithms/others/SkylineProblem.java @@ -1,17 +1,58 @@ package com.thealgorithms.others; import java.util.ArrayList; +import java.util.List; +import java.util.Objects; /** - * The {@code SkylineProblem} class is used to solve the skyline problem using a - * divide-and-conquer approach. - * It reads input for building data, processes it to find the skyline, and - * prints the skyline. + *

Skyline Problem

+ *

+ * Solves the classic skyline problem using a divide-and-conquer approach. Given + * a list of buildings (each defined by left, height, right), + * computes the silhouette (skyline) formed by these buildings when viewed from + * a distance. + *

+ *

+ * Usage example: + * + *

+ * SkylineProblem sp = new SkylineProblem();
+ * sp.building = new SkylineProblem.Building[3];
+ * sp.add(1, 10, 5);
+ * sp.add(2, 15, 7);
+ * sp.add(3, 12, 9);
+ * List skyline = sp.findSkyline(0, 2);
+ * 
+ *

+ *

+ * This class is not thread-safe. + *

*/ public class SkylineProblem { - Building[] building; - int count; + /** + * Array of buildings to process. Must be initialized before use. + */ + private Building[] building; + + /** + * Number of buildings added so far. + */ + public int count; + + /** + * Sets the building array to the specified size. + * + * @param size The size of the building array. + * @throws IllegalArgumentException if size is negative + */ + public void setBuilding(int size) { + if (size < 0) { + throw new IllegalArgumentException("Size must be non-negative"); + } + this.building = new Building[size]; + this.count = 0; + } /** * Adds a building with the given left, height, and right values to the @@ -20,8 +61,23 @@ public class SkylineProblem { * @param left The left x-coordinate of the building. * @param height The height of the building. * @param right The right x-coordinate of the building. + * @throws IllegalArgumentException if left >= right or height < 0 + * @throws IllegalStateException if building array is not initialized or is + * full */ public void add(int left, int height, int right) { + if (building == null) { + throw new IllegalStateException("Building array not initialized"); + } + if (count >= building.length) { + throw new IllegalStateException("Building array is full"); + } + if (left >= right) { + throw new IllegalArgumentException("Left coordinate must be less than right coordinate"); + } + if (height < 0) { + throw new IllegalArgumentException("Height must be non-negative"); + } building[count++] = new Building(left, height, right); } @@ -29,92 +85,99 @@ public void add(int left, int height, int right) { * Computes the skyline for a range of buildings using the divide-and-conquer * strategy. * - * @param start The starting index of the buildings to process. - * @param end The ending index of the buildings to process. + * @param start The starting index of the buildings to process (inclusive). + * @param end The ending index of the buildings to process (inclusive). * @return A list of {@link Skyline} objects representing the computed skyline. + * @throws IllegalArgumentException if indices are out of bounds or building + * array is null */ - public ArrayList findSkyline(int start, int end) { + public List findSkyline(int start, int end) { + if (building == null) { + throw new IllegalArgumentException("Building array is not initialized"); + } + if (start < 0 || end >= count || start > end) { + throw new IllegalArgumentException("Invalid start or end index"); + } // Base case: only one building, return its skyline. if (start == end) { - ArrayList list = new ArrayList<>(); + List list = new ArrayList<>(); list.add(new Skyline(building[start].left, building[start].height)); - list.add(new Skyline(building[end].right, 0)); // Add the end of the building + list.add(new Skyline(building[start].right, 0)); // Add the end of the building return list; } int mid = (start + end) / 2; - - ArrayList sky1 = this.findSkyline(start, mid); // Find the skyline of the left half - ArrayList sky2 = this.findSkyline(mid + 1, end); // Find the skyline of the right half + List sky1 = this.findSkyline(start, mid); // Find the skyline of the left half + List sky2 = this.findSkyline(mid + 1, end); // Find the skyline of the right half return this.mergeSkyline(sky1, sky2); // Merge the two skylines } /** * Merges two skylines (sky1 and sky2) into one combined skyline. * - * @param sky1 The first skyline list. - * @param sky2 The second skyline list. + * @param sky1 The first skyline list. Not modified. + * @param sky2 The second skyline list. Not modified. * @return A list of {@link Skyline} objects representing the merged skyline. + * @throws NullPointerException if either argument is null */ - public ArrayList mergeSkyline(ArrayList sky1, ArrayList sky2) { - int currentH1 = 0; - int currentH2 = 0; - ArrayList skyline = new ArrayList<>(); - int maxH = 0; - - // Merge the two skylines - while (!sky1.isEmpty() && !sky2.isEmpty()) { - if (sky1.get(0).coordinates < sky2.get(0).coordinates) { - int currentX = sky1.get(0).coordinates; - currentH1 = sky1.get(0).height; - - if (currentH1 < currentH2) { - sky1.remove(0); - if (maxH != currentH2) { - skyline.add(new Skyline(currentX, currentH2)); - } - } else { - maxH = currentH1; - sky1.remove(0); - skyline.add(new Skyline(currentX, currentH1)); - } - } else { - int currentX = sky2.get(0).coordinates; - currentH2 = sky2.get(0).height; - - if (currentH2 < currentH1) { - sky2.remove(0); - if (maxH != currentH1) { - skyline.add(new Skyline(currentX, currentH1)); - } - } else { - maxH = currentH2; - sky2.remove(0); - skyline.add(new Skyline(currentX, currentH2)); - } + public List mergeSkyline(List sky1, List sky2) { + Objects.requireNonNull(sky1, "sky1 must not be null"); + Objects.requireNonNull(sky2, "sky2 must not be null"); + int i = 0; + int j = 0; + int h1 = 0; + int h2 = 0; + int prevHeight = 0; + List result = new ArrayList<>(); + while (i < sky1.size() && j < sky2.size()) { + Skyline p1 = sky1.get(i); + Skyline p2 = sky2.get(j); + int x; + if (p1.coordinates < p2.coordinates) { + x = p1.coordinates; + h1 = p1.height; + i++; + } else if (p2.coordinates < p1.coordinates) { + x = p2.coordinates; + h2 = p2.height; + j++; + } else { // same x + x = p1.coordinates; + h1 = p1.height; + h2 = p2.height; + i++; + j++; + } + int maxH = Math.max(h1, h2); + if (result.isEmpty() || prevHeight != maxH) { + result.add(new Skyline(x, maxH)); + prevHeight = maxH; } } - - // Add any remaining points from sky1 or sky2 - while (!sky1.isEmpty()) { - skyline.add(sky1.get(0)); - sky1.remove(0); + // Append remaining points + while (i < sky1.size()) { + Skyline p = sky1.get(i++); + if (result.isEmpty() || result.get(result.size() - 1).height != p.height || result.get(result.size() - 1).coordinates != p.coordinates) { + result.add(new Skyline(p.coordinates, p.height)); + } } - - while (!sky2.isEmpty()) { - skyline.add(sky2.get(0)); - sky2.remove(0); + while (j < sky2.size()) { + Skyline p = sky2.get(j++); + if (result.isEmpty() || result.get(result.size() - 1).height != p.height || result.get(result.size() - 1).coordinates != p.coordinates) { + result.add(new Skyline(p.coordinates, p.height)); + } } - - return skyline; + return result; } /** - * A class representing a point in the skyline with its x-coordinate and height. + * Represents a point in the skyline with its x-coordinate and height. */ - public class Skyline { - public int coordinates; - public int height; + public static class Skyline { + /** The x-coordinate of the skyline point. */ + public final int coordinates; + /** The height of the skyline at the given coordinate. */ + public final int height; /** * Constructor for the {@code Skyline} class. @@ -126,16 +189,40 @@ public Skyline(int coordinates, int height) { this.coordinates = coordinates; this.height = height; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Skyline skyline = (Skyline) o; + return coordinates == skyline.coordinates && height == skyline.height; + } + + @Override + public int hashCode() { + return Objects.hash(coordinates, height); + } + + @Override + public String toString() { + return "(" + coordinates + ", " + height + ")"; + } } /** - * A class representing a building with its left, height, and right - * x-coordinates. + * Represents a building with its left, height, and right x-coordinates. */ - public class Building { - public int left; - public int height; - public int right; + public static class Building { + /** The left x-coordinate of the building. */ + public final int left; + /** The height of the building. */ + public final int height; + /** The right x-coordinate of the building. */ + public final int right; /** * Constructor for the {@code Building} class. @@ -149,5 +236,11 @@ public Building(int left, int height, int right) { this.height = height; this.right = right; } + + @Override + public String toString() { + return "Building{" + + "left=" + left + ", height=" + height + ", right=" + right + '}'; + } } } diff --git a/src/test/java/com/thealgorithms/others/SkylineProblemTest.java b/src/test/java/com/thealgorithms/others/SkylineProblemTest.java index 1ed5ced709c1..3112d769debf 100644 --- a/src/test/java/com/thealgorithms/others/SkylineProblemTest.java +++ b/src/test/java/com/thealgorithms/others/SkylineProblemTest.java @@ -1,86 +1,130 @@ package com.thealgorithms.others; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.ArrayList; +import java.util.List; import org.junit.jupiter.api.Test; -public class SkylineProblemTest { +class SkylineProblemTest { @Test - public void testSingleBuildingSkyline() { + void testSingleBuildingSkyline() { SkylineProblem skylineProblem = new SkylineProblem(); - skylineProblem.building = new SkylineProblem.Building[1]; + skylineProblem.setBuilding(1); skylineProblem.add(2, 10, 9); - ArrayList result = skylineProblem.findSkyline(0, 0); + List result = skylineProblem.findSkyline(0, 0); - assertEquals(2, result.get(0).coordinates); - assertEquals(10, result.get(0).height); - assertEquals(9, result.get(1).coordinates); - assertEquals(0, result.get(1).height); + assertEquals(List.of(new SkylineProblem.Skyline(2, 10), new SkylineProblem.Skyline(9, 0)), result); } @Test - public void testTwoBuildingsSkyline() { + void testTwoBuildingsSkyline() { SkylineProblem skylineProblem = new SkylineProblem(); - skylineProblem.building = new SkylineProblem.Building[2]; + skylineProblem.setBuilding(2); skylineProblem.add(1, 11, 5); skylineProblem.add(2, 6, 7); - ArrayList result = skylineProblem.findSkyline(0, 1); + List result = skylineProblem.findSkyline(0, 1); // Expected skyline points: (1, 11), (5, 6), (7, 0) - assertEquals(1, result.get(0).coordinates); - assertEquals(11, result.get(0).height); - assertEquals(5, result.get(1).coordinates); - assertEquals(6, result.get(1).height); - assertEquals(7, result.get(2).coordinates); - assertEquals(0, result.get(2).height); + assertEquals(List.of(new SkylineProblem.Skyline(1, 11), new SkylineProblem.Skyline(5, 6), new SkylineProblem.Skyline(7, 0)), result); } @Test - public void testMergeSkyline() { + void testMergeSkyline() { + List sky1 = List.of(new SkylineProblem.Skyline(2, 10), new SkylineProblem.Skyline(9, 0)); + List sky2 = List.of(new SkylineProblem.Skyline(3, 15), new SkylineProblem.Skyline(7, 0)); SkylineProblem skylineProblem = new SkylineProblem(); - ArrayList sky1 = new ArrayList<>(); - ArrayList sky2 = new ArrayList<>(); - - sky1.add(skylineProblem.new Skyline(2, 10)); - sky1.add(skylineProblem.new Skyline(9, 0)); - - sky2.add(skylineProblem.new Skyline(3, 15)); - sky2.add(skylineProblem.new Skyline(7, 0)); - - ArrayList result = skylineProblem.mergeSkyline(sky1, sky2); + List result = skylineProblem.mergeSkyline(sky1, sky2); // Expected merged skyline: (2, 10), (3, 15), (7, 10), (9, 0) - assertEquals(2, result.get(0).coordinates); - assertEquals(10, result.get(0).height); - assertEquals(3, result.get(1).coordinates); - assertEquals(15, result.get(1).height); - assertEquals(7, result.get(2).coordinates); - assertEquals(10, result.get(2).height); - assertEquals(9, result.get(3).coordinates); - assertEquals(0, result.get(3).height); + assertEquals(List.of(new SkylineProblem.Skyline(2, 10), new SkylineProblem.Skyline(3, 15), new SkylineProblem.Skyline(7, 10), new SkylineProblem.Skyline(9, 0)), result); } @Test - public void testMultipleBuildingsSkyline() { + void testMultipleBuildingsSkyline() { SkylineProblem skylineProblem = new SkylineProblem(); - skylineProblem.building = new SkylineProblem.Building[3]; + skylineProblem.setBuilding(3); skylineProblem.add(1, 10, 5); skylineProblem.add(2, 15, 7); skylineProblem.add(3, 12, 9); - ArrayList result = skylineProblem.findSkyline(0, 2); + List result = skylineProblem.findSkyline(0, 2); + + assertEquals(List.of(new SkylineProblem.Skyline(1, 10), new SkylineProblem.Skyline(2, 15), new SkylineProblem.Skyline(7, 12), new SkylineProblem.Skyline(9, 0)), result); + } + + @Test + void testAddBuildingInvalidCases() { + SkylineProblem skylineProblem = new SkylineProblem(); + // Not initialized + Exception ex = assertThrows(IllegalStateException.class, () -> skylineProblem.add(1, 2, 3)); + assertTrue(ex.getMessage().contains("not initialized")); + + skylineProblem.setBuilding(1); + skylineProblem.add(1, 2, 3); + // Array full + Exception ex2 = assertThrows(IllegalStateException.class, () -> skylineProblem.add(4, 5, 6)); + assertTrue(ex2.getMessage().contains("full")); + + // Invalid left >= right + SkylineProblem skylineProblem2 = new SkylineProblem(); + skylineProblem2.setBuilding(1); + Exception ex3 = assertThrows(IllegalArgumentException.class, () -> skylineProblem2.add(5, 2, 2)); + assertTrue(ex3.getMessage().contains("Left coordinate")); + + // Invalid height < 0 + Exception ex4 = assertThrows(IllegalArgumentException.class, () -> skylineProblem2.add(1, -1, 2)); + assertTrue(ex4.getMessage().contains("Height must be non-negative")); + } + + @Test + void testFindSkylineInvalidCases() { + SkylineProblem skylineProblem = new SkylineProblem(); + // Not initialized + Exception ex = assertThrows(IllegalArgumentException.class, () -> skylineProblem.findSkyline(0, 0)); + assertTrue(ex.getMessage().contains("not initialized")); + + skylineProblem.setBuilding(2); + skylineProblem.count = 1; + Exception ex2 = assertThrows(IllegalArgumentException.class, () -> skylineProblem.findSkyline(0, 1)); + assertTrue(ex2.getMessage().contains("Invalid start or end index")); + } + + @Test + void testMergeSkylineNullCases() { + SkylineProblem skylineProblem = new SkylineProblem(); + Exception ex1 = assertThrows(NullPointerException.class, () -> skylineProblem.mergeSkyline(null, List.of())); + Exception ex2 = assertThrows(NullPointerException.class, () -> skylineProblem.mergeSkyline(List.of(), null)); + assertTrue(ex1.getMessage().contains("sky1")); + assertTrue(ex2.getMessage().contains("sky2")); + } + + @Test + void testSkylineEqualsAndHashCode() { + SkylineProblem.Skyline s1 = new SkylineProblem.Skyline(1, 2); + SkylineProblem.Skyline s2 = new SkylineProblem.Skyline(1, 2); + SkylineProblem.Skyline s3 = new SkylineProblem.Skyline(2, 2); + assertEquals(s1, s2); + assertEquals(s1.hashCode(), s2.hashCode()); + assertNotEquals(s1, s3); + assertNotEquals(null, s1); + assertNotEquals("string", s1); + } + + @Test + void testSkylineToString() { + SkylineProblem.Skyline s = new SkylineProblem.Skyline(5, 10); + assertEquals("(5, 10)", s.toString()); + } - assertEquals(1, result.get(0).coordinates); - assertEquals(10, result.get(0).height); - assertEquals(2, result.get(1).coordinates); - assertEquals(15, result.get(1).height); - assertEquals(7, result.get(2).coordinates); - assertEquals(12, result.get(2).height); - assertEquals(9, result.get(3).coordinates); - assertEquals(0, result.get(3).height); + @Test + void testBuildingToString() { + SkylineProblem.Building b = new SkylineProblem.Building(1, 2, 3); + assertEquals("Building{left=1, height=2, right=3}", b.toString()); } }