6

I'm trying to get a result looking something like this: Miniors | Boys | 54kg - 62kg where every value delimited by a pipe | comes from an array containing a certain "type of restriction". For example: ageGroups, genders, weightClasses (as seen above).

The way I'm able to get this result right now is if I hard code the nested forEach-loops (using underscorejs), but this means I have to now how many arrays I have to loop over to get wanted result. This works "fine":

var categories = [];
_.each(ageGroups, function(ageGroup) {
   _.each(gender, function(gender) {
     _.each(weightClasses, function(weightClass) {
       categories.push(ageGroup.name + ' | ' + gender.name + ' | ' + weightClass.name);
      });
   });
});

The output is an array (categories) with all the possible combinations of the restriction arrays.

Now, my problem is that I need a way to do the same with an unknown number of restriction arrays. My guess for a proper solution is recursion, BUT I haven't been able to produce anything that actually works since I'm not able to wrap my head around recursion just yet :)

A fiddle prepared with some test data can be found here: jsFiddle. The fiddle uses angular for some simple databinding and debugging the result output and underscorejs for handling the arrays.

David G
  • 94,763
  • 41
  • 167
  • 253
aup
  • 800
  • 7
  • 19
  • Try not to use side-effects (i.e. `push`ing to a global `categories` array), but instead `return` from each step and use `map` (and `flatten`) – Bergi Nov 02 '14 at 20:32
  • Hmm.. Okey. Flatten wont be of any use here since I need a combination of the different values from the different arrays. But map might be something.. Even though I can't see how it would solve the problem I'm facing with a dynamic amount of arrays. You care to elaborate? – aup Nov 02 '14 at 20:43
  • Try to use `map` (even in a non-generic way) and you will see what you need the `flatten` for. Then, make a function that takes the `groups`, the current group's index (the "nesting level") and the current names of the visited groups. The base case (when the level of nesting has reached the length of the `groups`) would then return those current names (joined by ` | `), the recursive case - you'll figure out. – Bergi Nov 02 '14 at 20:55

2 Answers2

4

I recently wrote a recursive function to create all combinations of arrays. You would have to translate your data into an array of arrays that my function uses, but that shouldn't be difficult.

Anyway, here's the code with a runnable example:

var v = [['Miniors','Kadettes','Juniors', 'Seniors'], ['Boys','Girls','Men','Women'],['54kg - 62kg','64kg - 70kg','71kg - 78kg','79kg - 84kg']];
var combos = createCombinations(v);
for(var i = 0; i < combos.length; i++) {
  document.getElementsByTagName("body")[0].innerHTML += combos[i] + "<br/>";
}

function createCombinations(fields, currentCombinations) {
  //prevent side-effects
  var tempFields = fields.slice();

  //recursively build a list combinations
  var delimiter = ' | ';
  if (!tempFields || tempFields.length == 0) {
    return currentCombinations;
  }
  else {
    var combinations = [];
    var field = tempFields.pop();

    for (var valueIndex = 0; valueIndex < field.length; valueIndex++) {
      var valueName = field[valueIndex];

      if (!currentCombinations || currentCombinations.length == 0) {
        var combinationName = valueName;
        combinations.push(combinationName);
      }
      else {
        for (var combinationIndex = 0; combinationIndex < currentCombinations.length; combinationIndex++) {
          var currentCombination = currentCombinations[combinationIndex];
          var combinationName = valueName + delimiter + currentCombination;
          combinations.push(combinationName);
        }
      }
    }
    return createCombinations(tempFields, combinations);
  }
}
Dave
  • 10,748
  • 3
  • 43
  • 54
4
function iterate(lists, fn)
{
  var values = [];
  function process(listIndex)
  {
    var list = lists[listIndex];

    // no list? create the value
    if (!list)
    {
      fn.apply(null, values);
      return;
    }

    for (var i = 0; i < list.length; i++)
    {
      values[listIndex] = list[i];
      process(listIndex+1);
    }
  }

  process(0);
}

here is a working example based on the data mentioned in your question: http://jsbin.com/boqucu/2/edit

lloiser
  • 1,161
  • 9
  • 11
  • This works perfectly "out-of-the-box" for my specific case! Why are people down voteing this? I don't how ever see how I can use this dynamically? The function putting together the final name takes a specifik amount of parameters. Thanks! – aup Nov 02 '14 at 21:30
  • at least someone gives me some information. I will update my answer. Thanks. – lloiser Nov 02 '14 at 21:37
  • Awesome stuff! Just changed the fn-call to just pass in the values-array instead of fh.apply(). I think that did it! Thanks again! – aup Nov 02 '14 at 21:56