RxJava - groupBy, toMap and flatMap not working well together? - java

I would expect this small example to print all numbers which are divisible by 3.
#Test
public void test() {
Observable.range(1, 100)
.groupBy(n -> n % 3)
.toMap(g -> g.getKey())
.flatMap(m -> m.get(0))
.subscribe(System.out::println);
}
The println is not printing anything instead, and I don't get why.
I reduced this example from a more complex one, I understand this can be done in a different way, but I need it this way as there are more groups involved which needs to be manipulated in the flatMap at the same time.
Thanks for your help!

Use the method filter(Predicate<? super T> predicate) instead of groupBy(..) to emit the elements that satisfy a specified predicate.
Observable.range(1, 100)
.filter(n -> n % 3 == 0)
.toMap(g -> g.getKey())
.flatMap(m -> m.get(0))
.subscribe(System.out::println);
Java Stream-API works on the same principle:
IntStream.range(1, 100).filter(n -> n%3 == 0).forEach(System.out::println);
// prints 3, 6, 9, 12... on the each line

The groupBy operator maps the input source items to HashMap where the latest value is stored under the key.
Quoting the documentation:
If more than one source item maps to the same key, the HashMap will contain the latest of those items.
So in your case the toMap operator transforms the input sequence into the following HashMap:
{0=99, 1=100, 2=98}
To filter out all numbers indivisible by 3 from the range specified just use filter operator as #Nikolas advised.

As #akarnokd stated on a comment above, the example works with the latest version of RxJava v1 and v2, but doesn't work on the version we are using (com.netflix.rxjava:rxjava-core:jar:0.16.1, which is ancient) probably due to a bug in the library itself.

Related

What is the difference between Stream.of and IntStream.range?

Please, consider this code:
System.out.println("#1");
Stream.of(0, 1, 2, 3)
.peek(e -> System.out.println(e))
.sorted()
.findFirst();
System.out.println("\n#2");
IntStream.range(0, 4)
.peek(e -> System.out.println(e))
.sorted()
.findFirst();
The output will be:
#1
0
1
2
3
#2
0
Could anyone explain, why output of two streams are different?
Well, IntStream.range() returns a sequential ordered IntStream from startInclusive(inclusive) to endExclusive (exclusive) by an incremental step of 1, which means it's already sorted. Since it's already sorted, it makes sense that the following .sorted() intermediate operation does nothing. As a result, peek() is executed on just the first element (since the terminal operation only requires the first element).
On the other hand, the elements passed to Stream.of() are not necessarily sorted (and the of() method doesn't check if they are sorted). Therefore, .sorted() must traverse all the elements in order to produce a sorted stream, which allows the findFirst() terminal operation to return the first element of the sorted stream. As a result, peek is executed on all the elements, even though the terminal operation only needs the first element.
IntStream.range is already sorted:
// reports true
System.out.println(
IntStream.range(0, 4)
.spliterator()
.hasCharacteristics(Spliterator.SORTED)
);
So when sorted() method on the Stream is hit, internally, it will become a NO-OP.
Otherwise, as you already see in your first example, all the elements have to be sorted, only then findFirst can tell who is "really the first one".
Just notice that this optimization only works for naturally sorted streams. For example:
// prints too much you say?
Stream.of(new User(30), new User(25), new User(34))
.peek(x -> System.out.println("1 : before I call first sorted"))
.sorted(Comparator.comparing(User::age))
.peek(x -> System.out.println("2 : before I call second sorted"))
.sorted(Comparator.comparing(User::age))
.findFirst();
where (for brevity):
record User(int age) { }

java 8 make a stream of the multiples of two

I'm practicing streams in java 8 and im trying to make a Stream<Integer> containing the multiples of 2. There are several tasks in one main class so I won't link the whole block but what i got so far is this:
Integer twoToTheZeroth = 1;
UnaryOperator<Integer> doubler = (Integer x) -> 2 * x;
Stream<Integer> result = ?;
My question here probably isn't related strongly to the streams, more like the syntax, that how should I use the doubler to get the result?
Thanks in advance!
You can use Stream.iterate.
Stream<Integer> result = Stream.iterate(twoToTheZeroth, doubler);
or using the lambda directly
Stream.iterate(1, x -> 2*x);
The first argument is the "seed" (ie first element of the stream), the operator gets applied consecutively with every element access.
EDIT:
As Vinay points out, this will result in the stream being filled with 0s eventually (this is due to int overflow). To prevent that, maybe use BigInteger:
Stream.iterate(BigInteger.ONE,
x -> x.multiply(BigInteger.valueOf(2)))
.forEach(System.out::println);
Arrays.asList(1,2,3,4,5).stream().map(x -> x * x).forEach(x -> System.out.println(x));
so you can use the doubler in the map caller

Extracting sub-strings with Stream in Java

I'm new to Streams and Lambdas in Java. I have a variable like this -
List<String> lines = Arrays.asList("ab,12,bd","df,23,df","ef,98,dg");
I wanted these actions to happen.
Split each element in the list.
Extract 2nd element in the resulting array. (that is numbers).
Apply some function on it. To make it simple, let's I wanted to multiply it by 2.
Then collect the result as list, that is list containing 24,46,196.
I tried doing that in streams, but I'm not able to get. Any pointers will be helpful. Thank you.
Edit: I tried this way and got result -
List<Integer> result1 = lines.stream()
.map(l -> l.split(",")[1])
.map(l->Integer.parseInt(l))
.collect(Collectors.toList());
And got results as
[12, 23, 98]
Is this is correct way of doing stream?
It should be straightforward.
lines.stream().map(s -> Integer.parseInt(s.split(",")[1]) * 2).collect(Collectors.toList());
Update based on the updated question
Is this is correct way of doing stream?
Yes. You can further combine the two map operations into one like I have shown above.
Does the following work for you?
lines.stream().map(s -> s.split(",")[1])
.map(Integer::parseInt)
.map(f)
.collect(Collectors.toList());
where f is your function from Integer to the type you want to use in your final list.
What about:
List<Integer> result = lines.stream()
.map(line -> line.replaceFirst(".*,(\\d+),.*", "$1"))
.map(num -> 2 * Integer.parseInt(num))
.collect(Collectors.toList());
You may want to convert to int[] in that case you can use:
int[] result = lines.stream()
.map(line -> line.replaceFirst(".*,(\\d+),.*", "$1"))
.mapToInt(num -> 2 * Integer.parseInt(num))
.toArray();

Working with a stream of streams

I want to work out if I can do something like this. Suppose I have a stream of the numbers 1 - 20. I want to utilise a feature such as a drop 3 (limit or skip I guess in Java terms?) and produce a stream of streams that is the numbers:
1 - 20, 4 - 20, 7- 20, etc
then possibly flat map these alll into one stream. I've tried various combinations of using Stream.iterate primarily to generate streams from streams, but I keep geting an IllegalStateException saying the stream has already operated upon or closed.
For example one may expect this code:
Stream.iterate(Stream.of(1,2,3,4,5), x -> x.skip(1).collect(Collectors.toList()).stream()).limit(5).flatMap(x -> x).forEach(x -> System.out.println(x));
To produce: 1,2,3,4,5,2,3,4,5,3,4,5,4,5,5
But it doesn't, it throws an exception. Am I missing something obvious here. I know of a takeWhile operation but don't think it's out until Java 9.
EDIT: I have managed to get an ugly solution to what I was trying to achieve using the following:
List<Integer> list = IntStream.rangeClosed(1, 20).boxed().collect(Collectors.toList());
Stream.iterate(list, x -> x.stream().skip(3).collect(Collectors.toList())).limit(7).map(x -> x.stream().limit(3).collect(Collectors.toList())).flatMap(List::stream).forEach(System.out::println);
So I have a list 1..20, then end up with a stream of lists 1..20, 4..20, 7..20, etc. If I then take the first 3 elements of each of these I end up with 1..3, 4..6, 7..9, etc. If I flatMap this, I get back to 1..20.
My question is, A. is there a way for me to not have to hardcode limit(7) above, so it automatically stops when I've processed all of the input element set, and B. I don't like having to collect into lists - can I not do this purely with streams prior to a collection at the end or even just printing after a flat map?
Perhaps you're looking for something like
IntStream.rangeClosed(1, 20)
.flatMap(i -> IntStream.rangeClosed(i, 20))
...which you can then do whatever you like with; e.g. .forEach(System.out::println).
If you want to do this for every third number, you're better off doing something like
IntStream.rangeClosed(0, 20/3)
.map(i -> 3 * i + 1)
.flatMap(i -> IntStream.rangeClosed(i, 20))

create map of int occurrences using Java 8

I am aware my question is very similar to Count int occurrences with Java8
, but I still cannot solve my case, which must be easier to solve.
The need is to count how many times integers repeat in a stream of integers (will be coming from a file, may be up to 1000000 integers). I thought it might be useful to create a map, where Integer will be a Key, and number of occurrences will be a value.
The exception is
Error:(61, 66) java: method collect in interface
java.util.stream.IntStream cannot be applied to given types;
required:
java.util.function.Supplier,java.util.function.ObjIntConsumer,java.util.function.BiConsumer
found: java.util.stream.Collector> reason: cannot
infer type-variable(s) R
(actual and formal argument lists differ in length)
However, in Java 8 there is a Collectors.groupingBy, which should suffice
Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,Collector<? super T, A, D> downstream)
The problem is that my code is not compiling and I do not see - why.
I simplified it to this:
Map<Integer,Integer> result = IntStream.range(0,100).collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
What is the reason for not compiling?
Thank you in advance :)
IntStream has one method collect where the second argument operates on an int not an Object. Using boxed() turns an IntStream into a Stream<Integer>
Also counting() returns a long.
Map<Integer, Long> result = IntStream.range(0, 100).boxed()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
I have solved the task at hand using Peter's idea.
I'm posting the solution in case somebody is studying Java 8 and does not want to repeat my mistakes.
The task was to:
read numbers from file
find how often each number occurs
find how
many pairs can be found for numbers occurring more than once. For
example, if number 3 occurs 4 times, we will have 6 pairs (I used Apache's
CombinatoricsUtils.binomialCoefficient for that).
My solution:
long result = Arrays.stream(Files.lines(Paths.get(fileName)).mapToInt(Integer::parseInt).collect(() ->
new int[BYTE_MAX_VALUE], (array, value) -> array[value] += 1, (a1, a2) ->
Arrays.setAll(a1, i -> a1[i] + a2[i]))).map((int i) -> combinatorics(i, 2)).sum()
If you're open to using a third party library with primitive collections, you can potentially avoid the boxing operations. For example, if you use Eclipse Collections, you can write the following.
IntBag integers = Interval.oneTo(100).collectInt(i -> i % 10).toBag();
Assert.assertEquals(10, integers.occurrencesOf(0));
Assert.assertEquals(10, integers.occurrencesOf(1));
Assert.assertEquals(10, integers.occurrencesOf(9));
An IntHashBag is implemented by using an IntIntHashMap, so neither the keys (your integers) nor the values (the counts) are boxed.
The same can be accomplished if you loop through your file and add your results to an IntHashBag from an IntStream.
MutableIntBag integers = IntBags.mutable.empty();
IntStream.range(1, 101).map(i -> i % 10).forEach(integers::add);
Assert.assertEquals(10, integers.occurrencesOf(0));
Assert.assertEquals(10, integers.occurrencesOf(1));
Assert.assertEquals(10, integers.occurrencesOf(9));
Note: I am a committer for Eclipse Collections.

Categories

Resources