I have a 2D array with 2 rows and n columns.
I am trying to sort the array using the built in Arrays.sort method, but I am struggling with the comparator. I want to sort the 2D array by the first row, so that the 2nd row elements will remain with the original elements they were aligned with ie. keep the columns together.
For example original array:
int[] unsortedArray = new int[][]{
{ 6, 3, 1, 2, 3, 0},
{ 2, 1, 6, 6, 2, 4},
sorted array:
int[] unsortedArray = new int[][]{
{ 0, 1, 2, 3, 3, 6},
{ 4, 6, 6, 1, 2, 2},
What is the best way to go about doing this?
Cheers.
Here is an alternative way of keeping your columns aligned. This uses a simple class to hold both column elements.
class TwoInts {
public final int aElement;
public final int bElement;
public TwoInts(int a_element, int b_element) {
aElement = a_element;
bElement = b_element;
}
}
One by one, each column (from sub-array one and two) are placed in this object, and immediately put into a Map<Integer,List<TwoInts>>. The key is the element from intArrayArray[0], and the value is a list of TwoInts, because there may be (and in your example code, are) duplicate values in intArrayArray[0].
You then iterate through the map's key-set, and replace them into the array.
Full code:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
<P>{#code java SortOneArrayKeepSecondArrayElementsAligned}</P>
**/
public class SortOneArrayKeepSecondArrayElementsAligned {
public static final void main(String[] ignored) {
int[][] intArrayArray = new int[][]{
{ 6, 3, 1, 2, 3, 0},
{ 2, 1, 6, 6, 2, 4}};
output2DArray("Unsorted", intArrayArray);
Map<Integer,List<TwoInts>> twoIntMap = new TreeMap<Integer,List<TwoInts>>();
for(int i = 0; i < intArrayArray[0].length; i++) {
int intIn0 = intArrayArray[0][i];
if(!twoIntMap.containsKey(intIn0)) {
List<TwoInts> twoIntList = new ArrayList<TwoInts>(intArrayArray.length);
twoIntList.add(new TwoInts(intArrayArray[0][i], intArrayArray[1][i]));
twoIntMap.put(intIn0, twoIntList);
} else {
twoIntMap.get(intIn0).add(new TwoInts(intArrayArray[0][i], intArrayArray[1][i]));
}
}
int idx = 0;
Iterator<Integer> itr2i = twoIntMap.keySet().iterator();
while(itr2i.hasNext()) {
List<TwoInts> twoIntList = twoIntMap.get(itr2i.next());
for(TwoInts twoi : twoIntList) {
intArrayArray[0][idx] = twoi.aElement;
intArrayArray[1][idx++] = twoi.bElement;
}
}
output2DArray("Sorted", intArrayArray);
}
private static final void output2DArray(String description, int[][] twoD_array) {
System.out.println(description + ":");
System.out.println("0: " + Arrays.toString(twoD_array[0]));
System.out.println("1: " + Arrays.toString(twoD_array[1]));
System.out.println();
}
}
class TwoInts {
public final int aElement;
public final int bElement;
public TwoInts(int a_element, int b_element) {
aElement = a_element;
bElement = b_element;
}
}
Output:
[C:\java_code\]java SortOneArrayKeepSecondArrayElementsAligned
Unsorted:
0: [6, 3, 1, 2, 3, 0]
1: [2, 1, 6, 6, 2, 4]
Sorted:
0: [0, 1, 2, 3, 3, 6]
1: [4, 6, 6, 1, 2, 2]
Try the following:
Disclaimer: Not tested :)
myarray = new int[10][2];
// populate the array here
Arrays.sort(myarray, new Comparator<int[]>() {
public int compare(int[] a, int[] b) {
if (a[0] > b[0])
return 1;
else if (a[0] < b[0])
return -1;
else {
return 0;
}
}
};
You will have to write a custom sort. Look into Comparable and Comparator.
Something along the lines of:
public class MyComparator implements Comparator<T[]> {
int columnToSortOn;
public MyComparator(int columnToSortOn) {
this.columnToSortOn = columnToSortOn;
}
#Override
public int compare(T[] array1, T[] array2) {
return array1[columnToSortOn].compareTo(array2[columnToSortOn]);
}
}
Related
I am solving this kata on Codewars: https://www.codewars.com/kata/635fc0497dadea0030cb7936
this is my code:
public static void main(String[] args) {
int[][] ms;
ms = new int[][] {{1, 2, 3, 4},
{3, 1, 4, 2},
{4, 3, 2, 1},
{2, 4, 1, 3}};
System.out.println(count_different_matrices(ms));
}
private static final Set<int[]> registeredM = new HashSet<>();
static public int count_different_matrices(int[][] matrices) {
Arrays.stream(matrices).forEach(m -> {
if(unwrapPossibleMatrices(m).stream().noneMatch(registeredM::contains)) {
registeredM.add(m);
}});
registeredM.forEach(e -> System.out.println(Arrays.toString(e)));
return registeredM.size();
}
static private List<int[]> unwrapPossibleMatrices(int[] m) {
return Arrays.asList(new int[][]{
m,
{m[2], m[0], m[3], m[1]},
{m[3], m[2], m[1], m[0]},
{m[1], m[3], m[0], m[2]}
});
}
Output received in the console:
[1, 2, 3, 4]
[2, 4, 1, 3]
[4, 3, 2, 1]
[3, 1, 4, 2]
4
I expected output of only [1, 2, 3, 4], My train of thought was that contains() should invoke a.equals(b) where a and b in this example is of type int[] and when they will be compared by equals - it will check if length and elements in the arrays match. Instead what happened (I think) was that address of the object was checked - thus, giving different results for arrays with the same elements. My question is the following: how to modify this code to check actual elements passed in arrays?
Ok, I've changed my solution as #Pshemo pointed out (thank you :)
public static void main(String[] args) {
int[][] ms;
ms = new int[][] {{1, 2, 3, 4},
{3, 1, 4, 2},
{4, 3, 2, 1},
{2, 4, 1, 3}};
System.out.println(count_different_matrices(ms));
}
private static final Set<Row> registeredM = new HashSet<>();
static public int count_different_matrices(int[][] matrices) {
registeredM.clear();
Arrays.stream(matrices).forEach(m -> {
if(unwrapPossibleMatrices(m).stream().noneMatch(registeredM::contains)) {
registeredM.add(new Row(m));
}});
registeredM.forEach(e -> System.out.println(Arrays.toString(e.row())));
return registeredM.size();
}
private static List<Row> unwrapPossibleMatrices(int[] m) {
return Arrays.asList(
new Row(m),
new Row(new int[]{m[2], m[0], m[3], m[1]}),
new Row(new int[]{m[3], m[2], m[1], m[0]}),
new Row(new int[]{m[1], m[3], m[0], m[2]})
);
}
record Row(int[] row) {
#Override
public int hashCode() {
return Arrays.hashCode(row);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Row row1 = (Row) o;
return Arrays.equals(row, row1.row);
}
}
So what I want is this
int[][] arr=new int[2][8];
input:
1 1 3 1 5 3 7 1
5 2 4 8 3 7 5 2
output:
1 1 5 3 1 7 3 1
2 2 3 4 5 5 7 8
you can see that it is sorted by the second row in ascending order and the first row just follows,
how can I do this? help, please.
I tried doing below
Arrays.sort(arr[1]);
but I don't think it is working. It does sort the second row in ascending order but the first row is not matching the initial pair with the second row
Try this.
public static void main(String[] args) {
int[][] array = {
{1, 1, 3, 1, 5, 3, 7, 1},
{5, 2, 4, 8, 3, 7, 5, 2}
};
List<int[]> list = new AbstractList<int[]>() {
#Override
public int[] get(int index) {
return new int[] {array[1][index], array[0][index]};
}
#Override
public int[] set(int index, int[] value) {
int[] old = get(index);
array[1][index] = value[0];
array[0][index] = value[1];
return old;
}
#Override
public int size() {
return array[0].length;
}
};
Collections.sort(list, Arrays::compare);
for (int[] row : array)
System.out.println(Arrays.toString(row));
}
output:
[1, 1, 5, 3, 1, 7, 3, 1]
[2, 2, 3, 4, 5, 5, 7, 8]
Or
public static void main(String[] args) {
int[][] array = {
{1, 1, 3, 1, 5, 3, 7, 1},
{5, 2, 4, 8, 3, 7, 5, 2}
};
int[] sortedIndexes = IntStream.range(0, array[0].length)
.boxed()
.sorted(Comparator.comparing((Integer i) -> array[1][i])
.thenComparing(i -> array[0][i]))
.mapToInt(Integer::intValue)
.toArray();
int[][] output = IntStream.range(0, array.length)
.mapToObj(r -> IntStream.range(0, array[r].length)
.map(i -> array[r][sortedIndexes[i]])
.toArray())
.toArray(int[][]::new);
for (int[] r : output)
System.out.println(Arrays.toString(r));
}
It may be implemented using helper method(s) to transpose the input array, then transposed array may be sorted by column, and transposed again to restore the original rows/cols:
// create new array to store transposed
public static int[][] transpose(int[][] src) {
return transpose(src, new int[src[0].length][src.length]);
}
// use existing array to store the transposed
public static int[][] transpose(int[][] src, int[][] dst) {
for (int i = 0, n = src.length; i < n; i++) {
for (int j = 0, m = src[i].length; j < m; j++) {
dst[j][i] = src[i][j];
}
}
return dst;
}
Method sortByColumn (reusing the input array):
public static void sortByColumn(int[][] arr, Comparator<int[]> comparator) {
int[][] toSort = transpose(arr);
Arrays.sort(toSort, comparator);
transpose(toSort, arr);
}
Test:
int[][] arr = {
{7, 1, 3, 1, 5, 3, 1, 4, 4},
{5, 2, 4, 8, 3, 7, 5, 2, 5}
};
sortByColumn(arr, Comparator.comparingInt(col -> col[1]));
for (int[] row : arr) {
System.out.println(Arrays.toString(row));
}
Output:
in the first row values appear in the insertion order after sorting by the second element in each column.
[1, 4, 5, 3, 7, 1, 4, 3, 1]
[2, 2, 3, 4, 5, 5, 5, 7, 8]
Square arrays (width == height) may be transposed more efficiently without creating additional array:
public static int[][] transposeSquare(int[][] arr) {
for (int i = 0, n = arr.length; i < n; i++) {
// select the elements only above the main diagonal
for (int j = i + 1, m = arr[i].length; j < m; j++) {
int tmp = arr[i][j];
arr[i][j] = arr[j][i];
arr[j][i] = tmp;
}
}
return arr;
}
I need to find non similar rows in matrix and return set of such rows.
A rows is said to be similar if the sets of numbers occurring in these rows coincide.
Example:
origin:
1 2 2 4 4
4 2 1 4
3 2 4 1 5 8
expected result:
1 2 2 4 4
3 2 4 1 5 8
My ideas:
Clean duplicates from each row via convert two dimensional array to List>
Create new set of int[] and add row then if row was added its means that row is non similar.then record number of row. return created new set of rows of origin matrix.
I know that I can check if element was added via Boolean return value of Add method of Set.
But there is problem by forEach, that don't provide index. And I can't use expressions inside forEach. What should I do?
My code:
class NonSimilar {
private int[][] matrix;
private List<Set<Integer>> rows = new ArrayList<>();
public NonSimilar (int[][] matrix) {
this.matrix = matrix;
for (int i = 0; i < matrix.length; i++) {
rows.add(Arrays.stream(matrix[i]).boxed().collect(Collectors.toSet()));
}
}
public Set<int[]> getNonSimilarRows() {
Set<Set<Integer>> nonSimularRows = new HashSet<>();
rows.forEach(item -> nonSimularRows.add(item));
// Now I have to check successfully added rows numbers and construct new Set from Origin matrix
return new HashSet<int[]>();
}
}
Ok. I replaced forEach with for iteration and now all works correctly.
public Set<int[]> getNonSimilarRows() {
Set<Set<Integer>> nonSimularRows = new HashSet<>();
//rows.forEach(item -> nonSimularRows.add(item));
int index = -1;
ArrayList<Integer> indexes = new ArrayList<>();
for (Set<Integer> item : rows) {
index++;
if (nonSimularRows.add(item)) {
indexes.add(index);
}
}
HashSet<int[]> newSet = new HashSet<int[]>();
for (Integer item : indexes) {
newSet.add(matrix[item]);
}
return newSet;
}
Anyway code looks very ugly and I want to get advice how I can refactor code with modern approaches like forEach and Stream API.
You only need 2 lines of code to remove all "similar" rows:
Set<Set<Integer>> sets = new HashSet<>();
List<int[]> nonSimilar = Arrays.stream(matrix)
.filter(row -> sets.add(Arrays.stream(row).boxed().collect(Collectors.toSet())))
.collect(Collectors.toList());
The add() method of Set returns true if the set was changed - ie if the element being added is not already in the set, so we can use that as a filter.
List is chosen as the output of the stream to preserve order (a requirement that seems to be implied by the example data).
I leave it to the reader to convert List<int[]> to whatever output is required, because that's unimportant to the question/answer.
Some test code:
int[][] matrix = {{1, 2, 2, 4, 4},{4, 2, 1, 4}, {3, 2, 4, 1, 5, 8}};
Set<Set<Integer>> sets = new HashSet<>();
List<int[]> nonSimilar = Arrays.stream(matrix)
.filter(row -> sets.add(Arrays.stream(row).boxed().collect(Collectors.toSet())))
.collect(Collectors.toList());
nonSimilar.stream().map(Arrays::toString).forEach(System.out::println);
Output:
[1, 2, 2, 4, 4]
[3, 2, 4, 1, 5, 8]
See live demo.
Let's just say that it you need to give the first non-duplicate rows of the existing matrix. Then instead of keeping the indexes in a separate list, you could use a Map for which the unique key is the set of numbers in a row and the value is the row itself. Here is the complete class with the main method to test it :
public class NonSimilar {
private final int[][] matrix;
public NonSimilar(int[][] matrix) {
this.matrix = matrix;
}
public Set<int[]> getNonSimilarRows() {
Map<Set<Integer>, int[]> map = new HashMap<>();
for (int[] row : matrix) {
map.putIfAbsent(convertRowToSet(row), row);
}
return new HashSet<>(map.values());
}
public Set<Integer> convertRowToSet(int[] row){
return Arrays.stream(row).boxed().collect(Collectors.toSet());
}
public static void main(String[] args) {
int[][] matrix = {{1, 2, 2, 4, 4}, {4, 2, 1, 4}, {3, 2, 4, 1, 5, 8}};
Set<int[]> result = new NonSimilar(matrix).getNonSimilarRows();
result.forEach(row -> System.out.println(Arrays.toString(row)));
}
}
Now you might say that it prints
3 2 4 1 5 8
1 2 2 4 4
instead of
1 2 2 4 4
3 2 4 1 5 8
That's because the result is a Set and a set doesn't have the concept of order. If you really want it to be printed in the correct order, you can use a LinkedHashMap and return a LinkedHashSet.
NOTE : you can even make it shorter by using Collectors.toMap:
public Set<int[]> getNonSimilarRows() {
Map<Set<Integer>, int[]> map = Arrays.stream(matrix)
.collect(Collectors.toMap(this::convertRowToSet, Function.identity(), (r1, r2) -> null));
return new HashSet<>(map.values());
}
(r1, r2) -> r1 is to state that you accept duplicate keys and that you should keep the first value encountered. In the case of you want to keep the last value encountered, you can replace it by (r1, r2) -> r2.
With this
How to convert an Array to a Set in Java
How to compare two sets for equality
you could write it like this:
public class NonSimilarRowsTest {
#Test
public void test() {
int[][] matrix = {{1, 2, 2, 4, 4}, {4, 2, 1, 4}, {3, 2, 4, 1, 5, 8}};
int[][] expected = {{1, 2, 2, 4, 4}, {3, 2, 4, 1, 5, 8}};
assertEquals(expected, nonSimilarRows(matrix));
}
int[][] nonSimilarRows(int[][] matrix) {
Set<Set<Integer>> rows = new HashSet<>();
int[][] result = new int[matrix.length][];
int length = 0;
for (int[] row : matrix) {
if (rows.add(toSet(row))) {
result[length++] = row;
}
}
return Arrays.copyOf(result, length);
}
Set<Integer> toSet(int[] array) {
return Arrays.stream(array).boxed().collect(Collectors.toSet());
}
}
Here's another solution that maintains an unordered set that keeps tracks of duplicate rows and also maintains order by storing the results in list:
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Collectors;
public class Test {
private static final int[][] rows = new int[][] {
{ 1, 2, 2, 4, 4 },
{ 4, 2, 1, 4 },
{ 3, 2, 4, 1, 5, 8 }
};
private static Set<Set<Integer>> seenRows = new HashSet<>();
private static List<int[]> uniqueRows = new ArrayList<>();
public static void main(String[] args) {
for (int[] row : rows) {
Set<Integer> uniqueNumbers = Arrays.stream(row).boxed().collect(Collectors.toSet());
if (!seenRows.contains(uniqueNumbers)) {
uniqueRows.add(row);
seenRows.add(uniqueNumbers);
}
}
for (int[] row : uniqueRows) {
System.out.println(Arrays.toString(row));
}
}
}
Output:
[1, 2, 2, 4, 4]
[3, 2, 4, 1, 5, 8]
I'm trying to solve a problem on CodeFights called firstDuplicate, that states -
Given an array a that contains only numbers in the range from 1 to
a.length, find the first duplicate number for which the second
occurrence has the minimal index. In other words, if there are more
than 1 duplicated numbers, return the number for which the second
occurrence has a smaller index than the second occurrence of the other
number does. If there are no such elements, return -1.
Example
For a = [2, 3, 3, 1, 5, 2], the output should be firstDuplicate(a) =
3.
There are 2 duplicates: numbers 2 and 3. The second occurrence of 3
has a smaller index than than second occurrence of 2 does, so the
answer is 3.
For a = [2, 4, 3, 5, 1], the output should be firstDuplicate(a) = -1.
My solution -
public class FirstDuplicate {
private static HashMap<Integer, Integer> counts = new HashMap<>();
private static void findSecondIndexFrom(int[] num, int n, int i) {
// given an array, a starting index and a number, find second occurrence of that number beginning from next index
for(int x = i; x < num.length; x++) {
if(num[x] == n) {
// second occurrence found - place in map and terminate
counts.put(n, x);
return;
}
}
}
private static int firstDuplicate(int[] a) {
// for each element in loop, if it's not already in hashmap
// find it's second occurrence in array and place number and index in map
for(int i = 0; i < a.length; i++) {
if(!counts.containsKey(a[i])) {
findSecondIndexFrom(a, a[i], i+1);
}
}
System.out.println(counts);
// if map is empty - no duplicate elements, return -1
if(counts.size() == 0) {
return -1;
}
// else - get array of values from map, sort it, find lowest value and return corresponding key
ArrayList<Integer> values = new ArrayList<>(counts.values());
Collections.sort(values);
int lowest = values.get(0);
//System.out.println(lowest);
for(Map.Entry<Integer, Integer> entries: counts.entrySet()) {
if(entries.getValue() == lowest) {
return entries.getKey();
}
}
return -1;
}
public static void main(String[] args) {
// int[] a = new int[]{2, 3, 3, 1, 5, 2};
//int[] a = new int[]{2, 4, 3, 5, 1};
//int[] a = new int[]{8, 4, 6, 2, 6, 4, 7, 9, 5, 8};
//int[] a = new int[]{1, 1, 2, 2, 1};
int[] a = new int[]{10, 6, 8, 4, 9, 1, 7, 2, 5, 3};
System.out.println(firstDuplicate(a));
}
}
This solution passes only for about 4 of the 11 test cases on CodeFights. However, I manually executed each one of the test cases in my IDE, and each one produces the right result.
I can't figure out why this won't work in CodeFights. Does it have something to do with the use of the static HashMap?
Edited: Since adding and checking if element is present in Set can be done in one step, code can be simplified to:
public static int findDuplicateWithLowestIndex(int... a){
Set<Integer> set = new HashSet<>();
for(int num : a){
if(!set.add(num)){
return num;
}
}
return -1;
}
You're completly right Patrick.
Use this solution: here duplicateIndex should be very large number.
package sample;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Duplicate {
public static Integer secondIndex(Integer[] arr) {
List<Integer> arrlist = new ArrayList<>(Arrays.asList(arr));
int duplicateIndex = 999;
int ele = 0;
for (int i = 0; i < arrlist.size(); i++) {
int secondIndex = getSecondIndex(arrlist, arrlist.get(i));
if (secondIndex >= 0 && duplicateIndex > secondIndex) {
duplicateIndex = secondIndex;
ele = arrlist.get(i);
}
}
return duplicateIndex == 999 ? -1 : ele;
}
public static int getSecondIndex(List<Integer> arr, int ele) {
List<Integer> var0 = new ArrayList<>(arr);
var0.set(var0.indexOf(ele), -1);
return var0.indexOf(ele);
}
public static void main(String[] str) {
// Integer[] arr = new Integer[] { 2, 3, 3, 1, 5, 2 };
// Integer[] arr = new Integer[] { 2, 4, 3, 5, 1 };
// Integer[] arr = new Integer[] { 8, 4, 6, 2, 6, 4, 7, 9, 5, 8 };
// Integer[] arr = new Integer[]{1, 1, 2, 2, 1};
Integer[] arr = new Integer[] { 10, 6, 8, 4, 9, 1, 7, 2, 5, 3 };
System.out.println(secondIndex(arr));
}
}
Solution in Javascript
function solution(a) {
const duplicates = [];
for (const i of a) {
if (duplicates.includes(i))
return i;
else
duplicates.push(i);
}
return -1;
}
console.log(solution([2, 1, 3, 5, 3, 2])); // 3
console.log(solution([2, 2])); // 2
console.log(solution([2, 4, 3, 5, 1])); // -1
I have n lists, for example:
L_1 = [a_11, a_12, ...]
L_2 = [a_21, a_22, ...]
...
L_n = [a_n1, a_n2, ...]
where ith list has k_i elements.
And now, I want to generate all n-elements list, where ith element is from L_i, I mean:
[a_11, a_21, ..., a_n1]
[a_11, a_21, ..., a_n2]
...
[a_11, a_22, ..., a_n1]
[a_11, a_22, ..., a_n2]
...
[a_12, a_21, ..., a_n1]
[a_12, a_21, ..., a_n2]
...
[a_12, a_22, ..., a_n1]
[a_12, a_22, ..., a_n2]
...
The total number of lists shoulbe be equal to k_1*k_2*...k_n. Could you describe pseudo-code of this algorithm or use Java code? I can do this using nested for-loops when number of lists is hardcoded, but I'm completely blocked when n is customizable at runtime.
Ok, I implemented this algorithm.
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class PermutationGenerator {
private List<List<Integer>> result;
private List<List<Integer>> data;
public List<List<Integer>> permutate(List<List<Integer>> data) {
this.data = data;
this.result = Lists.newArrayList();
List<Integer> integers = new ArrayList<Integer>(Collections.nCopies(data.size(), 0));
foo(0, data.size() - 1, integers);
return result;
}
private void foo(Integer index, Integer maxIndex, List<Integer> output) {
List<Integer> list = data.get(index);
for (int i = 0; i < list.size(); i++) {
output.set(index, list.get(i));
if (index == maxIndex) {
result.add(Lists.newArrayList(output));
} else {
foo(index + 1, maxIndex, output);
}
}
}
}
Test class:
import com.google.common.collect.Lists;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
public class PermutationGeneratorTest {
#Test
public void test() throws Exception {
// given
PermutationGenerator pg = new PermutationGenerator();
List<Integer> list1 = Lists.newArrayList(1, 2, 3);
List<Integer> list2 = Lists.newArrayList(4, 5);
List<Integer> list3 = Lists.newArrayList(6, 7, 8, 9);
List<List<Integer>> input = Lists.newArrayList(list1, list2, list3);
// when
List<List<Integer>> output = pg.permutate(input);
// then
print(output);
}
private void print(List<List<Integer>> output) {
for (List<Integer> list : output) {
System.out.println(Arrays.toString(list.toArray()));
}
System.out.println("TOTAL: " + output.size());
}
}
Output:
[1, 4, 6]
[1, 4, 7]
[1, 4, 8]
[1, 4, 9]
[1, 5, 6]
[1, 5, 7]
[1, 5, 8]
[1, 5, 9]
[2, 4, 6]
[2, 4, 7]
[2, 4, 8]
[2, 4, 9]
[2, 5, 6]
[2, 5, 7]
[2, 5, 8]
[2, 5, 9]
[3, 4, 6]
[3, 4, 7]
[3, 4, 8]
[3, 4, 9]
[3, 5, 6]
[3, 5, 7]
[3, 5, 8]
[3, 5, 9]
TOTAL: 24
As you have already found out yourself, the usual trick is to think of the lists a non-uniform version of the g-adic numbers and do carry increment on the list index positions:
When you have n lists, you have n index positions in those lists:
index_pos = [i0, ..., in-1]
The trick is now as follows:
start with index_pos = [0, 0, ...]
increment index_pos[0].
If the result is larger or equal to lists[0].size(), set index_pos[0] = 0 and increment index_pos[1].
if index_pos[1] is larger than or equal to lists[1].size() ... and so on
You are done when index_pos[n - 1] overflows
An non-recursive solution in Java would be like
public static <T> void permute(
final List<List<T>> lists,
final Consumer<List<T>> consumer
)
{
final int[] index_pos = new int[lists.size()];
final int last_index = lists.size() - 1;
final List<T> permuted = new ArrayList<T>(lists.size());
for (int i = 0; i < lists.size(); ++i) {
permuted.add(null);
}
while (index_pos[last_index] < lists.get(last_index).size()) {
for (int i = 0; i < lists.size(); ++i) {
permuted.set(i, lists.get(i).get(index_pos[i]));
}
consumer.accept(permuted);
for (int i = 0; i < lists.size(); ++i) {
++index_pos[i];
if (index_pos[i] < lists.get(i).size()) {
/* stop at first element without overflow */
break;
} else if (i < last_index) {
index_pos[i] = 0;
}
}
}
}
Usage example:
public static void main(String[] args)
{
final List<List<Integer>> lists = new ArrayList<List<Integer>>();
final List<Integer> list0 = new ArrayList<Integer>();
list0.add(0);
list0.add(1);
list0.add(2);
list0.add(4);
lists.add(list0);
lists.add(list0);
lists.add(list0);
permute(lists, (permutation -> System.out.println(permutation)));
}