diff --git a/DIRECTORY.md b/DIRECTORY.md index 549e166cc8a7..13d1fe39c168 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -744,6 +744,7 @@ - 📄 [Rotation](src/main/java/com/thealgorithms/strings/Rotation.java) - 📄 [StringCompression](src/main/java/com/thealgorithms/strings/StringCompression.java) - 📄 [StringMatchFiniteAutomata](src/main/java/com/thealgorithms/strings/StringMatchFiniteAutomata.java) + - 📄 [SuffixArray](src/main/java/com/thealgorithms/strings/SuffixArray.java) - 📄 [Upper](src/main/java/com/thealgorithms/strings/Upper.java) - 📄 [ValidParentheses](src/main/java/com/thealgorithms/strings/ValidParentheses.java) - 📄 [WordLadder](src/main/java/com/thealgorithms/strings/WordLadder.java) @@ -1413,6 +1414,7 @@ - 📄 [RotationTest](src/test/java/com/thealgorithms/strings/RotationTest.java) - 📄 [StringCompressionTest](src/test/java/com/thealgorithms/strings/StringCompressionTest.java) - 📄 [StringMatchFiniteAutomataTest](src/test/java/com/thealgorithms/strings/StringMatchFiniteAutomataTest.java) + - 📄 [SuffixArrayTest](src/test/java/com/thealgorithms/strings/SuffixArrayTest.java) - 📄 [UpperTest](src/test/java/com/thealgorithms/strings/UpperTest.java) - 📄 [ValidParenthesesTest](src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java) - 📄 [WordLadderTest](src/test/java/com/thealgorithms/strings/WordLadderTest.java) diff --git a/src/main/java/com/thealgorithms/strings/SuffixArray.java b/src/main/java/com/thealgorithms/strings/SuffixArray.java new file mode 100644 index 000000000000..dbcac147b658 --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/SuffixArray.java @@ -0,0 +1,60 @@ +package com.thealgorithms.strings; + +import java.util.Arrays; + +/** + * Suffix Array implementation in Java. + * Builds an array of indices that represent all suffixes of a string in sorted order. + * Wikipedia Reference: https://en.wikipedia.org/wiki/Suffix_array + * Author: Nithin U. + * Github: https://github.com/NithinU2802 + */ + +public final class SuffixArray { + + private SuffixArray() { + } + + public static int[] buildSuffixArray(String text) { + int n = text.length(); + Integer[] suffixArray = new Integer[n]; + int[] rank = new int[n]; + int[] tempRank = new int[n]; + + // Initial ranking based on characters + for (int i = 0; i < n; i++) { + suffixArray[i] = i; + rank[i] = text.charAt(i); + } + + for (int k = 1; k < n; k *= 2) { + final int step = k; + + // Comparator: first by rank, then by rank + step + Arrays.sort(suffixArray, (a, b) -> { + if (rank[a] != rank[b]) { + return Integer.compare(rank[a], rank[b]); + } + int ra = (a + step < n) ? rank[a + step] : -1; + int rb = (b + step < n) ? rank[b + step] : -1; + return Integer.compare(ra, rb); + }); + + // Re-rank + tempRank[suffixArray[0]] = 0; + for (int i = 1; i < n; i++) { + int prev = suffixArray[i - 1]; + int curr = suffixArray[i]; + boolean sameRank = rank[prev] == rank[curr] && ((prev + step < n ? rank[prev + step] : -1) == (curr + step < n ? rank[curr + step] : -1)); + tempRank[curr] = sameRank ? tempRank[prev] : tempRank[prev] + 1; + } + + System.arraycopy(tempRank, 0, rank, 0, n); + + if (rank[suffixArray[n - 1]] == n - 1) { + break; + } + } + return Arrays.stream(suffixArray).mapToInt(Integer::intValue).toArray(); + } +} diff --git a/src/test/java/com/thealgorithms/strings/SuffixArrayTest.java b/src/test/java/com/thealgorithms/strings/SuffixArrayTest.java new file mode 100644 index 000000000000..9d4b3d582b75 --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/SuffixArrayTest.java @@ -0,0 +1,44 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +class SuffixArrayTest { + + @Test + void testEmptyString() { + int[] result = SuffixArray.buildSuffixArray(""); + assertArrayEquals(new int[] {}, result, "Empty string should return empty suffix array"); + } + + @Test + void testSingleCharacter() { + int[] result = SuffixArray.buildSuffixArray("a"); + assertArrayEquals(new int[] {0}, result, "Single char string should return [0]"); + } + + @Test + void testDistinctCharacters() { + int[] result = SuffixArray.buildSuffixArray("abc"); + assertArrayEquals(new int[] {0, 1, 2}, result, "Suffixes already in order for distinct chars"); + } + + @Test + void testBananaExample() { + int[] result = SuffixArray.buildSuffixArray("banana"); + assertArrayEquals(new int[] {5, 3, 1, 0, 4, 2}, result, "Suffix array of 'banana' should be [5,3,1,0,4,2]"); + } + + @Test + void testStringWithDuplicates() { + int[] result = SuffixArray.buildSuffixArray("aaaa"); + assertArrayEquals(new int[] {3, 2, 1, 0}, result, "Suffix array should be descending indices for 'aaaa'"); + } + + @Test + void testRandomString() { + int[] result = SuffixArray.buildSuffixArray("mississippi"); + assertArrayEquals(new int[] {10, 7, 4, 1, 0, 9, 8, 6, 3, 5, 2}, result, "Suffix array for 'mississippi' should match expected"); + } +}