Using java 8 streams to generate pairs of integers - java

I am trying to generate pairs of integers - I have a class Pair with a constructor taking 2 ints. The following code works but seems rather clunky - in particular the conversion from an intStream to an object stream using mapToObj(Integer::new).
private static List<Pair> success() {
return IntStream.range(0, 10).
mapToObj(Integer::new).flatMap(i -> IntStream.range(12, 15).
mapToObj(j -> new Pair(i, j))).
collect(Collectors.toList());
}
Firstly does anyone have a more elegant way to do this ?
Secondly when I refactored to extract some streams as variables, I get an error: IllegalStateException: stream has already been operated upon or closed. Here is the refactored method - does anyone know if this a problem with the code ?
static List<Pair> fail() {
Stream<Integer> outer = IntStream.range(0, 10).mapToObj(Integer::new);
IntStream inner = IntStream.range(12, 15);
Stream<Pair> pairStream = outer.flatMap(i ->
inner.mapToObj(j -> new Pair(i, j)));
return pairStream.collect(Collectors.toList());
}

It is possible to make it a bit more concise by replacing mapToObj(Integer::new) with boxed- but apart from that, Java is not that concise:
IntStream.range(0, 10)
.boxed()
.flatMap(i -> IntStream.range(12, 15)
.mapToObj(j -> new Pair(i, j)))
.collect(Collectors.toList());
As for the second question: There are other answers which link to the problem. The concrete problem is that inner is not used once, but each time of the outer flatMap().
This way it works:
final IntStream range = IntStream.range(0, 10);
List<Pair> ps = range
.boxed().flatMap(i -> {
final IntStream range1 = IntStream.range(12, 15);
return range1.
mapToObj(j -> new Pair<>(i, j));
}).
collect(Collectors.toList());

Why not use plain for-loops? Plain for-loops will:
Look nicer
Make your intent clear
static List<Pair> fail() {
List<Pair> pairs = new ArrayList<>(30);
for (int i = 0; i < 10; i++) {
for (int j = 12; j < 15; j++) {
pairs.add(new Pair(i, j));
}
}
return pairs;
}

If your Pair class accepts primitive ints you can eliminate the unnecessary boxing this way:
private static List<Pair> success() {
return IntStream.range(0, 10).
mapToObj(i -> IntStream.range(12, 15).
mapToObj(j -> new Pair(i, j))).
flatMap(Function.identity()).
collect(Collectors.toList());
}
As for extracting streams into variables, you may create a supplier instead:
private static List<Pair> success() {
Supplier<IntStream> inner = () -> IntStream.range(12, 15);
return IntStream.range(0, 10).
mapToObj(i -> inner.get().
mapToObj(j -> new Pair(i, j))).
flatMap(Function.identity()).
collect(Collectors.toList());
}
Though it seems unnecessary for me to extract the stream into the variable.

Related

Create a map of maps with counts from list

Given a List<Integer> l and a factor int f, I would like to use a stream to create a Map<Integer, Map<Integer, Long>> m such that the parent map has keys that are the index within l divided by f, and the value is a map of values to counts.
If the list is {1,1,1,4} and the factor is f=2 I would like to get:
0 ->
{
1 -> 2
}
1 ->
{
1 -> 1
4 -> 1
}
Basically, I'm hoping for a stream version of:
Map<Integer, Map<Integer, Long>> m = new HashMap<>();
for (int i = 0; i < l.size(); i++) {
m.computeIfAbsent(i/f, k -> new HashMap<>())
.compute(l.get(i), (k, v) -> v==null?1:v+1);
}
I realize it is fairly similar to this question about collecting a map of maps and I understand how to do a much simpler groupingBy with a count:
Map<Integer, Long> m = l.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
But I do not understand how to put those two ideas together without iterating.
Because I am working with indexes as one of the keys, I imagine that rather than starting with l.stream() I will start with IntStream.range(0, l.size()).boxed() which lets me get the first key (i -> i/f) and the second key(i -> l.get(i)), but I still don't know how to properly collect the counts.
Here is a solution.
public static void main(String[] args) {
final List<Integer> l = List.of(1,1,1,4);
final int f = 2;
final var value = IntStream.range(0,l.size())
.boxed()
.collect(Collectors.groupingBy(i -> i/f, Collectors.groupingBy(l::get, Collectors.counting())));
System.out.println(value);
}
Not sure if this is a personal requirement, but sometime using standard loops over streams is not necessarily a bad thing.
You can wrap your grouping collector in CollectingAndThen collector which takes a downstream collector and a finisher function. In the finisher you can modify the values (sublists) to a map:
List<Integer> list = List.of(1, 1, 1, 4);
int fac = 2;
AtomicInteger ai = new AtomicInteger();
Map<Integer,Map<Integer,Long>> result =
list.stream()
.collect(Collectors.groupingBy(
i -> ai.getAndIncrement() / fac,
Collectors.collectingAndThen(
Collectors.toList(), val -> val.stream()
.collect(Collectors.groupingBy(Function.identity(),
Collectors.counting())))));
System.out.println(result);

Iterate over two Lists of Lists with IntStream instead of streams

I am trying to use streams in order to iterate over two lists of lists in order to verify if the inner lists sizes are the same for the same index. I have managed to achieve this using streams, but I have to rewrite using an IntStream and mapToObj.
My current approach is:
List<List<String>> a = config.getStrips();
List<List<Integer>> b = slotMachineConfig.getWeights();
a.stream()
.filter(first ->
b.stream()
.allMatch(second -> second.size() == first.size())
)
.findFirst()
.orElseThrow(InvalidConfigException::new);
The problem is that I cannot be sure that the sizes will correspond for the big lists, so I have to rewrite this using IntStream and also using indexes for each list.
What I have so far, but does not work looks like this, I am trying to write a "validate" function in order to verify the inner lists, but it seems like I get an error there saying "no instance of type variable U exist so that void conforms to U".
IntStream.range(0, a.size())
.mapToObj(i -> validate(i, a.get(i), b.get(i)))
.findFirst()
.orElseThrow(SlotMachineInvalidConfigException::new);
public void validate(int index, List<String> firstList, List<Integer> secondList) {
How can I rewrite my method using IntStream and mapToObj, can anyone help me?
You have the right idea but you don't really need a separate validation function if you are just comparing sizes. Here's a working example that supports any list types:
public class ListSizeMatcher {
public <T,S> boolean sizeMatches(List<List<T>> list1, List<List<S>> list2) {
return list1.size() == list2.size()
&& IntStream.range(0, list1.size())
.allMatch(i -> list1.get(i).size() == list2.get(i).size());
}
public static void main(String[] args) {
ListSizeMatcher matcher = new ListSizeMatcher();
System.out.println(matcher.sizeMatches(List.of(List.of(1)), List.of(List.of("a"), List.of("b"))));
System.out.println(matcher.sizeMatches(List.of(List.of(1)), List.of(List.of("a", "b"))));
System.out.println(matcher.sizeMatches(List.of(List.of(1, 2)), List.of(List.of("a", "b"))));
}
}
Note that from a design perspective if each item in the list matches the corresponding item in a separate list you'd be better off creating a single class that contains both items.
If I understand correctly, I think something like this would work:
List<List<String>> a = config.getStrips();
List<List<Integer>> b = slotMachineConfig.getWeights();
if (a.size() != b.size()) throw new InvalidConfigException();
boolean allTheSame = IntStream.range(0, a.size())
.map(i -> a.get(i).size() - b.get(i).size())
.allMatch(diff -> diff == 0);
if (!allTheSame) throw new InvalidConfigException();
For the record, your validate function returns void but I'll assume it was meant to return a boolean
here is a more compact version
List<List<String>> a = new LinkedList<>();
List<List<Integer>> b = new LinkedList<>();
boolean match = IntStream.range(0, a.size())
.mapToObj(i -> a.get(i).size() == b.get(i).size())
.reduce(Boolean::logicalAnd).orElseThrow(InvalidConfigException::new);
if (!match) {
throw new InvalidConfigException();
}
Alternative:
List<List<String>> a = new LinkedList<>();
List<List<Integer>> b = new LinkedList<>();
if (IntStream.range(0, a.size()).filter(i -> a.get(i).size() != b.get(i).size()).count() > 0){
throw new InvalidConfigException();
};
At the end of the day it only takes 1 to be different and fail.
The error means that the validate method cannot be void and it is expected to return some valid value (possibly boolean).
If the inner lists are supposed to have the equal sizes to be valid, the check may look as follows:
// assuming the sizes of outer lists are equal
boolean allSizesEqual = IntStream.range(0, a.size())
.allMatch(i -> a.get(i).size() == b.get(i).size());
if (!allSizesEqual) {
throw new InvalidConfigException("Not all sizes are valid");
}
If there's a need to find specific indexes where a discrepancy is detected:
List<Integer> badIndexes = IntStream.range(0, a.size())
.filter(i -> a.get(i).size() != b.get(i).size()) // IntStream
.boxed() // Stream<Integer>
.collect(Collectors.toList());
if (!badIndexes.isEmpty()) {
throw new InvalidConfigException("Different indexes found: " + badIndexes);
}
Or validate method could be fixed to return appropriate value for the filter:
boolean allItemsValid = IntStream.range(0, a.size())
.allMatch(i -> listsAreValid(a.get(i), b.get(i)));
if (!allItemsValid) {
throw new InvalidConfigException("Not all entries are valid");
}
public boolean listsAreValid(List<String> innerA, List<Integer> innerB) {
// any advanced logic
return innerA.size() == innerB.size();
}

Converting Stream<IntStream> to a single IntStream

I want to convert a List<String> to an IntStream. Suppose that my list is like ["abc", "de", "fghi"]. Then the IntStream that I want is like 1,1,1,2,2,3,3,3,3. (The number of occurrences of a number i in the IntStream depends on the length of ith string in the given list)
I wrote the following method for that (it won't compile):
private static IntStream getSingleIntStream(List<String> list) {
final AtomicInteger atomicInteger = new AtomicInteger();
return list.stream()
.map(str -> {
atomicInteger.incrementAndGet();
return IntStream.range(0, str.length())
.map(i -> atomicInteger.get());
}); // Now I don't understand how can I convert this Stream<IntStream> to a single IntStream
}
But I don't understand how can I convert can I convert a Stream<IntStream> to a single IntStream. (My guess is that we can use flatMap somehow, but I don't exactly get how to use it.)
I'm using IntStream instead of Stream<Integer> to avoid auto-boxing and make my whole system more efficient.
The other solution would be like this:
IntStream result = IntStream.range(0,list.size())
.flatMap(i-> IntStream.range(0, list.get(i)
.length())
.map(j->i+1));
Expanding over the comment of #JB_Nizet
The method that I need to use is flatMapToInt.
private static IntStream getSingleIntStream(List<String> list) {
final AtomicInteger atomicInteger = new AtomicInteger();
return list.stream()
.flatMapToInt(str -> {
atomicInteger.incrementAndGet();
return IntStream.range(0, str.length())
.map(i -> atomicInteger.get());
});
}
The solution would be to use flatMapToInt:
private static IntStream getSingleIntStream(List<String> list) {
final AtomicInteger atomicInteger = new AtomicInteger();
return list.stream()
.flatMapToInt(str -> {
atomicInteger.incrementAndGet();
return IntStream.range(0, str.length())
.map(i -> atomicInteger.get());
});
}
but I'd rethink what you'd like to achieve here. Now, each String in the initial list will be replaced with a number taken from AtomicInteger (and this will be repeated String.length() times for each String):
getSingleIntStream(Arrays.asList("a", "bc")).forEach(System.out::println); // 1 2 2
I assume you wanted to number each char from every String:
private static IntStream getSingleIntStream(List<String> list) {
AtomicInteger atomicInteger = new AtomicInteger();
return list.stream()
.flatMapToInt(str -> IntStream.range(0, str.length())
.map(i -> atomicInteger.incrementAndGet()));
}
// 1 2 3
You are absolutely correct about flatMap, as Hadi J already shows in a good answer. I just wanted to offer my variant of the same:
private static IntStream getSingleIntStream(List<String> list) {
return IntStream.rangeClosed(1, list.size())
.flatMap(i -> list.get(i - 1).chars().map(ch -> i));
}
You may find this version more natural or concise. In any case both versions have the advantage of avoiding the AtomicInteger and the side effect of the stream pipeline on it. A stream pipeline should be free from side effects.
Let’s also see it in action:
List<String> list = List.of("abc", "de", "fghi");
int[] nums = getSingleIntStream(list).toArray();
System.out.println(Arrays.toString(nums));
[1, 1, 1, 2, 2, 3, 3, 3, 3]
When you need the index of an element inside a stream, the general trick is to start out from an IntStream of the indices and inside your stream pipeline make the lookup of the elements from the indices (list.get(i - 1)). In this case I am (unconventionally) using 1-based indices because you wanted your resulting numbers to start from 1. So we need to subtract 1 in the list lookup.

Java predicate - match against first predicate [duplicate]

I've just started playing with Java 8 lambdas and I'm trying to implement some of the things that I'm used to in functional languages.
For example, most functional languages have some kind of find function that operates on sequences, or lists that returns the first element, for which the predicate is true. The only way I can see to achieve this in Java 8 is:
lst.stream()
.filter(x -> x > 5)
.findFirst()
However this seems inefficient to me, as the filter will scan the whole list, at least to my understanding (which could be wrong). Is there a better way?
No, filter does not scan the whole stream. It's an intermediate operation, which returns a lazy stream (actually all intermediate operations return a lazy stream). To convince you, you can simply do the following test:
List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);
int a = list.stream()
.peek(num -> System.out.println("will filter " + num))
.filter(x -> x > 5)
.findFirst()
.get();
System.out.println(a);
Which outputs:
will filter 1
will filter 10
10
You see that only the two first elements of the stream are actually processed.
So you can go with your approach which is perfectly fine.
However this seems inefficient to me, as the filter will scan the whole list
No it won't - it will "break" as soon as the first element satisfying the predicate is found. You can read more about laziness in the stream package javadoc, in particular (emphasis mine):
Many stream operations, such as filtering, mapping, or duplicate removal, can be implemented lazily, exposing opportunities for optimization. For example, "find the first String with three consecutive vowels" need not examine all the input strings. Stream operations are divided into intermediate (Stream-producing) operations and terminal (value- or side-effect-producing) operations. Intermediate operations are always lazy.
return dataSource.getParkingLots()
.stream()
.filter(parkingLot -> Objects.equals(parkingLot.getId(), id))
.findFirst()
.orElse(null);
I had to filter out only one object from a list of objects. So i used this, hope it helps.
In addition to Alexis C's answer, If you are working with an array list, in which you are not sure whether the element you are searching for exists, use this.
Integer a = list.stream()
.peek(num -> System.out.println("will filter " + num))
.filter(x -> x > 5)
.findFirst()
.orElse(null);
Then you could simply check whether a is null.
Already answered by #AjaxLeung, but in comments and hard to find.
For check only
lst.stream()
.filter(x -> x > 5)
.findFirst()
.isPresent()
is simplified to
lst.stream()
.anyMatch(x -> x > 5)
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
// Stream is ~30 times slower for same operation...
public class StreamPerfTest {
int iterations = 100;
List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);
// 55 ms
#Test
public void stream() {
for (int i = 0; i < iterations; i++) {
Optional<Integer> result = list.stream()
.filter(x -> x > 5)
.findFirst();
System.out.println(result.orElse(null));
}
}
// 2 ms
#Test
public void loop() {
for (int i = 0; i < iterations; i++) {
Integer result = null;
for (Integer walk : list) {
if (walk > 5) {
result = walk;
break;
}
}
System.out.println(result);
}
}
}
A generic utility function with looping seems a lot cleaner to me:
static public <T> T find(List<T> elements, Predicate<T> p) {
for (T item : elements) if (p.test(item)) return item;
return null;
}
static public <T> T find(T[] elements, Predicate<T> p) {
for (T item : elements) if (p.test(item)) return item;
return null;
}
In use:
List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5);
Integer[] intArr = new Integer[]{1, 2, 3, 4, 5};
System.out.println(find(intList, i -> i % 2 == 0)); // 2
System.out.println(find(intArr, i -> i % 2 != 0)); // 1
System.out.println(find(intList, i -> i > 5)); // null
Improved One-Liner answer: If you are looking for a boolean return value, we can do it better by adding isPresent:
return dataSource.getParkingLots().stream().filter(parkingLot -> Objects.equals(parkingLot.getId(), id)).findFirst().isPresent();

How to morph this code snippet into Java 8 where logic depends on index value?

I have this code that I would like to see it in Java 8 flavor:
List<Double> outcome = ....
int step = data.size / 20;
for (int i = 0; i < 20; i++) {
Instance inst = data.get(i * step).getInstance();
if (inst.isPresent())
outcome.add(100);
else
outcome.add(0.0);
For me, it is easy to transform the code into Java 8 streams but I don't know how to implement the data.get(i * step) part.
You can use the IntStream, which is "a sequence of primitive int-valued elements supporting sequential and parallel aggregate operations".
For example:
IntStream.range(0, 20)
.forEach(i -> {
Instance inst = data.get(i * step).getInstance();
outcome.add(inst.isPresent() ? 100d : 0d);
});
As #AlexisC. suggested, this can be reduced to a one-liner:
List<Double> outcome =
IntStream.range(0, 20)
.mapToObj(i -> data.get(i*step).getInstance().isPresent()? 100d : 0d)
.collect(toList());
Here is an alternative solution that does not mutate the list but uses a collector instead (side-effect free code is generally recommended when using streams, especially if you may parallelise them in the future):
List<Double> outcome = IntStream.range(0, 20)
.mapToObj(i -> data.get(i * step).getInstance())
.map(inst -> inst.isPresent() ? 100d : 0d)
.collect(toList());

Categories