diff --git a/src/algorithms/string/combinations/__test__/combineWithRepetitions.test.js b/src/algorithms/string/combinations/__test__/combineWithRepetitions.test.js new file mode 100644 index 00000000..90c38958 --- /dev/null +++ b/src/algorithms/string/combinations/__test__/combineWithRepetitions.test.js @@ -0,0 +1,59 @@ +import combineWithRepetitions from '../combineWithRepetitions'; +import factorial from '../../../math/factorial/factorial'; + +describe('combineWithRepetitions', () => { + it('should combine string with repetitions', () => { + expect(combineWithRepetitions(['A'], 1)).toEqual([ + ['A'], + ]); + + expect(combineWithRepetitions(['A', 'B'], 1)).toEqual([ + ['A'], + ['B'], + ]); + + expect(combineWithRepetitions(['A', 'B'], 2)).toEqual([ + ['A', 'A'], + ['A', 'B'], + ['B', 'B'], + ]); + + expect(combineWithRepetitions(['A', 'B'], 3)).toEqual([ + ['A', 'A', 'A'], + ['A', 'A', 'B'], + ['A', 'B', 'B'], + ['B', 'B', 'B'], + ]); + + expect(combineWithRepetitions(['A', 'B', 'C'], 2)).toEqual([ + ['A', 'A'], + ['A', 'B'], + ['A', 'C'], + ['B', 'B'], + ['B', 'C'], + ['C', 'C'], + ]); + + expect(combineWithRepetitions(['A', 'B', 'C'], 3)).toEqual([ + ['A', 'A', 'A'], + ['A', 'A', 'B'], + ['A', 'A', 'C'], + ['A', 'B', 'B'], + ['A', 'B', 'C'], + ['A', 'C', 'C'], + ['B', 'B', 'B'], + ['B', 'B', 'C'], + ['B', 'C', 'C'], + ['C', 'C', 'C'], + ]); + + const combinationOptions = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']; + const combinationSlotsNumber = 4; + const combinations = combineWithRepetitions(combinationOptions, combinationSlotsNumber); + const n = combinationOptions.length; + const r = combinationSlotsNumber; + const expectedNumberOfCombinations = factorial((r + n) - 1) / (factorial(r) * factorial(n - 1)); + + expect(combinations.length).toBe(expectedNumberOfCombinations); + }); +}); diff --git a/src/algorithms/string/combinations/combineWithRepetitions.js b/src/algorithms/string/combinations/combineWithRepetitions.js new file mode 100644 index 00000000..0d2ef3c1 --- /dev/null +++ b/src/algorithms/string/combinations/combineWithRepetitions.js @@ -0,0 +1,38 @@ +/** + * @param {*[]} combinationOptions + * @param {number} combinationLength + * @return {*[]} + */ + +export default function combineWithRepetitions(combinationOptions, combinationLength) { + // If combination length equal to 0 then return empty combination. + if (combinationLength === 0) { + return [[]]; + } + + // If combination options are empty then return "no-combinations" array. + if (combinationOptions.length === 0) { + return []; + } + + // Init combinations array. + const combos = []; + + // Find all shorter combinations and attach head to each of those. + const headCombo = [combinationOptions[0]]; + const shorterCombos = combineWithRepetitions(combinationOptions, combinationLength - 1); + + for (let combinationIndex = 0; combinationIndex < shorterCombos.length; combinationIndex += 1) { + const combo = headCombo.concat(shorterCombos[combinationIndex]); + combos.push(combo); + } + + // Let's shift head to the right and calculate all the rest combinations. + const combinationsWithoutHead = combineWithRepetitions( + combinationOptions.slice(1), + combinationLength, + ); + + // Join all combinations and return them. + return combos.concat(combinationsWithoutHead); +}