Exception In Performing the Reduce Operation in Java Stream [duplicate] - java

This question already has answers here:
UnsupportedOperationException when trying to remove from the list returned by Array.asList
(6 answers)
Closed 3 years ago.
I am new To Java 8 and in below example I have created a Map having keyvalue as String and value as ArrayList of integer.
Map<String,List<Integer>> mapLstInteger=new HashMap<String,List<Integer>>() {
{
put("A",Arrays.asList(1,2,3));
put("B",Arrays.asList(4,5,6));
put("C",Arrays.asList(7,8,9));
}
};
I wrote below code to perform the sum for arrayList elements against every key and was trying to store the sum value in seperate ArrayList.
List<Integer> sumLst=mapLstInteger.entrySet().stream().map(e->e.getValue())
.reduce((inputLst, outputLst)->{
int sum=0;
for(int count=0;count<inputLst.size();count++)
{
sum=sum+inputLst.get(count);
}
outputLst.add(sum);
return outputLst;
}).get();
When I am trying to execute the below code I am getting below exception.
Exception in thread "main" java.lang.UnsupportedOperationException at
java.util.AbstractList.add(AbstractList.java:148) at
java.util.AbstractList.add(AbstractList.java:108) at
com.calculation.sum.client.Client.lambda$1(Client.java:43) at
java.util.stream.ReduceOps$2ReducingSink.accept(ReduceOps.java:123)
at
java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at
java.util.HashMap$EntrySpliterator.forEachRemaining(HashMap.java:1696)
at
java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at
java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at
java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at
java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at
java.util.stream.ReferencePipeline.reduce(ReferencePipeline.java:479)
at com.calculation.sum.client.Client.main(Client.java:37)
Can anybody please let me know what I did wrong in above code>

First you are using Arrays::asList which is documented as Returns a fixed-size list backed by the specified array, I think fixed size should tell you what you are doing wrong.
Than you are using an anti-pattern of creating a HashMap in place - by creating an anonymous inner class that extends HashMap, via that Map<String,List<Integer>> mapLstInteger=new HashMap<String,List<Integer>>().....
Than, you are violating the specification of reduce, which is supposed to return a new Object all the time, but you always put into outputLst.
Than, you are creating a Map when all you care about is its values - create a List<List<Integer>> in such a case.
Even your sentence I wrote below code to perform the sum for arrayList elements against every key is not correct according to your code. I would just make up my mind on the actual thing I want to achieve and then try to do it if I were you.

That happens because you're using the original AbstractList produced by Arrays.asList.That List<T> abstract implementation does not allow adding or removing elements.
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
But anyway, back at your problem. You can obtain what you want also via a custom Collector, where you can supply your custom List<T> implementation, be it an ArrayList, LinkedList, or whatever you feel like is better.
mapLstInteger.values()
.stream()
.collect(Collector.of(
() -> new ArrayList<>(), // Supplier
(output, toSumList) -> { // Accumulator
output.add(toSumList.stream()
.mapToInt(Integer::intValue)
.sum());
},
// The Combiner implementation will be called
// in case of a "parallel" Stream.
// No need to worry about it here.
// But in case, we would need to merge the partial results
(output, partial) -> {
output.addAll(partial);
return output;
}
));
A more succinct version is
mapLstInteger.values()
.stream()
.map(l -> l.stream().mapToInt(Integer::intValue).sum())
.collect(Collectors.toCollection(ArrayList::new));
That will correctly output [6, 15, 24]

You should be doing the following:
mapLstInteger.values().stream()
.flatMapToInt(list -> list.stream()
.filter(Objects::nonNull)
.mapToInt(Integer::intValue)).sum();
Added the filter to ensure you get no null pointers in the case of null Integers. As a general rule if you are forced to use a conventional loop inside of a stream you are probably doing something wrong. By mutating the list of ints to an int value we can easily sum as you see above.
Initially misunderstood the question thinking you wanted the overall sum alas here is an updated solution for the actual problem:
mapLstInteger.values().stream()
.map(list -> list.stream()
.filter(Objects::nonNull)
.mapToInt(Integer::intValue).sum())
.collect(Collectors.toList());

Related

How do I simultaneously iterate through a list and a set while pairing them in another map (using stream)

What i've tried is creating an iterator for the list and using stream on the set as such
Set //some object which has a getId method
Iterator<String> iterator = list.iterator();
someSet.stream()
.map(Collectors.toMap(e -> e.getId(), e -> iterator.next() );
The Stream API is designed to work and iterate through one and only one collection and no more.
If you want to achieve such iteration called "zipping", as mentioned in the another answer, you have to iterate the indices. Since Set is not ordered, you have to use List instead and know the order is not predictable.
However, the usage of Stream API should be fine here:
Set<MyObject> set = ... // MyObject with getId method
List<MyObject> listFromSet = new ArrayList<>(set);
List<MyObject> list = ... // existing list
IntStream.range(0, Math.min(list.size(), listFromSet.size()))
.mapToObj(index -> new AbstractMap.SimpleEntry<>(
listFromSet.get(index).getId(), // key
list.get(index)) // value
)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue)); // to Map
Few notes:
To know the highest number you can iterate through, you need ti find a lower from the sizes of the iterables: Math.min(list.size(), listFromSet.size()).
map(Collector.toMap(...)) doesn't convert a Stream to Map but is not a valid construct, moreover the method map is not a terminal operation. Use .collect(Collectors.toMap(...)) instead which is.
Not all the keys from set might be used, not all the values from list might be used, there is no guaranteed order of the keys and the matching key-value will be random.
If I were to implement this, I'd definetly go for the simple for-loop iteration over the Streams.
I think, what you wat to achieve is called "zip" in fuctional programming. This would be in Java to make a new stream from two existing streams by combining each of two corresponding elements of the given streams.
Look at this question to see how to do it:
Zipping streams using JDK8 with lambda (java.util.stream.Streams.zip)

What's the difference between list interface sort method and stream interface sorted method?

I'm interested in sorting an list of object based on date attribute in that object. I can either use list sort method.
list.sort( (a, b) -> a.getDate().compareTo(b.getDate()) );
Or I can use stream sorted method
List<E> l = list.stream()
.sorted( (a, b) -> a.getDate().compareTo(b.getDate()))
.collect(Collectors.toList());
Out of both above option which should we use and why?
I know the former one will update my original list and later one will not update the original but instead give me a fresh new list object.
So, I don't care my original list is getting updated or not. So which one is good option and why?
If you only need to sort your List, and don't need any other stream operations (such as filtering, mapping, etc...), there's no point in adding the overhead of creating a Stream and then creating a new List. It would be more efficient to just sort the original List.
If you wish to known which is best, your best option is to benchmark it: you may reuse my answer JMH test.
It should be noted that:
List::sort use Arrays::sort. It create an array before sorting. It does not exists for other Collection.
Stream::sorted is done as state full intermediate operation. This means the Stream need to remember its state.
Without benchmarking, I'd say that:
You should use collection.sort(). It is easier to read: collection.stream().sorted().collect(toList()) is way to long to read and unless you format your code well, you might have an headache (I exaggerate) before understanding that this line is simply sorting.
sort() on a Stream should be called:
if you filter many elements making the Stream effectively smaller in size than the collection (sorting N items then filtering N items is not the same than filtering N items then sorting K items with K <= N).
if you have a map transformation after the sort and you loose a way to sort using the original key.
If you use your stream with other intermediate operation, then sort might be required / useful:
collection.stream() // Stream<U> #0
.filter(...) // Stream<U> #1
.sorted() // Stream<U> #2
.map(...) // Stream<V> #3
.collect(toList()) // List<V> sorted by U.
;
In that example, the filter apply before the sort: the stream #1 is smaller than #0, so the cost of sorting with stream might be less than Collections.sort().
If all that you do is simply filtering, you may also use a TreeSet or a collectingAndThen operation:
collection.stream() // Stream<U> #0
.filter(...) // Stream<U> #1
.collect(toCollection(TreeSet::new))
;
Or:
collection.stream() // Stream<U>
.filter(...) // Stream<U>
.collect(collectingAndThen(toList(), list -> {
list.sort();
return list;
})); // List<V>
Streams have some overheads because it creates many new objects like a concrete Stream, a Collector, and a new List. So if you just want to sort a list and doesn't care about whether the original gets changed or not, use List.sort.
There is also Collections.sort, which is an older API. The difference between it and List.sort can be found here.
Stream.sorted is useful when you are doing other stream operations alongside sorting.
Your code can also be rewritten with Comparator:
list.sort(Comparator.comparing(YourClass::getDate)));
First one would be better in term of performance. In the first one, the sort method just compares the elements of the list and orders them. The second one will create a stream from your list, sort it and create a new list from that stream.
In your case, since you can update the first list, the first approach is the better, both in term of performance and memory consumption. The second one is convenient if you need to and with a stream, or if you have a stream and want to end up with a sorted list.
You use the first method
list.sort((a, b) -> a.getDate().compareTo(b.getDate()));
it's much faster than the second one and it didn't create a new intermediate object. You could use the second method when you want to do some additional stream operations (e.g. filtering, map).

Using Java 8 Stream Reduce to return List after performing operation on each element using previous elements values

I'm new to Streams and Reduce so I'm trying it out and have hit a problem:
I have a list of counters which have a start counter and end counter. The startcounter of an item is always the endcounter of the previous. I have a list of these counters listItems which I want to loop through efficiently, filter out inactive records and then reduce the list into a new List where all the StartCounters are set. I have the following code:
List<CounterChain> active = listItems.stream()
.filter(e -> e.getCounterStatus() == CounterStatus.ACTIVE)
.reduce(new ArrayList<CounterChain>(), (a,b) -> { b.setStartCounter(a.getEndCounter()); return b; });
But it doesn't really work and I'm kind of stuck, can anyone give me a few suggestions to help me get this working? Or is there an equally efficient better way to do this? Thanks!
A Reduction reduces all elements to a single value. Using a reduction function of the (a,b) -> b form will reduce all elements to the last one, so it’s not appropriate when you want to get a List containing all (matching) elements.
Besides that, you are performing a modification of the input value, which is violating the contract of that operation. Note further, that the function is required to be associative, i.e. it shouldn’t matter whether the stream will perform f(f(e₁,e₂),e₃)) or f(e₁,f(e₂,e₃)) when processing three subsequent stream elements with your reduction function.
Or, to put it in one line, you are not using the right tool for the job.
The cleanest solution is not to mix these unrelated operations:
List<CounterChain> active = listItems.stream()
.filter(e -> e.getCounterStatus() == CounterStatus.ACTIVE)
.collect(Collectors.toList());
for(int ix=1, num=active.size(); ix<num; ix++)
active.get(ix).setStartCounter(active.get(ix-1).getEndCounter());
The second loop could also be implemented using forEach, but it would require an inner class due to its stateful nature:
active.forEach(new Consumer<CounterChain>() {
CounterChain last;
public void accept(CounterChain next) {
if(last!=null) next.setStartCounter(last.getEndCounter());
last = next;
}
});
Or, using an index based stream:
IntStream.range(1, active.size())
.forEach(ix -> active.get(ix).setStartCounter(active.get(ix-1).getEndCounter()));
But neither has much advantage over a plain for loop.
although the solution with plain for loop provided by #Holger is good enough, I would like to recommend you try third-party library for this kind of common issues. for example: StreamEx or JOOL. Here is solution by StreamEx.
StreamEx.of(listItems).filter(e -> e.getCounterStatus() == CounterStatus.ACTIVE)
.scanLeft((a,b) -> { b.setStartCounter(a.getEndCounter()); return b; });

Get index in lambda foreach expression java 8

I want to remove objects from the list on a certain filter and there are more than one objects.
list.stream().filter(g->g.getName().equalsIgnoreCase("String")).forEach(result ->{
/* is it possible to get the index of the result here?
.remove(), will iterate through the list again. I don't want that.
*/
list.remove(result);
});
There is no way to get an index at this point, but modifying the list you’re streaming over, is not supported anyway. You would likely get a ConcurrentModificationException when you try.
Use the dedicated API for this operation:
list.removeIf(g -> g.getName().equalsIgnoreCase("String"));
The alternative would be collecting the elements you want to keep into a new List:
List<String> result = list.stream()
.filter(g -> !g.getName().equalsIgnoreCase("String"))
.collect(Collectors.toList());
you can using Collection#removeIf instead, for example:
list.removeIf(g -> g.getName().equalsIgnoreCase("String"));
Sorry if can't help for you
list.stream().filter(g->g.getName().equalsIgnoreCase("String")).forEach(result ->{
list.indexOf(result);
});

Flattening streams from a singleton Optional

This question is related to the Java 8 map and flatMap that is present both in Streams and Optionals. It is worth to note that C# has a similar construct named SelectMany.
I have learned about the two methods, in particular that in Streams you can use flatMap to get a Collection<T> from a Collection<Collection<T>> which is what I want.
In my example I have a nested class structure (from a DTD I have no control over) in which I want to compute a sum of values. I will not redact the class names for laziness.
class DatiTelematico {
private Adempimento adempimento;
}
class Adempimento {
private List<DatiNegozio> datiNegozio;
}
class DatiNegozio {
private List<Negozio> negozio;
}
class Negozio {
private List<Tassazione> tassazione;
}
class Tassazione {
private BigDecimal importo;
}
Given an Optional instance of a DatiTelematico class I would like to sum (importo) from telematico join adempimento join datiNegozio join negozio join tassazione.
The best I could do was to use nested lambdas and plain map method
optionalTelematico.map(DatiTelematico::getAdempimento)
.map(Adempimento::getDatiNegozio)
.map(l -> l.stream().map(DatiNegozio::getNegozio)
.map(n -> n.stream()
.map(Negozio::getTassazione)
.map(t -> t.stream()
.map(Tassazione::getImporto)
.reduce(BigDecimal.ZERO,
BigDecimal::add))
.reduce(BigDecimal.ZERO, BigDecimal::add))
.reduce(BigDecimal.ZERO, BigDecimal::add))
.orElse(BigDecimal.ZERO));
I have tried to start writing something like
optionalTelematico.map(DatiTelematico::getAdempimento)
.map(Adempimento::getDatiNegozio)
.map(l->l.stream().flatMap(n->n.getNegozio().stream().flatMap(s->s.getTassazione().stream().flatMap(Tassazione::getImporto)))....TBD
But then I get a compiler error
Method binding must be directly contained in a class (OTJLD A.3.1).
How do I smartly switch from an Optional<T> (singleton) to a Collection<U> that is to be summed over?
I am asking this to increase my knowledge of Java lambdas.
Both Optional and Stream actually only represent a single piece of data - in case of optional, that piece of data might be either absent or present, in case of stream, there may be other pieces of data coming before or after, but in current moment we have this piece only.
Now,
map method is essentially a kind of type transformation for both Optional and Stream: mapping takes a function I -> R, applying which one can make transformation Optional<I> -> Optional<R> (or Stream<I> -> Stream<R>).
flatMap method is a kind of transformation that can:
Transform optional value into another optional (possibly empty). That means a function type I -> Optional<R>
Transform each item in stream into another stream (having 0..n number of elements in it). That means function type I -> Stream<R>. Note that for streams this operation can change the number of elements that are contained in stream (but it won't change the fact that there always effectively one stream element being processed at a time).
In your particular case, by making transformation to optional, you may obtain up to Optional<List<DatiNegozio>> directly:
Optional<List<DatiNegozio>> optDatiNegozio = optionalDatiTelematico
.map(DatiTelematico::getAdempimento) // Optional<Adempimento>
.map(Adempimento::getDatiNegozio);
Every List<DatiNegozio> you can easily convert to Optional<BigDecimal> summing and accessing elements via Stream:
static Optional<BigDecimal> sumImporto(List<DatiNegozio> datiNegozio) {
return datiNegozio.stream() // Stream<DatiNegozio>
.map(DatiNegozio::getNegozio) // Stream<List<Negozio>>
// unroll stream of collections into a stream of collection elements
.flatMap(List::stream) // Stream<Negozio>
.map(Negozio::getTassazione) // Stream<List<Tassazione>>
// again, unroll stream of collections into a stream of collection elements
.flatMap(List::stream)
.map(Tassazione::getImporto) // Stream<BigDecimal>
// last thing we need to do is just reduce
.reduce(BigDecimal::add);
}
As you can see, second snippet allows you to convert List<DatiNegozio> into an Optional<BigDecimal>. After this, you have two options (stylistic choice):
There is a variant of reduce that yields BigDecimal instead of Optional<BigDecimal>:
.reduce(BigDecimal.ZERO, BigDecimal::add); // it yields concrete type instead of optional because even in case when there is no elements in stream, we can at least return value from which we started - ZERO
You can use second code snippet to produce Function that is usable in flatMap-ing an optional:
optionalDatiTelematico
.map(DatiTelematico::getAdempimento)
.map(Adempimento::getDatiNegozio)
.flatMap(Example::sumImporto) // reference to method from 2nd code snippet
.orElse(BigDecimal.ZERO); // what to do if we had Optional.empty at any point
You can use Collection.stream() method to convert Collection to Stream and use it in flatMap. So combination of .map(d -> d.getList()).flatMap(Collection::stream) returns stream for all internal lists of Stream<D>.
In your case it can looks like:
Optional.of(datiTelematico)
.map(DatiTelematico::getAdempimento)
.map(Adempimento::getDatiNegozio)
.map(Collection::stream)
.orElseGet(Stream::empty)
.map(DatiNegozio::getNegozio)
.flatMap(Collection::stream)
.map(Negozio::getTassazione)
.flatMap(Collection::stream)
.map(Tassazione::getImporto)
.reduce(BigDecimal.ZERO, BigDecimal::add);
Let say
Optional<DatiNegozio> abc = optionalTelematico.map(DatiTelematico::getAdempimento)
.map(Adempimento::getDatiNegozio)
Now when you say abc.map(xyz). xyz must be function which takes instance of DatiNegozio as one and only argument. In your case xyz is a lambda which takes one parameter l whose type should be DatiNegozio. You are now doing l.stream() which throws compile error beacsue stream() does not exist in l(instance ofDatiNegozio).

Categories

Resources