HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Calculate all possible combinations of an array of arrays or strings

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
arrayscombinationsarrayallpossiblecalculatestrings

Problem

I'm using code adapted from this Stack Overflow question to calculate all possible combinations from an array, which may contains strings.

For example:

[ "x", "y", "z" ] => "x y z"
[ [ "a", "b"] , "c", "d" ] => [ "a c d", "b c d" ]
[ [ "a", "b"] , ["c" "d"], "e"] => [ "a c e", "b c e", "a d e", "b d e" ]


However as the items could be strings or array I need to check for all possible combinations of array/string pairs when building the results.

This has ended in rather messy code, and I'd appreciate any hints on how to tidy it up. I've attempted to refactor it, but I guess I'm tired and missing something!

`function allPossibleCases(arr) {
if (arr.length === 0) {
return [];
}
else if (arr.length === 1){
return arr[0];
}
else {
var result = [];
var allCasesOfRest = allPossibleCases(arr.slice(1)); // recur with the rest of array

if(typeof allCasesOfRest === 'string') {
if(typeof arr[0] === 'string') {
result.push(arr[0] + " " + allCasesOfRest);
} else {
for (var i = 0; i

Solution

-
You don't need the else if/else blocks at the top; it can just be if blocks, since they return and thus exit the function anyway.

if (arr.length === 0) {
  return []; // stop here
} 

if (arr.length === 1){
  return arr[0]; // or here
}

// or continue with the rest of the function


-
As you yourself noted, normalize the input, so you don't need to be type-checking inside the loop

But I'd advice you ignore that you're dealing with strings specifically, and just worry about "combinations of stuff", and try for a generic solution.

Still, the manual array-building makes your code cryptic. I'd suggest a more functional approach, using map and reduce.

Not saying it's easier to follow at first glance, but there's less code, and it's generic:

// this produces a 2d array of combinations
function combinations(array) {
  if(!array.length) {
    return [];
  }

  // wrap non-array values
  // e.g. ['x',['y','z']] becomes [['x'],['y','z']]
  array = array.map(function (item) {
    return item instanceof Array ? item : [item];
  });

  // internal recursive function
  function combine(list) {
    var prefixes, combinations;

    if(list.length === 1) {
      return list[0];
    }

    prefixes = list[0];
    combinations = combine(list.slice(1)); // recurse

    // produce a flat list of each of the current
    // set of values prepended to each combination
    // of the remaining sets.
    return prefixes.reduce(function (memo, prefix) {
      return memo.concat(combinations.map(function (combination) {
        return [prefix].concat(combination);
      }));
    }, []);
  }

  return combine(array);
}


To convert the output of the above function to strings, just add another map call:

function allPossibleCases(arr) {
  return combinations(arr).map(function (combination) {
    return combination.join(" ");
  });
}


You get this:

[ 'x', 'y', 'z' ]                   => [ 'x y z' ]
[ [ 'a', 'b' ], 'c', 'd' ]          => [ 'a c d', 'b c d' ]
[ [ 'a', 'b' ], [ 'c', 'd' ], 'e' ] => [ 'a c e', 'a d e', 'b c e', 'b d e' ]


Note that the order of the last one is different from yours, though it contains the same combinations.

Code Snippets

if (arr.length === 0) {
  return []; // stop here
} 

if (arr.length === 1){
  return arr[0]; // or here
}

// or continue with the rest of the function
// this produces a 2d array of combinations
function combinations(array) {
  if(!array.length) {
    return [];
  }

  // wrap non-array values
  // e.g. ['x',['y','z']] becomes [['x'],['y','z']]
  array = array.map(function (item) {
    return item instanceof Array ? item : [item];
  });

  // internal recursive function
  function combine(list) {
    var prefixes, combinations;

    if(list.length === 1) {
      return list[0];
    }

    prefixes = list[0];
    combinations = combine(list.slice(1)); // recurse

    // produce a flat list of each of the current
    // set of values prepended to each combination
    // of the remaining sets.
    return prefixes.reduce(function (memo, prefix) {
      return memo.concat(combinations.map(function (combination) {
        return [prefix].concat(combination);
      }));
    }, []);
  }

  return combine(array);
}
function allPossibleCases(arr) {
  return combinations(arr).map(function (combination) {
    return combination.join(" ");
  });
}
[ 'x', 'y', 'z' ]                   => [ 'x y z' ]
[ [ 'a', 'b' ], 'c', 'd' ]          => [ 'a c d', 'b c d' ]
[ [ 'a', 'b' ], [ 'c', 'd' ], 'e' ] => [ 'a c e', 'a d e', 'b c e', 'b d e' ]

Context

StackExchange Code Review Q#52119, answer score: 7

Revisions (0)

No revisions yet.