Average difference between elements - java

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]

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]

Java 8 - Merge All Subsets Containing Common Elements

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);

Java 8 Grouping by Multiple Fields into Single Map

I have a class like this:
public class Foo {
private String a;
private String b;
private LocalDate c;
private int d;
}
I have a list of Foo objects that I want to group by a, b, and c and produce a map. Here's what I have so far:
Map<String, List<Foo>> test = foos.stream().collect(Collectors.groupingBy(Foo::getA, Collectors.collectingAndThen(Collectors.groupingBy(Foo::getB), Collections.unmodifiableList())));
But that itself is wrong. I don't know how to groupby multiple fields but still produce a Map<String, List<Foo>>. Any ideas what I'm doing wrong?
Edit 1: If I have the following Foo's:
{"Test", "Test", "10/02/2015", 5}
{"Test", "Test", "10/02/2015", 4}
{"Test", "Test", "10/02/2015", 3}
{"Test", "Test", "2/02/2015", 5}
{"Test", "Potato", "2/02/2015", 5}
Then it should group to:
{"Test", "Test", "10/02/2015", [5, 4, 3]}
{"Test", "Test", "2/02/2015", 5}
{"Test", "Potato", "2/02/2015", 5}
My original post was misleading in what exactly I wanted but basically it needs to group by a, b, d and produce a list of d. I know I'll probably have to create a new class to store them in like so:
public class FooResult {
private String a;
private String b;
private LocalDate c;
private List<Integer> d;
}
How can I group and map to a new class like shown above?
As a group by multiple fields is not implemented you have to use a composite key consisting of values from a, b and c. With that key the collect operation can be used like this with the Collector#of() factory method.
Map<String, List<Integer>> result = foos.stream().collect(Collector.of(
HashMap::new,
( map, foo ) -> {
map.compute(foo.a + "_" + foo.b + "_" + foo.c, (key,list) -> {
if(list == null){
list = new ArrayList<>();
}
list.add(foo.d);
return list;
});
},
( map1, map2 ) -> {
map2.forEach(( k, v ) -> {
map1.compute(k, (key, list) -> {
if(list == null){
list = v;
} else {
list.addAll(v);
}
return list;
});
});
return map1;
}
));
You can also use intermediate Map with a key that aggregates fields a, b and c from Foo class and List<Integer> value that collects all d field values.. In below example I have created MapKey class - a helper class that aggregates those fields and implements hashCode and equals methods so it can be used as a key in a HashMap.
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FooMain {
public static void main(String[] args) {
final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("MM/dd/yyyy");
final List<Foo> foos = Arrays.asList(
new Foo("Test", "Test", LocalDate.parse("10/02/2015", dateFormat), 5),
new Foo("Test", "Test", LocalDate.parse("10/02/2015", dateFormat), 4),
new Foo("Test", "Test", LocalDate.parse("10/02/2015", dateFormat), 3),
new Foo("Test", "Test", LocalDate.parse("02/02/2015", dateFormat), 5),
new Foo("Test", "Potato", LocalDate.parse("02/02/2015", dateFormat), 5)
);
List<FooResult> result = foos.stream()
.collect(Collectors.groupingBy(foo -> new MapKey(foo.a, foo.b, foo.c), Collectors.mapping(Foo::getD, Collectors.toList())))
.entrySet()
.stream()
.map(entry -> new FooResult(entry.getKey().a, entry.getKey().b, entry.getKey().c, entry.getValue()))
.collect(Collectors.toList());
result.forEach(System.out::println);
}
public static final class Foo {
private final String a;
private final String b;
private final LocalDate c;
private final int d;
Foo(String a, String b, LocalDate c, int d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
}
int getD() {
return d;
}
}
public static final class FooResult {
private final String a;
private final String b;
private final LocalDate c;
private final List<Integer> d;
FooResult(String a, String b, LocalDate c, List<Integer> d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
}
#Override
public String toString() {
return "FooResult{" +
"a='" + a + '\'' +
", b='" + b + '\'' +
", c=" + c +
", d=" + d +
'}';
}
}
public static final class MapKey {
private final String a;
private final String b;
private final LocalDate c;
MapKey(String a, String b, LocalDate c) {
this.a = a;
this.b = b;
this.c = c;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MapKey)) return false;
MapKey mapKey = (MapKey) o;
if (a != null ? !a.equals(mapKey.a) : mapKey.a != null) return false;
if (b != null ? !b.equals(mapKey.b) : mapKey.b != null) return false;
return c != null ? c.equals(mapKey.c) : mapKey.c == null;
}
#Override
public int hashCode() {
int result = a != null ? a.hashCode() : 0;
result = 31 * result + (b != null ? b.hashCode() : 0);
result = 31 * result + (c != null ? c.hashCode() : 0);
return result;
}
}
}
Then as you can see you can do your transformation is 6 lines of code. The output of this program is following:
FooResult{a='Test', b='Potato', c=2015-02-02, d=[5]}
FooResult{a='Test', b='Test', c=2015-02-02, d=[5]}
FooResult{a='Test', b='Test', c=2015-10-02, d=[5, 4, 3]}
I've also made Foo, FooResult and MapKey immutable - this is always a good choice when you have to deal with stream transformations. You don't want to have any side effects during stream manipulation and immutable objects guarantee that.

Java collection pack() method

What would be a good way to implement the pack() operation of a collection using Java stream? Here is what I'd like to accomplish:
List<String> items = Arrays.asList("A", "A", "B", "B", "A", "C", "C", "A", "A", "A");
List<List<String>> packs = items.stream().pack();
// packs: [[A,A],[B,B],[A],[C,C],[A,A,A]]
Alternatively, the pack operation could return a list of tuples in the form of (index, element, count):
[(0, A, 2), (2, B, 2), (4, A, 1), (5, C, 2), (7, A, 3)]
Currently I implemented this using a mutable accumulator in the following fashion:
Packer<String> packer = new Packer<>();
items.stream().forEach(packer);
List<Triple<Integer, T, Integer>> packs = packer.get();
public class Packer<T> implements Consumer<T>, Supplier<List<Triple<Integer, T, Integer>>>
{
private List<Triple<Integer, T, AtomicInteger>> result = new ArrayList<>();
private Optional<Triple<Integer, T, AtomicInteger>> currentElement = Optional.empty();
private int count = 0;
#Override
public void accept(T t)
{
if (currentElement.isPresent() && currentElement.get().getMiddle().equals(t))
{
currentElement.get().getRight().incrementAndGet();
}
else
{
currentElement = Optional.of(Triple.of(count, t, new AtomicInteger(1)));
result.add(currentElement.get());
}
count++;
}
#Override
public List<Triple<Integer, T, Integer>> get()
{
return result.stream().map(x -> Triple.of(x.getLeft(), x.getMiddle(), x.getRight().get())).collect(Collectors.toList());
}
}
You can collapse adjacent elements with StreamEx:
List<List<String>> packs = StreamEx.of(items)
.collapse(Object::equals, Collectors.toList())
.collect(Collectors.toList());
Output:
[[A, A], [B, B], [A], [C, C], [A, A, A]]
JDoodle Demo
I'm not sure if StreamEx supports collapsing with indexes.
Try this.
List<String> items = Arrays.asList("A", "A", "B", "B", "A", "C", "C", "A", "A", "A");
List<List<String>> result = items.stream()
.collect(() -> new LinkedList<List<String>>(),
(list, e) -> {
if (list.isEmpty() || !list.getLast().contains(e))
list.add(new LinkedList<>());
list.getLast().add(e);
},
(a, b) -> {});
System.out.println(result);
result
[[A, A], [B, B], [A], [C, C], [A, A, A]]
This can not be processed with parallel stream.
As #shmosel mentioned in his answer, it can done by StreamEx
List<List<String>> packs = StreamEx.of(items).collapse(Object::equals, Collectors.toList()).toList();
System.out.println(packs);
// [[A, A], [B, B], [A], [C, C], [A, A, A]]
MutableInt idx = MutableInt.of(0);
List<Triple<Integer, String, Integer>> packs2 = StreamEx.of(items).collapse(Object::equals, Collectors.toList())
.map(l -> Triple.of(idx.getAndAdd(l.size()), l.get(0), l.size())).toList();
System.out.println(packs2);
// [[0, A, 2], [2, B, 2], [4, A, 1], [5, C, 2], [7, A, 3]]
[Update]: Just found out, actually there are even simpler APIs available in StreamEx for this OP:
packs = StreamEx.of(items).groupRuns(Object::equals).toList();
System.out.println(packs);
packs2 = StreamEx.of(items).runLengths().mapKeyValue((k, v) -> Triple.of(idx.getAndAdd(v.intValue()), k, v.intValue())).toList();
System.out.println(packs2);
Amazing!
Seems like you could create a custom collector for that:
static class PackCollector<T> implements Collector<T, List<List<T>>, List<List<T>>> {
#Override
public Supplier<List<List<T>>> supplier() {
return () -> {
List<List<T>> list = new ArrayList<>();
list.add(new ArrayList<>());
return list;
};
}
#Override
public BiConsumer<List<List<T>>, T> accumulator() {
return (list, s) -> {
int size = list.size();
List<T> inner = list.get(size - 1);
int innerSize = inner.size();
if (innerSize > 0) {
T last = inner.get(inner.size() - 1);
if (s.equals(last)) {
inner.add(s);
} else {
List<T> newList = new ArrayList<>();
newList.add(s);
list.add(newList);
}
} else {
inner.add(s);
}
};
}
#Override
public BinaryOperator<List<List<T>>> combiner() {
return (left, right) -> {
List<T> lastLeft = left.get(left.size() - 1);
List<T> firstRight = right.get(0);
T leftElem = lastLeft.get(lastLeft.size() - 1);
T rightElem = firstRight.get(firstRight.size() - 1);
if (leftElem.equals(rightElem)) {
lastLeft.addAll(right.remove(0));
}
left.addAll(right);
return left;
};
}
#Override
public Set<Characteristics> characteristics() {
return EnumSet.of(Characteristics.IDENTITY_FINISH);
}
#Override
public Function<List<List<T>>, List<List<T>>> finisher() {
return Function.identity();
}
}
And then use it:
List<List<String>> result = items.stream().parallel().collect(new PackCollector<>());
System.out.println(result);

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();
}
}

Categories