Java 8 - Merge All Subsets Containing Common Elements - java

Starting with a set of sets "groups":
Set<Set<String>> groups = new HashSet<>();
I want to create a new list of sets by merging all subsets with common elements:
i.e. Starting with the sets below:
A = {a, b, c}
B = {c, d, e, f}
C = {f, g, h, i, j}
D = {k, l, m}
E = {m, n, o}
F = {p, q, r}
The final result would be:
Set 1 = {a, b, c, d, e, f, g, h, i, j}
Set 2 = {k, l, m, n, o}
Set 3 = {p, q, r}
Any advice on how to accomplish this would be appreciated.
EDIT: In case of uneven sets it would perform the same. So if it were a method, it pseudo would look like this:
public void doStuff(){
Set<Set<String>> groups = {{a,b,c}, {c,d,e,f}, {m, n, o}}
Set<Set<String>> newGroups = mergeSubsets(groups);
System.out.println(newGroups);
}
public Set<Set<String>> mergeSubsets(Set<Set<String>> groups){
//some operations
}
Console out:
New Groups: {{a,b,c,d,e,f}, {m, n, o}}

You can just implement the algorithm as you describe it in your problem statement -- find intersecting sets and merge them until there is nothing to merge. Standard library has a method Collections.disjoint that helps by determining if two collections have any elements in common:
// this implementation sacrifices efficiency for clarity
public Set<Set<String>> mergeSubsets(Set<Set<String>> groups) {
Set<Set<String>> result = new HashSet<>();
for (Set<String> set : groups) {
// try to find a set in result that intersects this set
// if one is found, merge the two. otherwise, add this set to result
result.stream()
.filter(x -> !Collections.disjoint(x, set))
.findAny()
.ifPresentOrElse( // this method was added in java 9
x -> x.addAll(set),
() -> result.add(new HashSet<>(set))
);
}
// if nothing got merged we are done; otherwise, recurse and try again
return result.size() == groups.size() ? result : mergeSubsets(result);
}

Here is the imperative way based on #NiksVij solution. Obviously the solution of #NiksVij is not correct and this answer aims to fix this and extend a bit more:
public class MergeSet {
public static void main(String... args) {
List<Set<String>> list = new ArrayList<>();
String[] A = {"a", "c", "e", "g"};
String[] B = {"b", "d", "f", "h"};
String[] C = {"c", "e", "f"};
String[] D = {"b"};
list.add(new HashSet<>(Arrays.asList(A)));
list.add(new HashSet<>(Arrays.asList(C)));
list.add(new HashSet<>(Arrays.asList(B)));
list.add(new HashSet<>(Arrays.asList(D)));
List<Set<String>> newGroups = merge(list);
System.out.println(newGroups);
}
#SuppressWarnings("empty-statement")
private static <T> List<Set<T>> merge(List<Set<T>> list) {
if (list == null || list.isEmpty()) {
return list;
}
List<Set<T>> merged = new ArrayList<>();
do {
merged.add(list.get(0));
list.remove(0);
while (mergeStep(merged.get(merged.size() - 1), list));
} while (!list.isEmpty());
return merged;
}
private static <T> boolean mergeStep(Set<T> setToCheck, List<Set<T>> remainingList) {
boolean atLeastOnceMerged = false;
Iterator<Set<T>> iterator = remainingList.iterator();
while (iterator.hasNext()) {
Set<T> elements = iterator.next();
boolean doMerge = !Collections.disjoint(elements, setToCheck);
if (doMerge) {
atLeastOnceMerged |= doMerge;
setToCheck.addAll(elements);
iterator.remove();
}
}
return atLeastOnceMerged;
}

import java.util.*;
public class MergeSet {
public static void main(String... args) {
List<Set<String>> groups = new ArrayList<>();
String[] A = {"a", "b", "c"};
String[] B = {"c", "d", "e", "f"};
String[] C = {"f", "g", "h", "i", "j"};
String[] D = {"k", "l", "m"};
String[] E = {"m", "n", "o"};
String[] F = {"p", "q", "r"};
groups.add(new HashSet<>(Arrays.asList(A)));
groups.add(new HashSet<>(Arrays.asList(B)));
groups.add(new HashSet<>(Arrays.asList(C)));
groups.add(new HashSet<>(Arrays.asList(D)));
groups.add(new HashSet<>(Arrays.asList(E)));
groups.add(new HashSet<>(Arrays.asList(F)));
Set<Set<String>> newGroups = mergeSubsets(groups);
System.out.println(newGroups);
}
private static Set<Set<String>> mergeSubsets(List<Set<String>> groups) {
List<Set<String>> newGroups = new ArrayList<>();
Set<String> init = groups.get(0);
groups.remove(0);
newGroups.add(init);
while (!groups.isEmpty()) {
removeMergedElementFromGroupAndUpdateNewGroup(newGroups.get(newGroups.size() - 1), groups);
if(!groups.isEmpty()) {
init = groups.get(0);
groups.remove(0);
newGroups.add(init);
}
}
return new HashSet<>(newGroups);
}
private static void removeMergedElementFromGroupAndUpdateNewGroup(Set<String> master2, List<Set<String>> masterList) {
Iterator<Set<String>> iterator = masterList.iterator();
while (iterator.hasNext()) {
Set<String> strings = iterator.next();
boolean merge = strings.stream().anyMatch(string -> master2.contains(string));
if (merge) {
master2.addAll(strings);
iterator.remove();
}
}
}
}
Hope this helps instead of Set<Set<String>> groups I have used List<Set<String>> groups for the ease of using lists if you have a constraint of using Set only , you can generate List from Set(say yourSet) by passing it into the constructor of Lists implementation , for eg.
groups = new ArrayList<>(yourSet);

Related

How efficiently sort a list by groups?

I need to group a given sort list by some given "blocks" or "groups" of elements. For example:
Given a list:
[A, B, C, D, E, F, G, H, I, J]
And groups
[A, C, D]
[F, E]
[J, H, I]
the result should be
[A, C, D, B, F, E, G, J, H, I]
The blocks of elements can not be mixed with non-group elements. The blocks should have the same order. The other elements of the list should mantain their order.
I have already found a solution. But it's not the most efficient code as you will see.
I'm using java 6 also...
public static List<CategoryProduct> sortProductsByBlocks(List<CategoryProduct> products, CategoryBlocks categoryBlocks) {
if (!validateCategoryBlocks(categoryBlocks)) {
return products;
}
Map<String, BlockView> mapProductByBlock = mapBlocksByPartnumber(categoryBlocks);
Map<String, BlockView> mapFirstProductByBlock = mapFirstProductByBlock(categoryBlocks);
Map<Integer, Block> blocksById = blocksById(categoryBlocks);
List<CategoryProduct> sortedProduct = Lists.newArrayList();
Map<String, CategoryProduct> productsMapByPartNumber = ProductHelper.getProductsMapByPartNumber(products);
List<CategoryProduct> processedProducts = Lists.newArrayList();
int j = 0;
for (int i = 0; i < products.size(); i++) {
CategoryProduct product = products.get(i);
if (blocksById.isEmpty() && !processedProducts.contains(product)) {
sortedProduct.add(j++, product);
processedProducts.add(product);
}
if (!processedProducts.contains(product) && (mapFirstProductByBlock.get(product.getPartNumber()) != null
|| mapProductByBlock.get(product.getPartNumber()) == null)) {
BlockView blockView = mapProductByBlock.get(product.getPartNumber());
if (blockView != null) {
Block block = blocksById.get(blockView.getBlockId());
if (block == null) {
sortedProduct.add(j++, product);
continue;
}
for (BlockProduct blockProduct : block.getProducts()) {
CategoryProduct categoryProduct = productsMapByPartNumber.get(blockProduct.getPartnumber());
sortedProduct.add(j++, categoryProduct);
processedProducts.add(categoryProduct);
}
blocksById.remove(blockView.getBlockId());
} else {
sortedProduct.add(j++, product);
processedProducts.add(product);
}
}
}
return sortedProduct;
}
Any advice to improve and make it faster will be welcome.
(edit with the improved code)
public static List<CategoryProduct> sortProductsByBlocks2(List<CategoryProduct> products,
CategoryBlocks categoryBlocks) {
if (!validateCategoryBlocks(categoryBlocks)) {
return products;
}
Map<String, Integer> blocksIdByFirstPartnumber = Maps.newHashMap();
List<String> partnumbersInBlocks = Lists.newArrayList();
for (int k = 0; k < categoryBlocks.getBlocks().size(); k++) {
Block block = categoryBlocks.getBlocks().get(k);
if (block != null && block.getProducts() != null) {
for (int i = 0; i < block.getProducts().size(); i++) {
BlockProduct blockProduct = block.getProducts().get(i);
if (i == 0) {
blocksIdByFirstPartnumber.put(blockProduct.getPartnumber(), k);
} else {
partnumbersInBlocks.add(blockProduct.getPartnumber());
}
}
}
}
CategoryProduct[] result = new CategoryProduct[products.size()];
Map<String, Integer> productsIndex = Maps.newHashMap();
Map<String, CategoryProduct> categoryProductByPartnumber = Maps.newHashMap();
int indexResult = 0;
for (CategoryProduct categoryProduct : products) {
String partNumber = categoryProduct.getPartNumber();
if (!partnumbersInBlocks.contains(partNumber)) {
if (blocksIdByFirstPartnumber.get(partNumber) != null) {
Block categoryProductBlock = categoryBlocks.getBlocks()
.get(blocksIdByFirstPartnumber.get(partNumber));
result[indexResult] = categoryProduct;
indexResult++;
for (int i = 1; i < categoryProductBlock.getProducts().size(); i++) {
BlockProduct blockProduct = categoryProductBlock.getProducts().get(i);
if (categoryProductByPartnumber.get(blockProduct.getPartnumber()) != null) {
result[indexResult] = categoryProductByPartnumber.get(blockProduct.getPartnumber());
} else {
productsIndex.put(blockProduct.getPartnumber(), indexResult);
result[indexResult] = null;
}
indexResult++;
}
} else {
result[indexResult] = categoryProduct;
indexResult++;
}
} else {
if (productsIndex.get(partNumber) != null) {
result[productsIndex.get(partNumber)] = categoryProduct;
} else {
categoryProductByPartnumber.put(partNumber, categoryProduct);
}
}
}
return Lists.newArrayList(Arrays.asList(result));
}
Performance:
Elements New algorithm Old algorithm
1200 0.002s 0.129s
12000 0.021s 14.673s
Form the code you submitted, I cannot figure out how your algorithm is fully working.
I can write another algorithm that will do the task.
Mark the first element for each group
[A,C,D] -> A
Remove from list(to_be_sorted) all elements from groups that are not marked
[A,C,D] -> remove [C,D]
perform sort on list
result ([A,B,F,G,J])
place removed element based on Mark
Initial Sorted List [A,B,F,G,J]
A->add [C,D]
List is [A,C,D,B,F,G,J]
B->as it is
F->add [E]
List is [A,C,D,B,F,E,G,J]
G->as it is
J->add [H,I]
Final Sorted List [A,C,D,B,F,E,G,J,H,I]
Time complexity is the same as sorting algorithm
By your definition it isn't entirely clear what the conditions are to merge the results from your given list and 'groups' ( arrays ). However, here is a solution based on your requirements using the assertion
"You want the first element of the list not contained in any of the groups inserted between the groups... "
public class MergeArrays {
private static final List<String> FIRST = new ArrayList<>(Arrays.asList("A", "B", "C", "D", "E", "F", "G", "H", "I", "J"));
private static final List<String> SECOND = new ArrayList<>(Arrays.asList("A", "C", "D"));
private static final List<String> THIRD = new ArrayList<>(Arrays.asList("F", "E"));
private static final List<String> FOURTH = new ArrayList<>(Arrays.asList("J", "H", "I"));
public static List<String> merge(List<String> source, List<String>... lists) {
List<String> result = new ArrayList<>();
for (List<String> list : lists) {
for (String value : list) {
source.remove(value);
}
}
for (List<String> list : lists) {
String value = null;
if (source.size() > 0) {
value = source.get(0);
source.remove(0);
}
result.addAll(merge(value, list));
}
return result;
}
public static List<String> merge(String value, List<String> list) {
List<String> result = new ArrayList<>(list);
if (value != null) {
result.add(value);
}
return result;
}
public static void main(String[] args) {
List<String> result = merge(FIRST, SECOND, THIRD, FOURTH);
System.out.println(result);
}
}
//Results
[A, C, D, B, F, E, G, J, H, I]

Average difference between elements

I need to transform list (time, channel) which is sorted by time ascending:
[15, A], [16, B], [17, C], [20, A], [22, C], [24, B], [26, C], [27, B], [28, A]
to this one:
[6.5, A], // ((20-15)+(28-20))/2 - average difference between elements (channel A)
[5.5, B], // ((24-16)+(27-24))/2
[4.5, C] // ((22-17)+(26-22))/2
using java streams.
Well, supposing that there is something like ChannelInfo class:
class ChannelInfo {
private final int time;
private final String channel;
// constructor, getters, setters
it could be achieved like this:
List<ChannelInfo> pairs = Arrays.asList(
new ChannelInfo(15, "A"), new ChannelInfo(16, "B"),
new ChannelInfo(17, "C"), new ChannelInfo(20, "A"),
new ChannelInfo(22, "C"), new ChannelInfo(24, "B"),
new ChannelInfo(26, "C"), new ChannelInfo(27, "B"),
new ChannelInfo(28, "A"));
Map<String, Double> map = pairs.stream()
.collect(Collectors.groupingBy(ChannelInfo::getChannel,
Collectors.collectingAndThen(Collectors.toList(),
list -> {
int size = list.size();
return IntStream.range(1, size)
.map(x -> (list.get(size - x).getTime() - list.get(size - x - 1).getTime()))
.average()
.orElse(0d);
})));
System.out.println(map); // {A=6.5, B=5.5, C=4.5}
As I understood from the original question, I can propose one of the ways, using own data class:
public class MyList {
private int first;
private String second;
public MyList(int it, String str) {
this.first = it;
this.second = str;
}
public MyList() {
}
public int getValue() {
return this.first;
}
public MyList getChannel(String str) {
if (str.equals(second)) {
return this;
}
return null;
}
}
And main class for processing:
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
public class MyListProcessing {
private ArrayList<MyList> ml = new ArrayList<MyList>();
private String[] channels = new String[]{"A", "B", "C"};
private double[] channels_avg = new double[channels.length];
private int[] channels_qnt = new int[channels.length];
public MyListProcessing() {
ml.add(new MyList(15, "A"));
ml.add(new MyList(16, "B"));
ml.add(new MyList(17, "C"));
ml.add(new MyList(20, "A"));
ml.add(new MyList(22, "C"));
ml.add(new MyList(24, "B"));
ml.add(new MyList(26, "C"));
ml.add(new MyList(27, "B"));
ml.add(new MyList(28, "A"));
getAverage();
}
private void getAverage() {
for (int i = 0; i < channels.length; i++) {
MyList mmll = new MyList();
double sum = 0.0;
for (int j = 0; j < ml.size(); j++) {
mmll = ml.get(j).getChannel(channels[i]);
if (mmll != null) {
sum += mmll.getValue();
channels_qnt[i] = channels_qnt[i] + 1;
}
channels_avg[i] = sum / channels_qnt[i];
}
}
NumberFormat formatter = new DecimalFormat("#0.00");
for (int i = 0; i < channels_avg.length; i++) {
System.out.println("[" + formatter.format(channels_avg[i]) + ", " + channels[i] + "]");
}
}
public static void main(String[] args) {
new MyListProcessing();
}
}
OUTPUT:
[21.00, A]
[22.33, B]
[21.67, C]

LinkedList of LinkedList with recursion - loop issue

I have a List of Lists structure and a recursive function called tree. In the following code it NEVER reaches the current == null statement so it will run forever.
If I cannot use null, what is the solution?
private void tree(LinkedList<LinkedList<String>> partitions, LinkedList<String> part)
{
LinkedList<String> current = findBiggerPartitionContained(partitions, part);
if (current == null) {
return;
}
tree(partitions, current);
}
private LinkedList<String> findBiggerPartitionContained(LinkedList<LinkedList<String>> partitions, LinkedList<String> part)
{
LinkedList<String> max = new LinkedList<>();
boolean flag = false;
for (LinkedList<String> item : partitions) {
if (item.size() > max.size() && part.containsAll(max)) {
max = item;
flag = true;
}
}
if (!flag)
return null;
flag = false;
return max;
}
Most of the time flag will be true because your condition tests item.size() > max.size(), and max is initialized with an empty list. When max is empty, the expression part.containsAll(max) will be true as well, which leads to unexpected results.
In order to fix this, you can use this in findBiggerPartitionContained:
if (item.size() > max.size() && item.containsAll(part)) {
max = item;
flag = true;
}
And this in tree:
if (current.equals(part)) {
return;
} else {
tree(partitions, current);
}
If I have understood correctly, you're looking for the biggest list in partitions which contains part. Maybe the following is less error prone and more readable:
List<String> result = partitions.stream().filter(list -> list.containsAll(part))
.max(Comparator.comparingInt(List::size))
.orElse(null);
You can test it with this MCVE:
List<String> p0 = new LinkedList<>(Arrays.asList("a", "b", "c"));
List<String> p1 = new LinkedList<>(Arrays.asList("a", "b"));
List<String> p2 = new LinkedList<>(Arrays.asList("a", "b", "c", "d"));
List<String> p3 = new LinkedList<>(Arrays.asList("a", "b", "e", "d"));
List<List<String>> partitions = Arrays.asList(p0, p1, p2, p3);
List<String> part = new LinkedList<>(Arrays.asList("a", "b", "e"));
List<String> result = partitions.stream().filter(list -> list.containsAll(part))
.max(Comparator.comparingInt(List::size))
.orElse(null);
System.out.println(result);
Bear in mind that this may returns null to handle absent Optionals.

How to cross join multiple Lists in Java? [duplicate]

This question already has answers here:
Get a list of combinations of lists' elements
(4 answers)
Closed 8 years ago.
Cross Join concept is the same as database cross join. I have multiple lists lets say 3 to start with and I have to join the lists as below:
List<E> l1: {a, b},
List<E> l2: {c, d},
List<E> l3: {e, f},
Cross join should produce:
List<E> l4 = {a, c, f},
List<E> l5 = {a, c, e},
List<E> l6 = {a, d, f},
List<E> l7 = {a, d, e},
List<E> l8 = {b, c, f},
List<E> l9 = {b, c, e},
List<E> l10 = {b, d, f},
List<E> l11= {b, d, e};
3 Lists with 3 elements each will give 27 new lists. And number of elements in each list is same, always.
Now one possible solution is to iterate with three for loops and then add to 3 new lists.
Is there any other possible way to achieve this so that complexity is lower?
Thanks
This code will work for any List<List<T>> parameter.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class X {
public static void main(String[] args) {
List<List<String>> param = new ArrayList<List<String>>() {{
add(Arrays.asList("A", "B"));
add(Arrays.asList("C", "D"));
add(Arrays.asList("E", "F"));
}};
for (List<String> l : getCross(param)) {
System.out.println(l);
}
}
public static <T> List<List<T>> getCross(List<List<T>> values) {
List<List<T>> accumulator = new ArrayList<List<T>>();
if (values.size() != 0) {
List<T> comb = new ArrayList<T>();
comb.addAll(Collections.<T>nCopies(values.size(), null));
getCross(accumulator, 0, comb, values);
}
return accumulator;
}
private static <T> void getCross(List<List<T>> accumulator, int idx, List<T> combination, List<List<T>> param) {
if (idx == combination.size()) {
accumulator.add(new ArrayList<T>(combination));
} else {
for(T t : param.get(idx)) {
combination.set(idx, t);
getCross(accumulator, idx + 1, combination, param);
}
}
}
}
Below code will work for any kind of metric.
static void joinList()
{
List l11 = new ArrayList();
List l22 = new ArrayList();
List l33 = new ArrayList();
l11.add("a");
l11.add("b");
l22.add("c");
l22.add("d");
l33.add("e");
l33.add("f");
List<List> crosslist = new ArrayList<List>();
for(int i =0;i<l11.size();i++)
{
for(int j=0 ; j<l22.size();j++)
{
for(int k =0; k<l33.size();k++)
{
List list = new ArrayList();
list.add(l11.get(i));
list.add(l22.get(j));
list.add(l33.get(k));
crosslist.add(list);
}
}
}
}
try my solution,
you can use here as much as you need lists.
public class Main{
private void run() {
List<String> l11 = new ArrayList<String>(){{add("a");add("b");}};
List<String> l22 = new ArrayList<String>(){{add("c");add("d");}};
List<String> l33 = new ArrayList<String>(){{add("e");add("f");}};
crossJoin(l11, l22, l33);
}
public void crossJoin(List<String> ... lists) {
for (List<String> list : lists) {
mixList(list, lists);
}
}
private void mixList(List<String> list, List<String>[] lists) {
for (List<String> listSrt : lists) {
if (listSrt == list) continue;
for (String baseString : list) {
System.out.print(baseString + " ");
for (String outerStr : listSrt ) {
System.out.print(outerStr + " ");
}
System.out.println(" ");
}
}
}
public static void main(String[] args) {
Main main = new Main();
main.run();
}
}

How do I generate a Cartesian product in Java?

I have a number of ArrayList with each ArrayList having objects and each one can have different length. I need to generate permutation like in the below example:
Suppose I have 2 ArrayList:
ArrayList A has object a, object b and object c
ArrayList B has object d, object e
Then the output should be 6 new ArrayList with these combinations:
Combination 1 object a and object d,
Combination 2 object a and object e,
Combination 3 object b and object d,
Combination 4 object b and object e,
Combination 5 object c and object d,
Combination 6 object c and object e,
Can anyone help me?
Guava 19+
Lists.cartesianProduct(List...)
E.g.:
List<Object> list1 = Arrays.asList("a", "b", "c");
List<Object> list2 = Arrays.asList("d", "e");
System.out.println(Lists.cartesianProduct(list1, list2));
Output:
[[a, d], [a, e], [b, d], [b, e], [c, d], [c, e]]
With Java8 streams
List<String> a = Arrays.asList("a", "b", "c");
List<String> b = Arrays.asList("d", "e");
String[][] AB = a.stream()
.flatMap(ai -> b.stream()
.map(bi -> new String[]{ai, bi}))
.toArray(String[][]::new);
System.out.println(Arrays.deepToString(AB));
output
[[a, d], [a, e], [b, d], [b, e], [c, d], [c, e]]
To get as List
List<List<String>> ll = a.stream()
.flatMap(ai -> b.stream()
.map(bi -> new ArrayList<>(Arrays.asList(ai, bi))))
.collect(Collectors.toList());
With an Iterable+Iterator:
import java.util.*;
class CartesianIterator <T> implements Iterator <List <T>> {
private final List <List <T>> lilio;
private int current = 0;
private final long last;
public CartesianIterator (final List <List <T>> llo) {
lilio = llo;
long product = 1L;
for (List <T> lio: lilio)
product *= lio.size ();
last = product;
}
public boolean hasNext () {
return current != last;
}
public List <T> next () {
++current;
return get (current - 1, lilio);
}
public void remove () {
++current;
}
private List<T> get (final int n, final List <List <T>> lili) {
switch (lili.size ())
{
case 0: return new ArrayList <T> (); // no break past return;
default: {
List <T> inner = lili.get (0);
List <T> lo = new ArrayList <T> ();
lo.add (inner.get (n % inner.size ()));
lo.addAll (get (n / inner.size (), lili.subList (1, lili.size ())));
return lo;
}
}
}
}
class CartesianIterable <T> implements Iterable <List <T>> {
private List <List <T>> lilio;
public CartesianIterable (List <List <T>> llo) {
lilio = llo;
}
public Iterator <List <T>> iterator () {
return new CartesianIterator <T> (lilio);
}
}
You can use them in a simplified for-loop:
class CartesianIteratorTest {
public static void main (String[] args) {
List <Character> la = Arrays.asList (new Character [] {'a', 'b', 'c'});
List <Character> lb = Arrays.asList (new Character [] {'d', 'e'});
List <List <Character>> llc = new ArrayList <List <Character>> ();
llc.add (la);
llc.add (lb);
CartesianIterable <Character> ci = new CartesianIterable <Character> (llc);
for (List<Character> lo: ci)
show (lo);
}
public static void show (List <Character> lo) {
System.out.print ("(");
for (Object o: lo)
System.out.print (o);
System.out.println (")");
}
}
Cartesian product of multiple lists using the map and reduce approach
The map method represents each element of the list as a singleton list and specifies the format of the result.
Intermediate output:
[[a], [b], [c]]
[[d], [e]]
[[f]]
The reduce method sums pairs of 2D lists into a single 2D list.
Final output:
[[a, d, f], [a, e, f], [b, d, f], [b, e, f], [c, d, f], [c, e, f]]
Try it online!
public static void main(String[] args) {
List<String> a = Arrays.asList("a", "b", "c");
List<String> b = Arrays.asList("d", "e");
List<String> c = Arrays.asList("f");
List<List<String>> cp = cartesianProduct(Arrays.asList(a, b, c));
// output
System.out.println(cp);
}
public static <T> List<List<T>> cartesianProduct(List<List<T>> lists) {
// check if not null
if (lists == null) return null;
// cartesian product of multiple lists
return lists.stream()
// only those lists that are not null and not empty
.filter(list -> list != null && list.size() > 0)
// represent each list element as a singleton list
.map(list -> list.stream().map(Collections::singletonList)
// Stream<List<List<T>>>
.collect(Collectors.toList()))
// intermediate output
.peek(System.out::println)
// stream of lists into a single list
.reduce((lst1, lst2) -> lst1.stream()
// combinations of inner lists
.flatMap(inner1 -> lst2.stream()
// concatenate into a single list
.map(inner2 -> Stream.of(inner1, inner2)
.flatMap(List::stream)
.collect(Collectors.toList())))
// list of combinations
.collect(Collectors.toList()))
// otherwise an empty list
.orElse(Collections.emptyList());
}
See also: Cartesian product of an arbitrary number of sets
Use Guava... Here is an example of a Cartesian product of a list with itself:
public static void main(String[] args) {
//How to do a cartesian product of a List of items
List<Integer> listToSelfMultiply = Arrays.asList(
new Integer(1), new Integer(2), new Integer(3), new Integer(4));
LinkedList<Integer> linkedListCopy = Lists.newLinkedList(listToSelfMultiply);
for (Integer i : listToSelfMultiply) {
if (linkedListCopy.size() == 1) {
break;
}
linkedListCopy.remove();
System.out.println("" + Arrays.deepToString(
Lists.cartesianProduct(Arrays.asList(i), linkedListCopy).toArray()) + "");
}
}
Use nested for loops that would have a loop for every ArrayList as below. I am assuming I have two ArrayLists - intList and stringList. I can have two nested for loops (one for each list) to generate the permutation.
for (Integer i : intList) {
for (String s : stringList) {
...
}
}
Cartesian product of multiple lists
You can use the reduce method with three parameters:
identity - specify the result stub.
List<List<T>>
accumulator - append elements of lists to the result.
List<List<T>> result, List<T> list
combiner - is used in parallel mode, combines the results.
List<List<T>> result1, List<List<T>> result2
Try it online!
/**
* #param lists the lists for multiplication
* #param <T> the type of list element
* #return the Cartesian product
*/
public static <T> List<List<T>> cartesianProduct(List<List<T>> lists) {
// check if incoming data is not null
if (lists == null) return Collections.emptyList();
return lists.stream()
// non-null and non-empty lists
.filter(list -> list != null && list.size() > 0)
// stream of lists into a single list
.reduce(// identity - specify the result stub
Collections.singletonList(Collections.emptyList()),
// accumulator - append elements of lists to the result
(result, list) -> result.stream()
.flatMap(inner -> list.stream()
.map(el -> {
List<T> nList = new ArrayList<>(inner);
nList.add(el);
return nList;
}))
// list of combinations
.collect(Collectors.toList()),
// combiner - is used in parallel mode, combines the results
(result1, result2) -> {
result1.addAll(result2);
return result1;
});
}
public static void main(String[] args) {
List<String> l1 = Arrays.asList("A", "B");
List<String> l2 = Arrays.asList("C", "D");
List<String> l3 = Arrays.asList("E", "F");
List<List<String>> cp = cartesianProduct(Arrays.asList(l1, l2, l3));
// output
System.out.println(cp);
}
Output:
[[A,C,E],[A,C,F],[A,D,E],[A,D,F],[B,C,E],[B,C,F],[B,D,E],[B,D,F]]
See also: Cartesian product of 3 collections

Categories