From 74760ec28b9a66c752530f13fe08a619ac81da0e Mon Sep 17 00:00:00 2001 From: Waldemar Bira Date: Tue, 16 Oct 2018 18:35:15 +0200 Subject: [PATCH 1/2] Implemented longest common subsequence algorithm --- src/algorithms/string/index.js | 4 +- .../string/longest_common_subsequence.js | 55 +++++++++++++++++++ .../string/testLongestCommonSubsequence.js | 45 +++++++++++++++ 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 src/algorithms/string/longest_common_subsequence.js create mode 100644 test/algorithms/string/testLongestCommonSubsequence.js diff --git a/src/algorithms/string/index.js b/src/algorithms/string/index.js index ad535890..a1d3e7e6 100644 --- a/src/algorithms/string/index.js +++ b/src/algorithms/string/index.js @@ -1,5 +1,7 @@ const levenshteindistance = require('./levenshtein_distance'); +const logestcommonsubsequence = require('./longest_common_subsequence'); module.exports = { - levenshteindistance + levenshteindistance, + logestcommonsubsequence }; diff --git a/src/algorithms/string/longest_common_subsequence.js b/src/algorithms/string/longest_common_subsequence.js new file mode 100644 index 00000000..4ffcadca --- /dev/null +++ b/src/algorithms/string/longest_common_subsequence.js @@ -0,0 +1,55 @@ +function readSolution(dp, firstWord, secondWord, i, j) { + if (i === 0 || j === 0) return ''; + + if (firstWord.charAt(i - 1) === secondWord.charAt(j - 1)) { + return readSolution(dp, secondWord, firstWord, i - 1, j - 1) + firstWord[i - 1]; + } + + if (dp[i][j - 1] > dp[i - 1][j]) { + return readSolution(dp, secondWord, firstWord, i, j - 1); + } + return readSolution(dp, secondWord, firstWord, i - 1, j); +} + +/** + * Calculates GCD of two numbers + * @param {String} firstWord First string + * @param {String} secondWord Second String + * @return {String} One of the possbile longest common subsequence for given inputs + * + * References: https://en.wikipedia.org/wiki/Longest_common_subsequence_problem + */ + +function longestcommonsubsequence(firstWord = '', secondWord = '') { + const firstWordSize = firstWord.length; + const secondWordSize = secondWord.length; + if (secondWordSize === 0) { + return ''; + } + + if (firstWordSize === 0) { + return ''; + } + const dp = [...Array(firstWordSize + 1)].map(() => Array(secondWordSize + 1)); + + for (let i = 0; i <= firstWordSize; i += 1) { + dp[i][0] = 0; + } + + for (let i = 0; i <= secondWordSize; i += 1) { + dp[0][i] = 0; + } + + for (let i = 1; i <= firstWordSize; i += 1) { + for (let j = 1; j <= secondWordSize; j += 1) { + if (firstWord.charAt(i - 1) === secondWord.charAt(j - 1)) { + dp[i][j] = dp[i - 1][j - 1] + 1; + } else { + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); + } + } + } + return readSolution(dp, firstWord, secondWord, firstWordSize, secondWordSize); +} + +module.exports = longestcommonsubsequence; diff --git a/test/algorithms/string/testLongestCommonSubsequence.js b/test/algorithms/string/testLongestCommonSubsequence.js new file mode 100644 index 00000000..33bd4bdf --- /dev/null +++ b/test/algorithms/string/testLongestCommonSubsequence.js @@ -0,0 +1,45 @@ +/* eslint-env mocha */ +const logestcommonsubsequence = require('../../../src').algorithms.string.logestcommonsubsequence; +const assert = require('assert'); + +describe('LCS', () => { + it('should find four letter long lsc for POLITECHNIKA and TOALETA', () => { + const stringA = 'POLITECHNIKA'; + const stringB = 'TOALETA'; + + const distance = logestcommonsubsequence(stringB, stringA); + assert.equal(distance.length, 4); + }); + + it('should find one letter long lsc for 123 and 543', () => { + const stringA = '123'; + const stringB = '543'; + + const distance = logestcommonsubsequence(stringA, stringB); + assert.equal(distance.length, 1); + }); + + it('should find four letter long lsc for abaabbaaa and babab', () => { + const stringA = 'abaabbaaa'; + const stringB = 'babab'; + + const distance = logestcommonsubsequence(stringA, stringB); + assert.equal(distance.length, 4); + }); + + it('should return empty string when one of inputs is empty', () => { + const stringA = 'abaabbaaa'; + const stringB = ''; + + const distance = logestcommonsubsequence(stringA, stringB); + assert.equal(distance.length, 0); + }); + + it('should return empty string when both inputs dont have lcs', () => { + const stringA = 'abs'; + const stringB = '123'; + + const distance = logestcommonsubsequence(stringA, stringB); + assert.equal(distance.length, 0); + }); +}); From 6094226aeeeec86e967243004ed1ad35240848b9 Mon Sep 17 00:00:00 2001 From: remes2000 Date: Sat, 1 Aug 2020 17:11:27 +0200 Subject: [PATCH 2/2] Re-write read solution method, change LCS tests, change eslint configuration to ignore linebreak rule --- .eslintrc | 3 +- .../string/longest_common_subsequence.js | 31 +++++++++++++------ .../string/testLongestCommonSubsequence.js | 18 +++++------ 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/.eslintrc b/.eslintrc index 262dafce..903ab11b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,6 +8,7 @@ "no-param-reassign": "off", "class-methods-use-this": "off", "no-bitwise": "off", - "no-continue": "off" + "no-continue": "off", + "linebreak-style": 0 } } diff --git a/src/algorithms/string/longest_common_subsequence.js b/src/algorithms/string/longest_common_subsequence.js index 4ffcadca..588c00fe 100644 --- a/src/algorithms/string/longest_common_subsequence.js +++ b/src/algorithms/string/longest_common_subsequence.js @@ -1,18 +1,29 @@ function readSolution(dp, firstWord, secondWord, i, j) { - if (i === 0 || j === 0) return ''; - - if (firstWord.charAt(i - 1) === secondWord.charAt(j - 1)) { - return readSolution(dp, secondWord, firstWord, i - 1, j - 1) + firstWord[i - 1]; - } - - if (dp[i][j - 1] > dp[i - 1][j]) { - return readSolution(dp, secondWord, firstWord, i, j - 1); + let solution = ''; + let value; + let leftCellValue; + let aboveCellValue; + while (i !== 0 && j !== 0) { + value = dp[i][j]; + leftCellValue = dp[i][j - 1]; + aboveCellValue = dp[i - 1][j]; + if (leftCellValue === value) { + j -= 1; + continue; + } else if (aboveCellValue === value) { + i -= 1; + continue; + } else { + solution = firstWord.charAt(i - 1) + solution; + i -= 1; + j -= 1; + } } - return readSolution(dp, secondWord, firstWord, i - 1, j); + return solution; } /** - * Calculates GCD of two numbers + * Calculates Longest Common Subsequence (LCS) of two numbers * @param {String} firstWord First string * @param {String} secondWord Second String * @return {String} One of the possbile longest common subsequence for given inputs diff --git a/test/algorithms/string/testLongestCommonSubsequence.js b/test/algorithms/string/testLongestCommonSubsequence.js index 33bd4bdf..75420b98 100644 --- a/test/algorithms/string/testLongestCommonSubsequence.js +++ b/test/algorithms/string/testLongestCommonSubsequence.js @@ -3,28 +3,28 @@ const logestcommonsubsequence = require('../../../src').algorithms.string.logest const assert = require('assert'); describe('LCS', () => { - it('should find four letter long lsc for POLITECHNIKA and TOALETA', () => { + it('should find OLTA or OLEA for POLITECHNIKA and TOALETA', () => { const stringA = 'POLITECHNIKA'; const stringB = 'TOALETA'; - const distance = logestcommonsubsequence(stringB, stringA); - assert.equal(distance.length, 4); + const result = logestcommonsubsequence(stringB, stringA); + assert.equal(['OLTA', 'OLEA'].indexOf(result) !== -1, true); }); - it('should find one letter long lsc for 123 and 543', () => { + it('should find 3 for 123 and 543', () => { const stringA = '123'; const stringB = '543'; - const distance = logestcommonsubsequence(stringA, stringB); - assert.equal(distance.length, 1); + const result = logestcommonsubsequence(stringA, stringB); + assert.equal(result, '3'); }); - it('should find four letter long lsc for abaabbaaa and babab', () => { + it('should find baba or abab for abaabbaaa and babab', () => { const stringA = 'abaabbaaa'; const stringB = 'babab'; - const distance = logestcommonsubsequence(stringA, stringB); - assert.equal(distance.length, 4); + const result = logestcommonsubsequence(stringA, stringB); + assert.equal(['baba', 'abab'].indexOf(result) !== -1, true); }); it('should return empty string when one of inputs is empty', () => {