diff --git a/src/algorithms/string/combinations/__test__/combineWithoutRepetitions.test.js b/src/algorithms/string/combinations/__test__/combineWithoutRepetitions.test.js new file mode 100644 index 00000000..6d2987ec --- /dev/null +++ b/src/algorithms/string/combinations/__test__/combineWithoutRepetitions.test.js @@ -0,0 +1,40 @@ +import combineWithoutRepetitions from '../combineWithoutRepetitions'; +import factorial from '../../../math/factorial/factorial'; + +describe('combineWithoutRepetitions', () => { + it('should combine string without repetitions', () => { + expect(combineWithoutRepetitions('AB', 3)).toEqual([]); + expect(combineWithoutRepetitions('AB', 1)).toEqual(['A', 'B']); + expect(combineWithoutRepetitions('A', 1)).toEqual(['A']); + expect(combineWithoutRepetitions('AB', 2)).toEqual(['AB']); + expect(combineWithoutRepetitions('ABC', 2)).toEqual(['AB', 'AC', 'BC']); + expect(combineWithoutRepetitions('ABC', 3)).toEqual(['ABC']); + expect(combineWithoutRepetitions('ABCD', 3)).toEqual([ + 'ABC', + 'ABD', + 'ACD', + 'BCD', + ]); + expect(combineWithoutRepetitions('ABCDE', 3)).toEqual([ + 'ABC', + 'ABD', + 'ABE', + 'ACD', + 'ACE', + 'ADE', + 'BCD', + 'BCE', + 'BDE', + 'CDE', + ]); + + const combinationOptions = 'ABCDEFGH'; + const combinationSlotsNumber = 4; + const combinations = combineWithoutRepetitions(combinationOptions, combinationSlotsNumber); + const n = combinationOptions.length; + const r = combinationSlotsNumber; + const expectedNumberOfCombinations = factorial(n) / (factorial(r) * factorial(n - r)); + + expect(combinations.length).toBe(expectedNumberOfCombinations); + }); +}); diff --git a/src/algorithms/string/combinations/combineWithoutRepetitions.js b/src/algorithms/string/combinations/combineWithoutRepetitions.js new file mode 100644 index 00000000..c087990d --- /dev/null +++ b/src/algorithms/string/combinations/combineWithoutRepetitions.js @@ -0,0 +1,67 @@ +/* + @see: https://stackoverflow.com/a/127898/7794070 + + Lets say your array of letters looks like this: "ABCDEFGH". + You have three indices (i, j, k) indicating which letters you + are going to use for the current word, You start with: + + A B C D E F G H + ^ ^ ^ + i j k + + First you vary k, so the next step looks like that: + + A B C D E F G H + ^ ^ ^ + i j k + + If you reached the end you go on and vary j and then k again. + + A B C D E F G H + ^ ^ ^ + i j k + + A B C D E F G H + ^ ^ ^ + i j k + + Once you j reached G you start also to vary i. + + A B C D E F G H + ^ ^ ^ + i j k + + A B C D E F G H + ^ ^ ^ + i j k + ... + */ + +/** + * @param {string} combinationOptions + * @param {number} combinationLength + * @return {string[]} + */ +export default function combineWithoutRepetitions(combinationOptions, combinationLength) { + // If combination length is just 1 then return combinationOptions. + if (combinationLength === 1) { + return Array.from(combinationOptions); + } + + // Init combinations array. + const combinations = []; + + for (let i = 0; i <= (combinationOptions.length - combinationLength); i += 1) { + const smallerCombinations = combineWithoutRepetitions( + combinationOptions.substr(i + 1), + combinationLength - 1, + ); + + for (let j = 0; j < smallerCombinations.length; j += 1) { + combinations.push(combinationOptions[i] + smallerCombinations[j]); + } + } + + // Return all calculated combinations. + return combinations; +}