Search code examples
dartcombinations

dart generating all possible combinations list of lists


I have a list of lists, similar to this:

a = [ [1,2,3], [4,5,6], [7,8,9,10]]

I'd like to create all possible combinations, like this:

[(1, 4, 7), (1, 4, 8), (1, 4, 9), (1, 4, 10), (1, 5, 7), (1, 5, 8), (1, 5, 9), (1, 5, 10), (1, 6, 7), (1, 6, 8), (1, 6, 9), (1, 6, 10), (2, 4, 7), (2, 4, 8), (2, 4, 9), (2, 4, 10), (2, 5, 7), (2, 5, 8), (2, 5, 9), (2, 5, 10), (2, 6, 7), (2, 6, 8), (2, 6, 9), (2, 6, 10), (3, 4, 7), (3, 4, 8), (3, 4, 9), (3, 4, 10), (3, 5, 7), (3, 5, 8), (3, 5, 9), (3, 5, 10), (3, 6, 7), (3, 6, 8), (3, 6, 9), (3, 6, 10)]

For python, there's a library that does exactly this.
Is there a similar solution for Dart?
If not, I'd appreciate a simple code that accomplish that


Solution

  • Could not find a package which does exactly what you want, but I guess your can do something like this if you want to introduce your own method:

    void main() {
      print(combinations([
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9, 10]
      ]));
      // ([1, 4, 7], [1, 4, 8], [1, 4, 9], [1, 4, 10], ..., [3, 6, 9], [3, 6, 10])
    }
    
    Iterable<List<T>> combinations<T>(
        List<List<T>> lists, [
          int index = 0,
          List<T>? prefix,
        ]) sync* {
      prefix ??= <T>[];
    
      if (lists.length == index) {
        yield prefix.toList();
      } else {
        for (final value in lists[index]) {
          yield* combinations(lists, index + 1, prefix..add(value));
          prefix.removeLast();
        }
      }
    }
    

    More efficient solution but also more risky to use since it does require the user of combinations to take care when consuming the output and make sure not to keep any instances of the inner Iterable:

    void main() {
      print(combinations([
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9, 10]
      ]).map((e) => e.toList()));
      // ([1, 4, 7], [1, 4, 8], [1, 4, 9], [1, 4, 10], ..., [3, 6, 9], [3, 6, 10])
    }
    
    Iterable<Iterable<T>> combinations<T>(
        List<List<T>> lists, [
          int index = 0,
          List<T>? prefix,
        ]) sync* {
      prefix ??= <T>[];
    
      if (lists.length == index) {
        yield prefix;
      } else {
        for (final value in lists[index]) {
          yield* combinations(lists, index + 1, prefix..add(value));
          prefix.removeLast();
        }
      }
    }
    

    The problem with this solution is the risk of misuse as the following example:

      final listOfCombinations = combinations([
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9, 10]
      ]).toList();
      print(listOfCombinations);
      // [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []]
    

    Which should instead be:

      final listOfCombinations = combinations([
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9, 10]
      ]).map((e) => e.toList()).toList();
      print(listOfCombinations);
      // [[1, 4, 7], [1, 4, 8], [1, 4, 9], [1, 4, 10], [1, 5, 7], [1, 5, 8], [1, 5, 9], [1, 5, 10], [1, 6, 7], [1, 6, 8], [1, 6, 9], [1, 6, 10], [2, 4, 7], [2, 4, 8], [2, 4, 9], [2, 4, 10], [2, 5, 7], [2, 5, 8], [2, 5, 9], [2, 5, 10], [2, 6, 7], [2, 6, 8], [2, 6, 9], [2, 6, 10], [3, 4, 7], [3, 4, 8], [3, 4, 9], [3, 4, 10], [3, 5, 7], [3, 5, 8], [3, 5, 9], [3, 5, 10], [3, 6, 7], [3, 6, 8], [3, 6, 9], [3, 6, 10]]
    

    So, use the first suggested solution if you don't want the risk of this kind of issues. :)