From 9e210ae56003b90ffa4f1aa2e877ed68e09bd4cb Mon Sep 17 00:00:00 2001 From: hariv Date: Thu, 21 Jun 2018 06:42:13 -0700 Subject: [PATCH] Z algorithm implementation (#77) * Implemented Z algorithm * Fixed bugs in implementation and added tests * Added README explaining z algorithm --- src/algorithms/string/z-algorithm/README.md | 27 +++++++++ .../z-algorithm/__test__/zAlgorithm.test.js | 12 ++++ .../string/z-algorithm/zAlgorithm.js | 57 +++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 src/algorithms/string/z-algorithm/README.md create mode 100644 src/algorithms/string/z-algorithm/__test__/zAlgorithm.test.js create mode 100644 src/algorithms/string/z-algorithm/zAlgorithm.js diff --git a/src/algorithms/string/z-algorithm/README.md b/src/algorithms/string/z-algorithm/README.md new file mode 100644 index 00000000..733ebf5c --- /dev/null +++ b/src/algorithms/string/z-algorithm/README.md @@ -0,0 +1,27 @@ +# Z-algorithm + +The Z-algorithm finds occurrences of a "word" `W` +within a main "text string" `T` in linear time. + +Given a string `S` of length `n`, the algorithm produces +an array, `Z` where `Z[i]` represents the ongest substring +starting from `S[i]` which is also a prefix of `S`. Finding +`Z` for the string obtained by concatenating the word, `W` +with a nonce character, say `$` followed by the text, `T`, +helps with pattern matching, for if there is some index `i` +such that `Z[i]` equals the pattern length, then the pattern +must be present at that point. + +While the `Z` array can be computed with two nested loops, the +following strategy shows how to obtain it in linear time, based +on the idea that as we iterate over the letters in the string +(index `i` from `1` to `n - 1`), we maintain an interval `[L, R]` +which is the interval with maximum `R` such that `1 ≤ L ≤ i ≤ R` +and `S[L...R]` is a prefix that is also a substring (if no such +interval exists, just let `L = R =  - 1`). For `i = 1`, we can +simply compute `L` and `R` by comparing `S[0...]` to `S[1...]`. + +## Complexity + +- **Time:** `O(|W| + |T|)` +- **Space:** `O(|W|)` \ No newline at end of file diff --git a/src/algorithms/string/z-algorithm/__test__/zAlgorithm.test.js b/src/algorithms/string/z-algorithm/__test__/zAlgorithm.test.js new file mode 100644 index 00000000..d58ff75b --- /dev/null +++ b/src/algorithms/string/z-algorithm/__test__/zAlgorithm.test.js @@ -0,0 +1,12 @@ +import zAlgorithm from '../zAlgorithm'; + +describe('zAlgorithm', () => { + it('should find word position in given text', () => { + expect(zAlgorithm('abcbcglx', 'abca')).toBe(-1); + expect(zAlgorithm('abcbcglx', 'bcgl')).toBe(3); + expect(zAlgorithm('abcxabcdabxabcdabcdabcy', 'abcdabcy')).toBe(15); + expect(zAlgorithm('abcxabcdabxabcdabcdabcy', 'abcdabca')).toBe(-1); + expect(zAlgorithm('abcxabcdabxaabcdabcabcdabcdabcy', 'abcdabca')).toBe(12); + expect(zAlgorithm('abcxabcdabxaabaabaaaabcdabcdabcy', 'aabaabaaa')).toBe(11); + }); +}); diff --git a/src/algorithms/string/z-algorithm/zAlgorithm.js b/src/algorithms/string/z-algorithm/zAlgorithm.js new file mode 100644 index 00000000..f93318ce --- /dev/null +++ b/src/algorithms/string/z-algorithm/zAlgorithm.js @@ -0,0 +1,57 @@ +/** + * @param {string} word + * @param {string} text + * @return {number[]} + */ + +function buildZArray(word, text) { + const zString = `${word}$${text}`; + const zArray = new Array(zString.length); + let left = 0; + let right = 0; + let k = 0; + + for (let i = 1; i < zString.length; i += 1) { + if (i > right) { + left = i; + right = i; + + while (right < zString.length && zString[right - left] === zString[right]) { + right += 1; + } + + zArray[i] = right - left; + right -= 1; + } else { + k = i - left; + if (zArray[k] < (right - i) + 1) { + zArray[i] = zArray[k]; + } else { + left = i; + while (right < zString.length && zString[right - left] === zString[right]) { + right += 1; + } + + zArray[i] = right - left; + right -= 1; + } + } + } + + return zArray; +} + +/** + * @param {string} text + * @param {string} word + * @return {number} + */ +export default function zAlgorithm(text, word) { + const zArray = buildZArray(word, text); + for (let i = 1; i < zArray.length; i += 1) { + if (zArray[i] === word.length) { + return (i - word.length - 1); + } + } + return -1; +}