25

I have a problem that is really kind of a general programming question, but my implementation is in Java, so I will provide my examples that way

I have a class like this:

public class Foo {
    LinkedHashMap<String, Vector<String>> dataStructure;

    public Foo(LinkedHashMap<String, Vector<String>> dataStructure) {
        this.dataStructure = dataStructure;
    }

    public String[][] allUniqueCombinations() {
        //this is what I need to do
    }
}

I need to generate a nested array from my LinkedHashMap that represents every unique combination of all values in the LHM. for example, if my LHM looks like this (pseudocode, but I think you can get the idea..):

{"foo" => ["1","2","3"], "bar" => ["3","2"], "baz" => ["5","6","7"]};

then my String[][] should look like this:

{
   {"foo","bar","baz"},
   {"1","3","5"},
   {"1","2","5"},
   {"1","3","6"},
   {"1","2","6"},
   {"1","3","7"},
   {"1","2","7"},
   {"2","3","5"},
   {"2","2","5"},
   {"2","3","6"},
   {"2","2","6"},
   {"2","3","7"},
   {"2","2","7"},
   {"3","3","5"},
   {"3","2","5"},
   {"3","3","6"},
   {"3","2","6"},
   {"3","3","7"},
   {"3","2","7"},
}

I think that's all of them, I made that manually (obviously) so I might have missed a set, but I think this illustrates what I'm trying to do. It doesn't matter what order each set comes in, so long as all unique combinations are present. Also to be clear, you don't know how many elements are in the LHM, nor how many elements are in each subsequent Vector. I have found answers that match the case where you want every unique combination of all elements in a single array, but nothing that fits this exactly.

Update: I changed my types to strings because my real world example is actually strings. I was trying to use integers to make the example more readable, but the answers I've gotten so far do not translate well to strings. So, yes they are numbers, but in my actual case, they will be strings that wouldn't make much sense to anyone but people who use this particular application. so, this is just an abstraction of it.

Chris Drappier
  • 5,280
  • 10
  • 40
  • 64
  • Quick question - why `Vector`? http://stackoverflow.com/questions/1386275/why-java-vector-class-is-considered-obsolete-or-deprecated – josh.trow Mar 06 '12 at 20:55
  • Why are all the uniqueCombinations of length 3? What is the significance of the entry [3,2] in the input? – Adam Mar 06 '12 at 20:58
  • @josh.trow it's a Vector because it's a Vector. I don't have a good explanation for you. I'm not really a big fan of static typing. – Chris Drappier Mar 06 '12 at 21:07
  • @Adam the unique combinations have a length of three because dataStructure.size() == 3 in this scenario. if there were 4 elements in the top level of dataStructure, then each one would be 4.. – Chris Drappier Mar 06 '12 at 21:07
  • What is the significance of the `Integer` key in the LHM? Should the ordering for the combination arrays be based on the order of the keys, or the ordering of the LHM? (i.e. if I added keys 3,2,1 in that order to the LHM, should I use the same ordering or 1,2,3) Suppose I have mappings for keys 1,2,4,6...should the combination array skip 3 and 5 or use some special value? (e.g. 0, -1, etc.) – Kevin K Mar 06 '12 at 21:40
  • Nevermind, it's clearer now with the `String` example – Kevin K Mar 06 '12 at 21:45

12 Answers12

22

Try something like this:

public static void generate(int[][] sets) {
    int solutions = 1;
    for(int i = 0; i < sets.length; solutions *= sets[i].length, i++);
    for(int i = 0; i < solutions; i++) {
        int j = 1;
        for(int[] set : sets) {
            System.out.print(set[(i/j)%set.length] + " ");
            j *= set.length;
        }
        System.out.println();
    }
}

public static void main(String[] args) {
    generate(new int[][]{{1,2,3}, {3,2}, {5,6,7}});
}

which will print:

1 3 5
2 3 5
3 3 5
1 2 5
2 2 5
3 2 5
1 3 6
2 3 6
3 3 6
1 2 6
2 2 6
3 2 6
1 3 7
2 3 7
3 3 7
1 2 7
2 2 7
3 2 7

I've implemented the algorithm above based on (I believe) one of Knuth's TAOCP books (in the comments @chikitin has a more specific reference: it is in PRE FASCICLE 2A section 7.2.1.1 Generating All n-tuple, of The Art Of Computer Programming by Knuth, Addison Wesley).

Note that I've named the arrays set, but they needn't hold unique elements, of course. The time I used it, they did contain unique elements, hence the name.

EDIT

It's pretty much a 1-on-1 translation:

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Vector;

public class Foo {

    private LinkedHashMap<String, Vector<String>> dataStructure;

    public Foo(LinkedHashMap<String, Vector<String>> dataStructure){
        this.dataStructure = dataStructure;
    }

    public String[][] allUniqueCombinations(){
        int n = dataStructure.keySet().size();
        int solutions = 1;

        for(Vector<String> vector : dataStructure.values()) {
            solutions *= vector.size();            
        }

        String[][] allCombinations = new String[solutions + 1][];
        allCombinations[0] = dataStructure.keySet().toArray(new String[n]);

        for(int i = 0; i < solutions; i++) {
            Vector<String> combination = new Vector<String>(n);
            int j = 1;
            for(Vector<String> vec : dataStructure.values()) {
                combination.add(vec.get((i/j)%vec.size()));
                j *= vec.size();
            }
            allCombinations[i + 1] = combination.toArray(new String[n]);
        }

        return allCombinations;
    }

    public static void main(String[] args) {
        LinkedHashMap<String, Vector<String>> data = new LinkedHashMap<String, Vector<String>>();
        data.put("foo", new Vector<String>(Arrays.asList("1", "2", "3")));
        data.put("bar", new Vector<String>(Arrays.asList("3", "2")));
        data.put("baz", new Vector<String>(Arrays.asList("5", "6", "7")));

        Foo foo = new Foo(data);

        for(String[] combination : foo.allUniqueCombinations()) {
            System.out.println(Arrays.toString(combination));            
        }
    }
}

If you run the class above, the following is printed:

[foo, bar, baz]
[1, 3, 5]
[2, 3, 5]
[3, 3, 5]
[1, 2, 5]
[2, 2, 5]
[3, 2, 5]
[1, 3, 6]
[2, 3, 6]
[3, 3, 6]
[1, 2, 6]
[2, 2, 6]
[3, 2, 6]
[1, 3, 7]
[2, 3, 7]
[3, 3, 7]
[1, 2, 7]
[2, 2, 7]
[3, 2, 7]
Bart Kiers
  • 166,582
  • 36
  • 299
  • 288
  • this doesn't translate too well to my actual use-case, I updated the question, if you wanted to take another shot at, I'd appreciate it. thanks! – Chris Drappier Mar 06 '12 at 21:35
  • well, because the value part of the LHM is a Vector, i can't say solutions *= sets[i].length. also, i can't call dataStructure.get(i.toString()) or anything like that because the keys will be "foo","bar", "baz", etc.. – Chris Drappier Mar 06 '12 at 21:44
  • You can iterate over your map, and instead of 'length', you can call 'size. I don't see any problem. – Bart Kiers Mar 06 '12 at 21:50
  • let me play with it some more, I can see where this is going. The logic seems right, it's just going to take some effort to massage the data structure to get it to fit. – Chris Drappier Mar 06 '12 at 22:03
  • @BartKiers that's a very nice algorithm for generating the cross product of a lists of lists, could you provide a more precise reference to where you learned it? if not the exact page in TAOCP, at least an online link where it's explained? – Óscar López Mar 08 '12 at 19:47
  • @ÓscarLópez, I'll try to get a more precise reference. I'll have a look tomorrow evening (I'm in CET). – Bart Kiers Mar 08 '12 at 20:01
  • 2
    How can one explain the `set[(i/j)%set.length` part of the inner for loop and the subsequent `j *= vec.size();`? Or is that a trick to know? – Ketcomp Jul 22 '16 at 22:01
  • 2
    Thanks for the solution. It is in PRE FASCICLE 2A section 7.2.1.1 Generating All n-tuple, of book The art of computer programming by Knuth, Addison Wesley. – chikitin Feb 12 '18 at 04:06
  • @chikitin do you know which algorithm in 7.2.1.1 this answer is based on? I'm having difficulty matching the algorithms described in that section to the one in the answer. This is an old answer but pointing at the specific algorithm that applies here would make answer a great one. As it stands, what happens in the inner loop is hard to understand. – mahonya Jan 10 '19 at 10:46
  • I get this error when number of pairs are equal to 3147776. Python successfully creates cartesian product but the code you provided, gives this error: ```Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.lang.Double.valueOf(Double.java:519)``` Even if I assign heap space to 10 gb, same error persists. It means there is some memory leak in this code. – foobar Apr 14 '20 at 04:30
4

How about generating the product lazily, ie. only create the tuple when you're accessing it?

/**
* A random access view of tuples of a cartesian product of ArrayLists
*
* Orders tuples in the natural order of the cartesian product
*
* @param T the type for both the values and the stored tuples, ie. values of the cartesian factors are singletons
* While the type of input sets is List<T> with elements being treated as singletons
*
*/

abstract public class CartesianProductView<T> extends AbstractList<T> {

private final List<List<T>> factors;
private final int size;

/**
 * @param factors the length of the factors (ie. the elements of the factors argument) should not change,
 *  otherwise get may not return all tuples, or throw exceptions when trying to access the factors outside of range
 */
public CartesianProductView(List<List<T>> factors) {
    this.factors = new ArrayList<>(factors);
    Collections.reverse(this.factors);
    int acc = 1;
    for (Iterator<List<T>> iter = this.factors.iterator(); iter.hasNext(); ) {
        acc *= iter.next().size();
    }
    this.size = acc;
}

@Override
public T get(int index) {
    if (index < 0 || index >= size()) {
        throw new IndexOutOfBoundsException(String.format("index %d > size() %d", index, size()));
    }

    T acc = null;
    for (Iterator<List<T>> iter = factors.iterator(); iter.hasNext();) {
        List<T> set = iter.next();
        acc = makeTupleOrSingleton(set.get(index % set.size()), acc);
        index /= set.size();
    }
    return acc;
}

@Override
public int size() {
    return size;
}

private T makeTupleOrSingleton(T left, T right) {
    if (right == null) {
        return left;
    }
    return makeTuple(left, right);
}

/**
 *
 * @param left      a singleton of a value
 * @param right     a tuple of values taken from the cartesian product factors, with null representing the empty set
 * @return          the sum of left and right, with the value of left being put in front
 */
abstract protected T makeTuple(T left, T right);
}

and use it like this

final List<List<String>> l1 = new ArrayList<List<String>>() {{ add(singletonList("a")); add(singletonList("b")); add(singletonList("c")); }};
final List<List<String>> l2 = new ArrayList<List<String>>() {{ add(singletonList("X")); add(singletonList("Y")); }};
final List<List<String>> l3 = new ArrayList<List<String>>() {{ add(singletonList("1")); add(singletonList("2")); add(singletonList("3")); add(singletonList("4")); }};


List<List<List<String>>> in = new ArrayList<List<List<String>>>() {{ add(l1); add(l2); add(l3); }};

List<List<String>> a = new CartesianProductView<List<String>>(in) {

    @Override
    protected List<String> makeTuple(final List<String> left, final List<String> right) {
        return new ArrayList<String>() {{ add(left.get(0)); addAll(right); }};
    }

};

System.out.println(a);

The result:

[[a, X, 1], [a, X, 2], [a, X, 3], [a, X, 4], [a, Y, 1], [a, Y, 2], [a, Y, 3], [a, Y, 4], [b, X, 1], [b, X, 2], [b, X, 3], [b, X, 4], [b, Y, 1], [b, Y, 2], [b, Y, 3], [b, Y, 4], [c, X, 1], [c, X, 2], [c, X, 3], [c, X, 4], [c, Y, 1], [c, Y, 2], [c, Y, 3], [c, Y, 4]]

As an added bonus, you can use it join strings all with all:

final List<String> l1 = new ArrayList<String>() {{ add("a"); add("b"); add("c"); }};
final List<String> l2 = new ArrayList<String>() {{ add("X"); add("Y"); }};
final List<String> l3 = new ArrayList<String>() {{ add("1"); add("2"); add("3"); add("4"); }};


List<List<String>> in = new ArrayList<List<String>>() {{ add(l1); add(l2); add(l3); }};

List<String> a = new CartesianProductView<String>(in) {

    @Override
    protected String makeTuple(String left, String right) {
        return String.format("%s%s", left, right);
    }

};

System.out.println(a);

The result:

[aX1, aX2, aX3, aX4, aY1, aY2, aY3, aY4, bX1, bX2, bX3, bX4, bY1, bY2, bY3, bY4, cX1, cX2, cX3, cX4, cY1, cY2, cY3, cY4]
Manos Nikolaidis
  • 21,608
  • 12
  • 74
  • 82
Adrian Panasiuk
  • 7,249
  • 5
  • 33
  • 54
4

I know it's long after you needed the answer, but somehow I can't refrain from noticing that one could switch to Groovy, at least for some part of a Java application, and write a wrapper class to match the desired interface. The Groovy code for such permutations is

myListOfLists.combinations()

Ever since I started using Groovy in my Java applications, it's much faster to write them and way more interesting to debug/profile them (ehem...)

Bulba
  • 805
  • 8
  • 10
4

Guava has a utility method which returns a cartesian product of the given list of sets: Sets.cartesianProduct.

Vitalii Fedorenko
  • 110,878
  • 29
  • 149
  • 111
3

Take a look at the following two methods, they do exactly what you asked for. I wrote them to be generic, it doesn't matter how long your lists are or how many keys exist in the map, the combinations generated are correct.

The code below is iterative, based on the algorithm of Python's itertools.product() function for calculating the Cartesian product of a list of lists.

public String[][] allUniqueCombinations() {

    List<String> labels = new ArrayList<String>();
    List<List<String>> lists = new ArrayList<List<String>>();

    for (Map.Entry<String, Vector<String>> entry : dataStructure.entrySet()) {
        labels.add(entry.getKey());
        lists.add(entry.getValue());
    }

    List<List<String>> combinations = product(lists);
    int m = combinations.size() + 1;
    int n = labels.size();
    String[][] answer = new String[m][n];

    for (int i = 0; i < n; i++)
        answer[0][i] = labels.get(i);
    for (int i = 1; i < m; i++)
        for (int j = 0; j < n; j++)
            answer[i][j] = combinations.get(i-1).get(j);

    return answer;

}

private List<List<String>> product(List<List<String>> lists) {

    List<List<String>> result = new ArrayList<List<String>>();
    result.add(new ArrayList<String>());

    for (List<String> e : lists) {
        List<List<String>> tmp1 = new ArrayList<List<String>>();
        for (List<String> x : result) {
            for (String y : e) {
                List<String> tmp2 = new ArrayList<String>(x);
                tmp2.add(y);
                tmp1.add(tmp2);
            }
        }
        result = tmp1;
    }

    return result;

}

I tested them with the example in the question:

LinkedHashMap<String, Vector<String>> sample = 
    new LinkedHashMap<String, Vector<String>>();

Vector<String> v1 = new Vector<String>();
v1.add("1"); v1.add("2"); v1.add("3");
Vector<String> v2 = new Vector<String>();
v2.add("3"); v2.add("2");
Vector<String> v3 = new Vector<String>();
v3.add("5"); v3.add("6"); v3.add("7");

sample.put("foo", v1);
sample.put("bar", v2);
sample.put("baz", v3);

Foo foo = new Foo(sample);
String[][] ans = foo.allUniqueCombinations();
for (String[] row : ans)
    System.out.println(Arrays.toString(row));

The answer that gets printed is the expected (although the combinations appear in a different order):

[foo, bar, baz]
[1, 3, 5]
[1, 3, 6]
[1, 3, 7]
[1, 2, 5]
[1, 2, 6]
[1, 2, 7]
[2, 3, 5]
[2, 3, 6]
[2, 3, 7]
[2, 2, 5]
[2, 2, 6]
[2, 2, 7]
[3, 3, 5]
[3, 3, 6]
[3, 3, 7]
[3, 2, 5]
[3, 2, 6]
[3, 2, 7]
Óscar López
  • 232,561
  • 37
  • 312
  • 386
  • Oscar, it was hard to decide whether to accept yours or Bart's answer, I had to go with Bart's since he answered first, but yours is very good as well, thank you! – Chris Drappier Mar 07 '12 at 15:37
2

You could also solve this really easily with Functional Java's List monad:

import fj.data.List;

public class cartesian {
 public static void main(String[] args) {
  List<String>  foo = List.list("a", "b");
  List<Integer> bar = List.list(1,2,3);
  List<Float>   baz = List.list(0.2f,0.4f,0.3f);

  List<P3<String, Integer, Float>> 
  // the Cartesian product is assembled into a list of P3's
  result = foo.bind(bar, baz, P.<String, Integer, Float>p3()); 

  String out = Show.listShow(Show.p3Show(Show.stringShow, Show.intShow, Show.floatShow))
               .showS(result);
  System.out.println(out);
 }
}
Shlomi
  • 4,708
  • 1
  • 23
  • 32
2

Thanks to Vitalii Fedorenko, I could achive the same for list by using

Lists.cartesianProduct(..) 

from https://guava.dev/releases/23.5-jre/api/docs/com/google/common/collect/Lists.html#cartesianProduct-java.util.List...-

I think, if it is required for a production code then it is better to rely on tried and tested library like Guava rather than building our own.

Apostolos
  • 10,033
  • 5
  • 24
  • 39
shashi ranjan
  • 380
  • 4
  • 8
1

I'm late to the party but I followed Shiomi's link and translated the functions into Java. The result is an easy to follow and understand algorithm (I may be a little slow since I had a hard time understanding Bart Kiers' solution).

Here it is (the key is an int, replacing to String should be straightforward):

Usage

    public void testProduct(){
        Map<Integer, List<String>> data =   new LinkedHashMap<Integer, List<String>>(){{                
            put(0, new ArrayList<String>(){{
                add("John"); add("Sarah");                      
            }});                
            put(1, new ArrayList<String>(){{
                add("Red"); add("Green"); add("Blue"); add("Orange");
            }});
            put(2, new ArrayList<String>(){{
                add("Apple"); add("Tomatoe"); add("Bananna");                   
            }});
    }};

        List<String[]> product =  GetCrossProduct(data);
        for(String[] o : product)
            System.out.println(Arrays.toString(o));

    }

Result

[John, Red, Apple]
[John, Red, Tomatoe]
[John, Red, Bananna]
[John, Green, Apple]
[John, Green, Tomatoe]
[John, Green, Bananna]
[John, Blue, Apple]
[John, Blue, Tomatoe]
[John, Blue, Bananna]
[John, Orange, Apple]
[John, Orange, Tomatoe]
[John, Orange, Bananna]
[Sarah, Red, Apple]
[Sarah, Red, Tomatoe]
[Sarah, Red, Bananna]
[Sarah, Green, Apple]
[Sarah, Green, Tomatoe]
[Sarah, Green, Bananna]
[Sarah, Blue, Apple]
[Sarah, Blue, Tomatoe]
[Sarah, Blue, Bananna]
[Sarah, Orange, Apple]
[Sarah, Orange, Tomatoe]
[Sarah, Orange, Bananna]

Cartesian Product Functions

    public static List<String[]> GetCrossProduct(Map<Integer, List<String>> lists)
    {
        List<String[]> results = new ArrayList<String[]>();
        GetCrossProduct(results, lists, 0, new String[(lists.size())]);
        return results;
    }

    private void GetCrossProduct(List<String[]> results, Map<Integer, List<String>> lists, int depth, String[] current)
    {
        for (int i = 0; i < lists.get(depth).size(); i++)
        {
            current[depth] = lists.get(depth).get(i);            
            if (depth < lists.keySet().size() - 1)
                GetCrossProduct(results, lists, depth + 1, current);
            else{
                results.add(Arrays.copyOf(current,current.length));                
            }
        }
    }       
Ulises
  • 13,229
  • 5
  • 34
  • 50
1

Recursive solution:

public <T> List<List<T>> cartesianProduct(int i, List<T>... a) {
    if(i == a.length ) {
        List<List<T>> result = new ArrayList<>();
        result.add(new ArrayList());
        return result;
    }
    List<List<T>> next = cartesianProduct(i+1, a);
    List<List<T>> result = new ArrayList<>();
    for(int j=0; j < a[i].size(); j++) {
        for(int k=0; k < next.size(); k++) {
            List<T> concat = new ArrayList();
            concat.add(a[i].get(j));
            concat.addAll(next.get(k));
            result.add(concat);
        }
    }
    return result;
}
ankushbbbr
  • 867
  • 1
  • 6
  • 15
1

Here is a link, its c#, but i am sure you could work with that!

Shlomi
  • 4,708
  • 1
  • 23
  • 32
1

A LinkedHashMap of Vectors of Strings is ... - troublesome. I had to spend much time in converting a solution to use it, but in the end, I don't produce an ArrayOfArrays, but a List of List and keep the last step to the reader.

import java.util.*;
/**
    CartesianProductLHM   
*/
public class CartesianProductLHM
{
    LinkedHashMap <String, Vector<String>> dataStructure;

    public CartesianProductLHM (final String[] data) {
        dataStructure = new LinkedHashMap <String, Vector<String>> ();
        for (String str : data)
        {
            String [] kv = str.split (":");
            String [] values = kv[1].split (","); 
            Vector <String> v = new Vector <String> ();
            for (String s: values) {
                v.add (s);
            //  System.out.print (s); 
            }
            // System.out.println ("\n---");
            dataStructure.put (kv[0], v);
        }
        // System.out.println ("    --- --- ---");
    }

    List <String> getCombiFor (final int i, final List <List <String>> livs) 
    {
        List <String> ls = new ArrayList <String> ();
        if (! livs.isEmpty ()) {
            List <String> vs = livs.remove (0); 
            int idx = i % vs.size (); 
            String elem = vs.get (idx);
            ls.add (elem);
            ls.addAll (getCombiFor (i / vs.size (), livs));
        }
        return ls;
    }

    List <String> getOuterCombiFor (int i, List <List <String>> coll) 
    {
        List <String> ls = new ArrayList <String> ();
        if (! coll.isEmpty ()) {
            List <List <String>> livs = new ArrayList <List <String>> ();
            for (List<String> li : coll) 
            {
                livs.add (li);
            }   
            ls.addAll (getCombiFor (i, livs));
        } 
        return ls;  
    }   

    public List <List <String>> allUniqueCombinations () {
        Collection <Vector <String>> li = dataStructure.values (); 
        List <List <String>> lls = new ArrayList <List <String>> ();
        for (Vector <String> vs : li) {
            List <String> l = new ArrayList <String> ();
            for (String s : vs) {
                l.add (s);
            }
            lls.add (l);
        }
        int count = 1;
        for (Vector <String> vec: li) {
            count *= vec.size ();
        }       
        List <List <String>> result = new ArrayList <List <String>> ();
        for (int i = 0; i < count; ++i) 
        {
            List <String> l = getOuterCombiFor (i, lls);
            result.add (l);
        }
        return result;  
    }

    public static void main (String args[])
    {
        String[] arr = {"foo:1,2,3", "bar:a,b", "baz:5,6,7"};
        CartesianProductLHM cp = new CartesianProductLHM (arr);
        List <List <String>> lls = cp.allUniqueCombinations ();
        for (List <String> ls : lls) 
        {
            for (String s : ls)
                System.out.print (s + "\t");
            System.out.println ();
        }
    }
}

Well - yes, and I parse some test data.

The main idea is, that you have some Lists (abc, 12, defg, ...) and you have 3 possibilities at pos 0, 2 at pos 1, 4 at pos 3 and so on, so 3*2*4 combinations so far.

From the numbers 0 to 23 you can pick from each sublist with modulo, and hand the rest of the number divided by the size of the previous list and the remaining lists recursively to the procedure, until there is no list left.

user unknown
  • 35,537
  • 11
  • 75
  • 121
0

You can generate the combinations recursively.

public class Main {
    public static void main(String[] args) {
        int[][] arr = new int[][] { { 1, 2, 3 }, { 3, 2 }, { 5, 6, 7 } };
        cartesianProduct(arr, 0, new int[arr.length]);
    }

    private static void cartesianProduct(int[][] arr, int level, int[] cp) {
        if (level == arr.length) {
            for (int x : cp)
                System.out.print(x + " ");
            System.out.println();
            return;
        }

        for (int i = 0; i < arr[level].length; i++) {
            cp[level] = arr[level][i];
            cartesianProduct(arr, level + 1, cp);
        }
    }
}

Output :

1 3 5 
1 3 6 
1 3 7 
1 2 5 
1 2 6 
1 2 7 
2 3 5 
2 3 6 
2 3 7 
2 2 5 
2 2 6 
2 2 7 
3 3 5 
3 3 6 
3 3 7 
3 2 5 
3 2 6 
3 2 7 
sujithvm
  • 2,351
  • 3
  • 15
  • 16