Collect pairs from a stream - java

I have a stream of objects like this:
"0", "1", "2", "3", "4", "5",
How can I transform it to stream of pairs :
{ new Pair("0", "1"), new Pair("2", "3"), new Pair("4", "5")}.
The stream size is unknown. I am reading data from a file that might be big. I have only iterator to collection and I transform this iterator to stream using spliterator.
I know that here is a answer for processing adjacent pairs with StreamEx :
Collect successive pairs from a stream
Can this be done in java or StreamEx ?
Thanks

It's not a natural fit but you can do
List input = ...
List<Pair> pairs = IntStream.range(0, input.size() / 2)
.map(i -> i * 2)
.mapToObj(i -> new Pair(input.get(i), input.get(i + 1)))
.collect(Collectors.toList());
To create Pairs as you go in a stream you need a stateful lambdas which should be generally avoided but can be done. Note: this will only works if the stream is single threaded. i.e. not parallel.
Stream<?> stream =
assert !stream.isParallel();
Object[] last = { null };
List<Pair> pairs = stream.map(a -> {
if (last[0] == null) {
last[0] = a;
return null;
} else {
Object t = last[0];
last[0] = null;
return new Pair(t, a);
}
}).filter(p -> p != null)
.collect(Collectors.toList());
assert last[0] == null; // to check for an even number input.

If you don't want to collect the elements
The title of the question says collect pairs from a stream, so I'd assume that you want to actually collect these, but you commented:
Your solution works, the problem is that it loads the data from file to PairList and then I may use stream from this collection to process pairs. I can't do it because the data might be too big to store in the memory.
so here's a way to do this without collecting the elements.
It's relatively straightforward to transform an Iterator<T> into an Iterator<List<T>>, and from that to transform a stream into a stream of pairs.
/**
* Returns an iterator over pairs of elements returned by the iterator.
*
* #param iterator the base iterator
* #return the paired iterator
*/
public static <T> Iterator<List<T>> paired(Iterator<T> iterator) {
return new Iterator<List<T>>() {
#Override
public boolean hasNext() {
return iterator.hasNext();
}
#Override
public List<T> next() {
T first = iterator.next();
if (iterator.hasNext()) {
return Arrays.asList(first, iterator.next());
} else {
return Arrays.asList(first);
}
}
};
}
/**
* Returns an stream of pairs of elements from a stream.
*
* #param stream the base stream
* #return the pair stream
*/
public static <T> Stream<List<T>> paired(Stream<T> stream) {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(paired(stream.iterator()), Spliterator.ORDERED),
false);
}
#Test
public void iteratorAndStreamsExample() {
List<String> strings = Arrays.asList("a", "b", "c", "d", "e", "f");
Iterator<List<String>> pairs = paired(strings.iterator());
while (pairs.hasNext()) {
System.out.println(pairs.next());
// [a, b]
// [c, d]
// [e, f]
}
paired(Stream.of(1, 2, 3, 4, 5, 6, 7, 8)).forEach(System.out::println);
// [1, 2]
// [3, 4]
// [5, 6]
// [7, 8]
}
If you want to collect the elements...
I'd do this by collecting into a list, and using an AbstractList to provide a view of the elements as pairs.
First, the PairList. This is a simple AbstractList wrapper around any list that has an even number of elements. (This could easily be adapted to handle odd length lists, once the desired behavior is specified.)
/**
* A view on a list of its elements as pairs.
*
* #param <T> the element type
*/
static class PairList<T> extends AbstractList<List<T>> {
private final List<T> elements;
/**
* Creates a new pair list.
*
* #param elements the elements
*
* #throws NullPointerException if elements is null
* #throws IllegalArgumentException if the length of elements is not even
*/
public PairList(List<T> elements) {
Objects.requireNonNull(elements, "elements must not be null");
this.elements = new ArrayList<>(elements);
if (this.elements.size() % 2 != 0) {
throw new IllegalArgumentException("number of elements must have even size");
}
}
#Override
public List<T> get(int index) {
return Arrays.asList(elements.get(index), elements.get(index + 1));
}
#Override
public int size() {
return elements.size() / 2;
}
}
Then we can define the collector that we need. This is essentially shorthand for collectingAndThen(toList(), PairList::new):
/**
* Returns a collector that collects to a pair list.
*
* #return the collector
*/
public static <E> Collector<E, ?, PairList<E>> toPairList() {
return Collectors.collectingAndThen(Collectors.toList(), PairList::new);
}
Note that it could be worthwhile defining a PairList constructor that doesn't defensively copy the list, for the use case that we know the backing list is freshly generated (as in this case). That's not really essential right now, though. But once we did that, this method would be collectingAndThen(toCollection(ArrayList::new), PairList::newNonDefensivelyCopiedPairList).
And now we can use it:
/**
* Creates a pair list with collectingAndThen, toList(), and PairList::new
*/
#Test
public void example() {
List<List<Integer>> intPairs = Stream.of(1, 2, 3, 4, 5, 6)
.collect(toPairList());
System.out.println(intPairs); // [[1, 2], [2, 3], [3, 4]]
List<List<String>> stringPairs = Stream.of("a", "b", "c", "d")
.collect(toPairList());
System.out.println(stringPairs); // [[a, b], [b, c]]
}
Here's a complete source file with a runnable example (as a JUnit test):
package ex;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Test;
public class PairCollectors {
/**
* A view on a list of its elements as pairs.
*
* #param <T> the element type
*/
static class PairList<T> extends AbstractList<List<T>> {
private final List<T> elements;
/**
* Creates a new pair list.
*
* #param elements the elements
*
* #throws NullPointerException if elements is null
* #throws IllegalArgumentException if the length of elements is not even
*/
public PairList(List<T> elements) {
Objects.requireNonNull(elements, "elements must not be null");
this.elements = new ArrayList<>(elements);
if (this.elements.size() % 2 != 0) {
throw new IllegalArgumentException("number of elements must have even size");
}
}
#Override
public List<T> get(int index) {
return Arrays.asList(elements.get(index), elements.get(index + 1));
}
#Override
public int size() {
return elements.size() / 2;
}
}
/**
* Returns a collector that collects to a pair list.
*
* #return the collector
*/
public static <E> Collector<E, ?, PairList<E>> toPairList() {
return Collectors.collectingAndThen(Collectors.toList(), PairList::new);
}
/**
* Creates a pair list with collectingAndThen, toList(), and PairList::new
*/
#Test
public void example() {
List<List<Integer>> intPairs = Stream.of(1, 2, 3, 4, 5, 6)
.collect(toPairList());
System.out.println(intPairs); // [[1, 2], [2, 3], [3, 4]]
List<List<String>> stringPairs = Stream.of("a", "b", "c", "d")
.collect(toPairList());
System.out.println(stringPairs); // [[a, b], [b, c]]
}
}

Assuming there is a Pair with left, right and getters and a constructor:
static class Paired<T> extends AbstractSpliterator<Pair<T>> {
private List<T> list = new ArrayList<>(2);
private final Iterator<T> iter;
public Paired(Iterator<T> iter) {
super(Long.MAX_VALUE, 0);
this.iter = iter;
}
#Override
public boolean tryAdvance(Consumer<? super Pair<T>> consumer) {
getBothIfPossible(iter);
if (list.size() == 2) {
consumer.accept(new Pair<>(list.remove(0), list.remove(0)));
return true;
}
return false;
}
private void getBothIfPossible(Iterator<T> iter) {
while (iter.hasNext() && list.size() < 2) {
list.add(iter.next());
}
}
}
Usage would be:
Iterator<Integer> iterator = List.of(1, 2, 3, 4, 5).iterator();
Paired<Integer> p = new Paired<>(iterator);
StreamSupport.stream(p, false)
.forEach(pair -> System.out.println(pair.getLeft() + " " + pair.getRight()));

I know I'm late to the party, but all of the answers seem to be really complicated or have a lot of GC overhead/short-lived objects (which is not a big deal with modern JVMs), but why not do it simply like this?
public class PairCollaterTest extends TestCase {
static class PairCollater<T> implements Function<T, Stream<Pair<T, T>>> {
T prev;
#Override
public Stream<Pair<T, T>> apply(T curr) {
if (prev == null) {
prev = curr;
return Stream.empty();
}
try {
return Stream.of(Pair.of(prev, curr));
} finally {
prev = null;
}
}
}
public void testPairCollater() {
Stream.of("0", "1", "2", "3", "4", "5").sequential().flatMap(new PairCollater<>()).forEach(System.out::println);
}
}
Prints:
(0,1)
(2,3)
(4,5)

Just replace IntStream.range(1, 101) with your stream (you don't need to know your stream's size) -
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
public class TestClass {
public static void main(String[] args) {
final Pair pair = new Pair();
final List<Pair> pairList = new ArrayList<>();
IntStream.range(1, 101)
.map(i -> {
if (pair.a == null) {
pair.a = i;
return 0;
} else {
pair.b = i;
return 1;
}
})
.filter(i -> i == 1)
.forEach(i -> {
pairList.add(new Pair(pair));
pair.reset();
});
pairList.stream().forEach(p -> System.out.print(p + " "));
}
static class Pair {
public Object a;
public Object b;
public Pair() {
}
public Pair(Pair orig) {
this.a = orig.a;
this.b = orig.b;
}
void reset() {
a = null;
b = null;
}
#Override
public String toString() {
return "{" + a + "," + b + '}';
}
}
}

Related

Combine similar elements in a Java Stream

How to combine consecutive similar objects that are in a Java Stream?
Till now I could not find a good solution, so I hope you can help.
Suppose we have a class like the following:
class Someclass {
String type;
int count;
}
When having a Stream { "a", 1 }, { "a", 2}, { "b", 4}, { "b", "5" } and that needs to be processed as { "a", 3 }, { "b", 9 }.
Combining means of course, adding the counts for the objects with the same type.
How to do?
You can use Collectors.toMap to collect the stream into the desired Map.
Demo:
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
class MyType {
String type;
int count;
public MyType(String type, int count) {
this.type = type;
this.count = count;
}
public String getType() {
return type;
}
public int getCount() {
return count;
}
}
public class Main {
public static void main(String[] args) {
List<MyType> list = List.of(new MyType("a", 1),
new MyType("a", 2),
new MyType("b", 4),
new MyType("b", 5));
Map<String, Integer> map = list.stream()
.collect(Collectors.toMap(MyType::getType, MyType::getCount, Integer::sum));
System.out.println(map);
}
}
Output:
{a=3, b=9}
Assume you had a list of maps as in your example.
first stream the list
then flatten all the entrySets into a single stream.
the collect each key/value pair
use the (a,b)->a+b merge function to add values for duplicate keys.
List<Map<String, Integer>> maps = List.of(Map.of("a", 1),
Map.of("a", 2), Map.of("b", 4), Map.of("b", 5));
Map<String, Integer> result =
maps.stream().flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Entry::getKey,
Entry::getValue, (a, b) -> a + b));
System.out.println(result);
Prints
{a=3, b=9}
If you had an array of objects, you could do it as follows.
The compute method puts the value there if it isn't present, or adds to the existing one if it is present.
Object[] values = {"a",1,"b",4,"a",2,"b",5};
Map<String,Integer> map1 = new HashMap<>();
for (int i = 0; i < values.length-1; i+=2) {
Integer val = (Integer)values[i+1];
map1.compute((String)values[i], (k,v)->v == null ? val : v +val);
}
System.out.println(map1);

Flatten nested arrays in java

I want to flatten nested arrays like:
[[[1],2],[3]],4] -> [1,2,3,4]
manually in java I can't find a clue ! :S
I have tried a manual java script guide but it doesn't get a solution
public static void main(String[] args) {
Object arr[] = { 1, 2, new Object[] { 4, new int[] { 5, 6 }, 7 }, 10 };
String deepToString = Arrays.deepToString(arr);
String replace = deepToString.replace("[", "").replace("]", "");
String array[] = replace.split(",");
int temp[] = new int[array.length];
for (int i = 0; i < array.length; i++) {
temp[i] = Integer.parseInt(array[i].trim());
}
System.out.println(Arrays.toString(temp));
}
The Stream API offers a compact and flexible solution. Using the method
private static Stream<Object> flatten(Object[] array) {
return Arrays.stream(array)
.flatMap(o -> o instanceof Object[] a? flatten(a): Stream.of(o));
}
or prior to JDK 16
private static Stream<Object> flatten(Object[] array) {
return Arrays.stream(array)
.flatMap(o -> o instanceof Object[]? flatten((Object[])o): Stream.of(o));
}
you can perform the operation as
Object[] array = { 1, 2, new Object[]{ 3, 4, new Object[]{ 5 }, 6, 7 }, 8, 9, 10 };
System.out.println("original: "+Arrays.deepToString(array));
Object[] flat = flatten(array).toArray();
System.out.println("flat: "+Arrays.toString(flat));
or when you assume the leaf objects to be of a specific type:
int[] flatInt = flatten(array).mapToInt(Integer.class::cast).toArray();
System.out.println("flat int: "+Arrays.toString(flatInt));
I created a class to solve this using Java, the code is also shown below.
Solution:
package com.conorgriffin.flattener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Flattens an array of arbitrarily nested arrays of integers into a flat array of integers.
* <p/>
* #author conorgriffin
*/
public class IntegerArrayFlattener {
/**
* Flatten an array of arbitrarily nested arrays of integers into a flat array of integers. e.g. [[1,2,[3]],4] -> [1,2,3,4].
*
* #param inputArray an array of Integers or nested arrays of Integers
* #return flattened array of Integers or null if input is null
* #throws IllegalArgumentException
*/
public static Integer[] flatten(Object[] inputArray) throws IllegalArgumentException {
if (inputArray == null) return null;
List<Integer> flatList = new ArrayList<Integer>();
for (Object element : inputArray) {
if (element instanceof Integer) {
flatList.add((Integer) element);
} else if (element instanceof Object[]) {
flatList.addAll(Arrays.asList(flatten((Object[]) element)));
} else {
throw new IllegalArgumentException("Input must be an array of Integers or nested arrays of Integers");
}
}
return flatList.toArray(new Integer[flatList.size()]);
}
}
Unit Tests:
package com.conorgriffin.flattener;
import org.junit.Assert;
import org.junit.Test;
/**
* Tests IntegerArrayFlattener
*/
public class IntegerArrayFlattenerTest {
Integer[] expectedArray = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
#Test
public void testNullReturnsNull() throws IllegalArgumentException {
Assert.assertNull(
"Testing a null argument",
IntegerArrayFlattener.flatten(null)
);
}
#Test
public void testEmptyArray() throws IllegalArgumentException {
Assert.assertArrayEquals(
"Testing an empty array",
new Integer[]{},
IntegerArrayFlattener.flatten(new Object[]{})
);
}
#Test
public void testFlatArray() throws IllegalArgumentException {
Assert.assertArrayEquals(
"Testing a flat array",
expectedArray,
IntegerArrayFlattener.flatten(new Object[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
);
}
#Test
public void testNestedArray() throws IllegalArgumentException {
Assert.assertArrayEquals(
"Testing nested array",
expectedArray,
IntegerArrayFlattener.flatten(new Object[]{1, 2, 3, 4, new Object[]{5, 6, 7, 8}, 9, 10})
);
}
#Test
public void testMultipleNestedArrays() throws IllegalArgumentException {
Assert.assertArrayEquals(
"Testing multiple nested arrays",
expectedArray,
IntegerArrayFlattener.flatten(new Object[]{1, 2, new Object[]{3, 4, new Object[]{5}, 6, 7}, 8, 9, 10})
);
}
#Test(expected = IllegalArgumentException.class)
public void throwsExceptionForObjectInArray() throws IllegalArgumentException {
IntegerArrayFlattener.flatten(
new Object[]{new Object()}
);
}
#Test(expected = IllegalArgumentException.class)
public void throwsExceptionForObjectInNestedArray() throws IllegalArgumentException {
IntegerArrayFlattener.flatten(
new Object[]{1, 2, new Object[]{3, new Object()}}
);
}
#Test(expected = IllegalArgumentException.class)
public void throwsExceptionForNullInArray() throws IllegalArgumentException {
IntegerArrayFlattener.flatten(
new Object[]{null}
);
}
#Test(expected = IllegalArgumentException.class)
public void throwsExceptionForNullInNestedArray() throws IllegalArgumentException {
IntegerArrayFlattener.flatten(
new Object[]{1, 2, new Object[]{3, null}}
);
}
}
If it's a primitive array with only two levels, you could do:
Arrays.stream(array)
.flatMapToInt(o -> Arrays.stream(o))
.toArray()
to get the corresponding boxed array (which you can unbox if necessary)
That's the way I would solve it.
Don't know which kind of efficiency you are looking for. But yeah. that does the job in JavaScript.
arr.toString().split(',').filter((item) => item).map((item) => Number(item))
A probably more efficient way to do this would be to use reduce and concat method from arr and recursion.
function flattenDeep(arr1) {
return arr1.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), []);
}
This is how I solved this problem in Java:
public class ArrayUtil {
/**
* Utility to flatten an array of arbitrarily nested arrays of integers into
* a flat array of integers. e.g. [[1,2,[3]],4] -> [1,2,3,4]
* #param inputList
*/
public static Integer[] flattenArray(ArrayList<Object> inputList) {
ArrayList<Integer> flatten = new ArrayList<Integer>();
if (inputList.size() <= 0) {
return new Integer[0]; // if the inputList is empty, return an empty Integer[] array.
}
for (Object obj : inputList) {
recursiveFlatten(flatten, obj); // otherwise we can recursively flatten the input list.
}
Integer [] flatArray = new Integer[flatten.size()];
return flatArray = flatten.toArray(flatArray);
}
/**
* Recursively flatten a nested array.
* #param flatten
* #param o
*/
private static void recursiveFlatten(ArrayList<Integer> flatten, Object o){
if(isInteger(o)){ // if the object is of type Integer, just add it into the list.
flatten.add((Integer)o);
} else if(o instanceof ArrayList){ // otherwise, we need to call to recursively flatten the array
for(Object obj : (ArrayList<Object>) o){ // for the case where there are deeply nested arrays.
recursiveFlatten(flatten, obj);
}
}
}
/**
* Return true if object belongs to Integer class,
* else return false.
* #param obj
* #return
*/
private static boolean isInteger(Object obj) {
return obj instanceof Integer;
}
}
It could be flattened by iterative approach.
static class ArrayHolder implements Iterator<Object> {
private final Object[] elements;
private int index = -1;
public ArrayHolder(final Object[] elements) {
this.elements = elements;
}
#Override
public boolean hasNext() {
return Objects.nonNull(elements) && ++index < elements.length;
}
#Override
public Object next() {
if (Objects.isNull(elements) || (index == -1 || index > elements.length))
throw new NoSuchElementException();
return elements[index];
}
}
private static boolean hasNext(ArrayHolder current) {
return Objects.nonNull(current) && current.hasNext();
}
private void flat(Object[] elements, List<Object> flattened) {
Deque<ArrayHolder> stack = new LinkedList<>();
stack.push(new ArrayHolder(elements));
ArrayHolder current = null;
while (hasNext(current)
|| (!stack.isEmpty() && hasNext(current = stack.pop()))) {
Object element = current.next();
if (Objects.nonNull(element) && element.getClass().isArray()) {
Object[] e = (Object[]) element;
stack.push(current);
stack.push(new ArrayHolder(e));
current = null;
} else {
flattened.add(element);
}
}
}
You can find the full source here
You can use recursion to solve this problem.
private void flat(Object[] elements, List<Object> flattened) {
for (Object element : elements)
{
if (Objects.nonNull(element) && element.getClass().isArray())
{
flat((Object[])element, flattened);
}
else
{
flattened.add(element);
}
}
}
Here is the link for recursion.
The recursive call approach will work for this case:
private static void recursiveCall(Object[] array) {
for (int i=0;i<array.length;i++) {
if (array[i] instanceof Object[]) {
recursiveCall((Object[]) array[i]);
}else {
System.out.println(array[i]);
}
}
}
package com.app;
import java.util.Arrays;
public class Test2 {
public static void main(String[] args) {
Object arr[] = { 1, 2, new Object[] { 4, new int[] { 5, 6 }, 7 }, 10 };
String deepToString = Arrays.deepToString(arr);
String replace = deepToString.replace("[", "").replace("]", "");
String array[] = replace.split(",");
int temp[] = new int[array.length];
for (int i = 0; i < array.length; i++) {
temp[i] = Integer.parseInt(array[i].trim());
}
System.out.println(Arrays.toString(temp));
}
}
you can try this code:
String a = "[[[1],2],[3]],4] ";
a= a.replaceAll("[(\\[|\\])]", "");
String[] b = a.split(",");

Double a stream

I want to double a Stream (no DoubleStream). Meaning I start with a stream and want to get a new stream where each element of the old stream is streamed twice. So 1,2,3,4,4,5 gives us 1,1,2,2,3,3,4,4,4,4,5,5. Is there such a stream operation?
Create an inner stream which will contain current element two times and flatMap this stream.
stream.flatMap(e -> Stream.of(e,e))
If you want to multiply the number of elements by n you can create an utility method like this one:
public static <T> Stream<T> multiplyElements(Stream<T> in, int n) {
return in.flatMap(e -> IntStream.range(0, n).mapToObj(i -> e));
// we can also use IntStream.rangeClosed(1, n)
// but I am used to iterating from 0 to n (where n is excluded)
}
(but try to use a better name for this method, since the current one may be ambiguous)
Usage example:
multiplyElements(Stream.of(1,2), 3).forEach(System.out::println);
Output:
1
1
1
2
2
2
You can create a stream of 2 elements for each original element and flatMap it:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 4, 5);
List<Integer> doubled = list.stream().flatMap(i -> Stream.of(i, i)).collect(toList());
Here's a simple example of what biziclop has described in the comments.
static <E> Collection<E> multiply(Collection<E> source, int count) {
return new AbstractCollection<E>() {
#Override
public int size() {
return count * source.size();
}
#Override
public Iterator<E> iterator() {
return new Iterator<E>() {
final Iterator<E> it = source.iterator();
E next;
int i = 0;
#Override
public boolean hasNext() {
return i < size();
}
#Override
public E next() {
if (hasNext()) {
if ((i % count) == 0) {
next = it.next();
}
++i;
return next;
} else {
throw new NoSuchElementException();
}
}
};
}
};
}
(Working example on Ideone.)
CW'd since it wasn't my idea and the flatMap suggestions more directly answer the question.

Split list into multiple lists with fixed number of elements in java 8

I want to something which is similar to the scala grouped function. Basically, pick 2 elements at a time and process them. Here is a reference for the same :
Split list into multiple lists with fixed number of elements
Lambdas do provide things like groupingBy and partitioningBy but none of them seem to do the same as the grouped function in Scala. Any pointers would be appreciated.
You can use Guava library.
List<Integer> bigList = ...
List<List<Integer>> smallerLists = Lists.partition(bigList, 10);
It sounds like a problem that is better handled like a low-level Stream operation just like the ops provided by the Stream API itself. A (relative) simple solution may look like:
public static <T> Stream<List<T>> chunked(Stream<T> s, int chunkSize) {
if(chunkSize<1) throw new IllegalArgumentException("chunkSize=="+chunkSize);
if(chunkSize==1) return s.map(Collections::singletonList);
Spliterator<T> src=s.spliterator();
long size=src.estimateSize();
if(size!=Long.MAX_VALUE) size=(size+chunkSize-1)/chunkSize;
int ch=src.characteristics();
ch&=Spliterator.SIZED|Spliterator.ORDERED|Spliterator.DISTINCT|Spliterator.IMMUTABLE;
ch|=Spliterator.NONNULL;
return StreamSupport.stream(new Spliterators.AbstractSpliterator<List<T>>(size, ch)
{
private List<T> current;
#Override
public boolean tryAdvance(Consumer<? super List<T>> action) {
if(current==null) current=new ArrayList<>(chunkSize);
while(current.size()<chunkSize && src.tryAdvance(current::add));
if(!current.isEmpty()) {
action.accept(current);
current=null;
return true;
}
return false;
}
}, s.isParallel());
}
Simple test:
chunked(Stream.of(1, 2, 3, 4, 5, 6, 7), 3)
.parallel().forEachOrdered(System.out::println);
The advantage is that you do not need a full collection of all items for subsequent stream processing, e.g.
chunked(
IntStream.range(0, 1000).mapToObj(i -> {
System.out.println("processing item "+i);
return i;
}), 2).anyMatch(list->list.toString().equals("[6, 7]")));
will print:
processing item 0
processing item 1
processing item 2
processing item 3
processing item 4
processing item 5
processing item 6
processing item 7
true
rather than processing a thousand items of IntStream.range(0, 1000). This also enables using infinite source Streams:
chunked(Stream.iterate(0, i->i+1), 2).anyMatch(list->list.toString().equals("[6, 7]")));
If you are interested in a fully materialized collection rather than applying subsequent Stream operations, you may simply use the following operation:
List<Integer> list=Arrays.asList(1, 2, 3, 4, 5, 6, 7);
int listSize=list.size(), chunkSize=2;
List<List<Integer>> list2=
IntStream.range(0, (listSize-1)/chunkSize+1)
.mapToObj(i->list.subList(i*=chunkSize,
listSize-chunkSize>=i? i+chunkSize: listSize))
.collect(Collectors.toList());
You can create your own collector. Something like this:
class GroupingCollector<T> implements Collector<T, List<List<T>>, List<List<T>>> {
private final int elementCountInGroup;
public GroupingCollector(int elementCountInGroup) {
this.elementCountInGroup = elementCountInGroup;
}
#Override
public Supplier<List<List<T>>> supplier() {
return ArrayList::new;
}
#Override
public BiConsumer<List<List<T>>, T> accumulator() {
return (lists, integer) -> {
if (!lists.isEmpty()) {
List<T> integers = lists.get(lists.size() - 1);
if (integers.size() < elementCountInGroup) {
integers.add(integer);
return;
}
}
List<T> list = new ArrayList<>();
list.add(integer);
lists.add(list);
};
}
#Override
public BinaryOperator<List<List<T>>> combiner() {
return (lists, lists2) -> {
List<List<T>> r = new ArrayList<>();
r.addAll(lists);
r.addAll(lists2);
return r;
};
}
#Override
public Function<List<List<T>>, List<List<T>>> finisher() {
return lists -> lists;
}
#Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
}
And then you can use it in a way like this:
List<List<Integer>> collect = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).collect(new GroupingCollector<>(3));
System.out.println(collect);
Will print:
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
A recursive solution to transform the list to a list-of-lists would also be possible
int chunkSize = 2;
private <T> List<List<T>> process(List<T> list) {
if (list.size() > chunkSize) {
List<T> chunk = list.subList(0, chunkSize);
List<T> rest = list.subList(chunkSize, list.size());
List<List<T>> lists = process(rest);
return concat(chunk, lists);
} else {
ArrayList<List<T>> retVal = new ArrayList<>();
retVal.add(list);
return retVal;
}
}
private <T> List<List<T>> concat(List<T> chunk, List<List<T>> rest) {
rest.add(0, chunk);
return rest;
}
You could write your own collector finisher, similar to
final List<String> strings = Arrays.asList("Hello", "World", "I", "Am", "You");
final int size = 3;
final List<List<String>> stringLists = strings.stream()
.collect(Collectors.collectingAndThen(Collectors.toList(), new Function<List<String>, List<List<String>>>() {
#Override
public List<List<String>> apply(List<String> strings) {
final List<List<String>> result = new ArrayList<>();
int counter = 0;
List<String> stringsToAdd = new ArrayList<>();
for (final String string : strings) {
if (counter == 0) {
result.add(stringsToAdd);
} else {
if (counter == size) {
stringsToAdd = new ArrayList<>();
result.add(stringsToAdd);
counter = 0;
}
}
++counter;
stringsToAdd.add(string);
}
return result;
}
}));
System.out.println("stringLists = " + stringLists); // stringLists = [[Hello, World, I], [Am, You]]
A simple version with java 8 streams api:
static <T> List<List<T>> partition(List<T> list, Integer partitionSize) {
int numberOfLists = BigDecimal.valueOf(list.size())
.divide(BigDecimal.valueOf(partitionSize), 0, CEILING)
.intValue();
return IntStream.range(0, numberOfLists)
.mapToObj(it -> list.subList(it * partitionSize, Math.min((it+1) * partitionSize, list.size())))
.collect(Collectors.toList());
}

How to get Cartesian product from multiple lists?

Say I have several List<T>s, I will put them into another list or other collections, so I don't know how many list<T> I have until I call List<List<T>>.size()
Take below List<Integer> as an example:
list1=[1,2]
list2=[3,4]
list3=[5,6]
....
listn=[2*n-1,2n];
How can I get the result of list1*list2*list3*...listn as a Cartesian product?
For example:
list1*list2*list3
should be:
[1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5],[2,4,6]
You can use recursion to achieve it, your base case of recursion is when input is empty then return empty list, else process the remaining elements. E.g.
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
public class CartesianProduct {
public static <T> List<List<T>> calculate(List<List<T>> input) {
List<List<T>> res = new ArrayList<>();
if (input.isEmpty()) { // if no more elements to process
res.add(new ArrayList<>()); // then add empty list and return
return res;
} else {
// we need to calculate the cartesian product
// of input and store it in res variable
process(input, res);
}
return res; // method completes , return result
}
private static <T> void process(List<List<T>> lists, List<List<T>> res) {
//take first element of the list
List<T> head = lists.get(0);
//invoke calculate on remaining element, here is recursion
List<List<T>> tail = calculate(lists.subList(1, lists.size()));
for (T h : head) { // for each head
for (List<T> t : tail) { //iterate over the tail
List<T> tmp = new ArrayList<>(t.size());
tmp.add(h); // add the head
tmp.addAll(t); // and current tail element
res.add(tmp);
}
}
}
public static void main(String[] args) {
//we invoke the calculate method
System.out.println(calculate(Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4),
Arrays.asList(5, 6))));
}
}
Output
[[1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5],[2,4,6]]
Thanks to #sol4me 's answer using tail recursion, here is another version which is not using tail recursion but I think is easier to understand.
public class CartesianProduct {
public static <T> List<List<T>> calculate(List<List<T>> input) {
List<List<T>> result = new ArrayList<List<T>>();
if (input.isEmpty()) { // If input an empty list
// add empty list and return
result.add(new ArrayList<T>());
return result;
} else {
// get the first list as a head
List<T> head = input.get(0);
// recursion to calculate a tail list
List<List<T>> tail = calculate(input.subList(1, input.size()));
// we merge every head element with every tail list.
for (T h : head) {
for (List<T> t : tail) {
List<T> resultElement = new ArrayList<T>();
resultElement.add(h);
resultElement.addAll(t);
result.add(resultElement);
}
}
}
return result;
}
public static void main(String[] args) {
List<List<Integer>> bigList = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4),
Arrays.asList(5, 6),
Arrays.asList(7, 8));
System.out.println(calculate(bigList));
}
}
The map-and-reduce approach using nested loops
Prepare a list of lists List<List<T>> populated with a single empty value. This list is used further as a storage of intermediate results and as a final result.
Sequentially append the data from incoming lists List<List<T>> to the intermediate result and obtain the final result. Schematically, this iterative process looks as follows:
result0: [[]]
list1: [1,2]
-------
result1: [[1],[2]]
list2: [3,4]
-------
result2: [[1,3],[1,4],[2,3],[2,4]]
list3: [5,6]
-------
result3: [[1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5],[2,4,6]]
Try it online!
/**
* #param lists an arbitrary number of lists
* #param <T> the type of the elements
* #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();
// Cartesian product, intermediate result
List<List<T>> cp = Collections.singletonList(Collections.emptyList());
// iterate through incoming lists
for (List<T> list : lists) {
// non-null and non-empty lists
if (list == null || list.size() == 0) continue;
// intermediate result for next iteration
List<List<T>> next = new ArrayList<>();
// rows of current intermediate result
for (List<T> row : cp) {
// elements of current list
for (T el : list) {
// new row for next intermediate result
List<T> nRow = new ArrayList<>(row);
nRow.add(el);
next.add(nRow);
}
}
// pass to next iteration
cp = next;
}
// Cartesian product, final result
return cp;
}
public static void main(String[] args) {
List<List<Integer>> lists = prepareLists(3);
List<List<Integer>> cp = cartesianProduct(lists);
// output without spaces
System.out.println(lists.toString().replace(" ", ""));
System.out.println(cp.toString().replace(" ", ""));
}
// supplementary method, prepares lists for multiplication
public static List<List<Integer>> prepareLists(int n) {
List<List<Integer>> lists = new ArrayList<>(n);
for (int i = 1; i <= n; i++)
lists.add(Arrays.asList(i * 2 - 1, i * 2));
return lists;
}
Output:
[[1,2],[3,4],[5,6]]
[[1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5],[2,4,6]]
See also: Generate all combinations from multiple lists

Categories

Resources