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);
Related
Want to convert as per the method signature below.
Map<A, Set<B>> convert(Map<A, List<B>> original){
original.entrySet().stream().map(entry -> {
return new SimpleImmutableEntry(entry.getKey(), new HashSet<>(entry.getValue()));
}).collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
}
Its giving me compiling issue not sure why. All am doing is transforming entry to intermediate entry of Map.Entry<String, Set<String>> and than collecting.
A simplier definition skips intermediate map step as you can convert each Map.Entry<A, List<B>> into Map.Entry<A, Set<B>> inside the collect step:
Map<A, Set<B>> convert(Map<A, List<B>> original){
return original.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> new HashSet<>(e.getValue())));
}
You could prefix the method convert with static <A,B> to ensure that it applies to any types A and B.
static <A,B> Map<A, Set<B>> convert(Map<A, List<B>> original){
...
}
package com.student;
import java.util.*;
import java.util.stream.Collectors;
public class MapConversion {
static Map < String, Set < String >> convert(Map < String, List < String >> map1) {
return map1.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e - > new HashSet(e.getValue())));
}
public static void main(String[] args) {
List < String > list1 = Arrays.asList(new String[] {
"a",
"b"
});
Map < String, List < String >> map1 = new HashMap < > () {
{
put("strin1", Arrays.asList(new String[] {
"a",
"a"
}));
put("strin2", Arrays.asList(new String[] {
"d",
"d"
}));
put("strin3", Arrays.asList(new String[] {
"e",
"f"
}));
}
};
System.out.println(convert(map1));
}
}
.collect(Collectors.groupingBy(Point::getName, Collectors.summingInt(Point::getCount)));
I have a list of Point objects that I want to group by a certain key (the name field) and sum by the count field of that class. The code above does the job but returns a map of Point objects. However, I want a list of Point objects returned - not a map.
What is the cleanest way to do this with java 8 streams?
Example:
input = [pt("jack", 1), pt("jack", 1), pt("jack", 1)]
result = [pt("jack", 3)]
Thanks
You can use Collectors.toMap() with a merge function as parameter.
If you add a function to sum count fields:
public class Point {
//...
public static Point sum(Point p1, Point p2) {
return new Point(p1.getName(), p1.getCount()+p2.getCount());
}
}
Then you can use it in toMap():
List<Point> list = Collections.nCopies(10, new Point("jack", 1));
Collection<Point> output = list.stream()
.collect(Collectors.toMap(Point::getName, Function.identity(), Point::sum)) // results as Map<String, Point> {"jack", Point("jack",10)}
.values(); // to get the Point instances
System.out.println(output);
Output:
[Point [name=jack, count=10]]
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
public class Pointers {
private String name;
private int count;
public Pointers(String name, int count) {
this.name = name;
this.count = count;
}
public String getName() {
return name;
}
public int getCount() {
return count;
}
public void incrementCount(int amount) {
count += amount;
}
public boolean equals(Object obj) {
boolean equal = false;
if (obj instanceof Pointers) {
Pointers other = (Pointers) obj;
equal = name.equals(other.getName());
}
return equal;
}
public String toString() {
return name + count;
}
public static void main(String[] args) {
List<Pointers> list = List.of(new Pointers("Jack", 1),
new Pointers("Jack", 1),
new Pointers("Jack", 1));
Supplier<List<Pointers>> supplier = () -> new ArrayList<Pointers>();
BiConsumer<List<Pointers>, Pointers> accumulator = (l, p) -> {
if (l.contains(p)) {
Pointers elem = l.get(l.indexOf(p));
elem.incrementCount(p.getCount());
}
else {
l.add(p);
}
};
BiConsumer<List<Pointers>, List<Pointers>> combiner = (l1, l2) -> {
};
List<Pointers> lst = list.stream()
.collect(supplier, accumulator, combiner);
System.out.println(lst);
}
}
Actually, you were close. You can take the key (name) and value (point sum) and repackage it into a new Point object and return as a list. Note that by re-assiging to list, you destroy the original one which will of course be garbage collected. This approach does not require a modification of your current class.
list = list.stream()
.collect(Collectors.groupingBy(Point::getName,
Collectors.summingInt(Point::getCount)))
.entrySet().stream()
.map(e -> new Point(e.getKey(), e.getValue()))
.collect(Collectors.toList());
In this java assignment we have a for loop that reads through a text file we use in this program, and we are supposed to replace it with a stream. Here is part of the program and what we are supposed to replace:
import java.io.FileNotFoundException;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class FrequentWords {
public static void main(String[] args) throws FileNotFoundException {
String filename = "SophieSallyJack.txt";
if (args.length == 1) {
filename = args[0];
}
Map<String, Integer> wordFrequency = new TreeMap<>();
List<String> incoming = Utilities.readAFile(filename);
// TODO replace the following loop with a single statement using streams
// that populates wordFrequency
for (String word : incoming) {
word = word.toLowerCase();
if (!"".equals(word.trim())) {
Integer cnt = wordFrequency.get(word);
if (cnt == null) {
wordFrequency.put(word, 1);
} else {
int icnt = cnt + 1;
wordFrequency.put(word, icnt);
}
}
}
I have tried this and I can't seem to figure out anything else:
incoming.stream()
.collect(Collectors.toMap(word -> word, word -> 1, Integer::sum)).entrySet();
Here's what you can try:
wordFrequency = incoming.stream()
.map(String::toLowerCase).filter(word -> !word.trim().isEmpty())
.collect(Collectors.toMap
(word -> word, word -> 1, (a, b) -> a + b, TreeMap::new));
You missed the BinaryOperator that will merge values of the key already exists Collectors.toMap()
As an alternative, you can simply use :
wordFrequency = incoming.stream()
.map(String::toLowerCase) // only if you're planning to store in lowercase or else move this to filtering predicate
.filter(word -> !word.trim().isEmpty())
.collect(Collectors.toMap(Function.identity(),
word -> 1, Integer::sum));
considering that you're aware that entrySet() is a Set instead which cannot be assigned to a Map anyway as in the question.
private static Map<String,Integer> toMapFunction(Collection< ? extends String> collection){
return collection.stream().map(String::toLowerCase)
.filter(str -> !str.trim().isEmpty())
.collect(Collectors.toMap(Function.identity(), value -> 1, (oldValue, newValue) -> oldValue + newValue, TreeMap::new));
}
public static void main(String[] args) {
List<String> stringList = Arrays.asList("joy", "joy", "lobo", "lobo", "lobo", "joy", "joy", "joy", "david", "hibbs");
System.out.println(toMapFunction(stringList));
}
and this will be output of the program:
{david=1, hibbs=1, joy=5, lobo=3}
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 + '}';
}
}
}
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());
}