Java Flux GroupedFlux count() print - java

Hi rectors all aver the world. I'm doing reactivi programming in Java. I'm a java/grails/react developer but first steps with reactive programming with Spring Boot, version 2 M7 in this case. In the next code:
#GetMapping(API_BASE_PATH + "/flux4")
public String flux4() {
StringBuilder sb = new StringBuilder();
Flux.just("alpha", "bravo", "charlie")
.map(String::toUpperCase)
.flatMap(s -> Flux.fromArray(s.split("")))
.groupBy(String::toString)
.sort(Comparator.comparing(GroupedFlux::key))
.map(group -> group.key() + " => " + group.count() + "; ")
.subscribe(sb::append);
return "flux 4: " + sb.toString();
}
I get the group.key() printed but how caon I get printed the group.count() ?
Any help is welcome
Cheers
Juan

I think I am reading the same book as you :)
Here is the solution using block().
Flux.just("alpha", "beta", "charlie")
.map(String::toUpperCase)
.flatMap(s -> Flux.fromArray(s.split("")))
.groupBy(String::toString)
.sort((o1,o2) -> o1.key().compareTo(o2.key()))
.flatMap(group -> Mono.just(Tuples.of(group.key(), group.count().block())))
.map(keyAndCount -> keyAndCount.getT1() + " => " + keyAndCount.getT2() + "; ")
I am wondering if there is an alternate way that doesn't call block()?. Now group.count() returns a Mono<Long>, and group.key() returns a String. It would be good if we could combine the two to form a Mono<Tuple2<String, Long>> without having to evaluate the result of the Mono. Seems like there should be a general method for doing that?
The book tried to use:
Mono.just(group.key()).and(group.count())
But that just listens for the completion events and returns a Mono<Void>, and therefore gives me compile errors...
Addendum: found it! Use the zip method:
Flux.just("alpha", "beta", "charlie")
.map(String::toUpperCase)
.flatMap(s -> Flux.fromArray(s.split("")))
.groupBy(String::toString)
.sort((o1,o2) -> o1.key().compareTo(o2.key()))
.flatMap(group -> Mono.zip(Mono.just(group.key()), group.count()))
.map(keyAndCount -> keyAndCount.getT1() + " => " + keyAndCount.getT2() + "; ")

Related

Need a way to get to get the pattern in a string matched with List.stream().anyMatch()

I'm matching a list of strings against a list of keywords. The goal is to get a string only one time if it has any of the keywords in it.
Actually I'm doing it with this loop:
for (int i = 0; i < keywords.size(); i++) {
if (text.toLowerCase().contains(keywords.get(i))) {
System.out.println("keyword >>> " + keywords.get(i)
+ " text >>> " + text);
break;
}
}
I like to know if there is a way to get the keyword if I'm using the java stream API like so:
if (keywords.stream().anyMatch(text.toLowerCase()::contains)) {
System.out.println("keyword >>> " + "how to get the keyword here"
+ " text >>> " + text);
}
anyMatch returns a boolean
Using .filter() & findFirst() you can retrieve a value. Note that if you use findFirst() you get an Optional<String> and could either use ifPresent() like this:
keywords.stream()
.filter(keyword -> text.toLowerCase().contains(keyword))
.findFirst()
.ifPresent(keyWord -> System.out.println("[2] keyword >>> " + keyWord + " text >>> " + text));
Or with Optional.isPresent() & Optional.get():
Optional<String> firstKey = keywords.stream()
.filter(keyword -> text.toLowerCase().contains(keyword))
.findFirst();
if (firstKey.isPresent()) {
System.out.println("[2] keyword >>> " + firstKey.get() + " | text >>> " + text);
}
As a less fancy alternative to stream there is a nicer for syntax in Java 1.6+
for(String keyword: keywords) {
if (text.toLowerCase().contains(keyword)) {
System.out.println("keyword >>> " + keyword
+ "text >>> " + text);
break;
}
}
I find the for syntax easier to read than the stream syntax. In a previous job I did a lot of C++ lambda functions with BOOST and the code was hard to parse, and the errors hard to trace.
Another option would be to print an informational message if the keyword was not found. This is easily done by moving the isPresent test inside the print statement.
List<String> keywords =
Arrays.asList("now", "is", "the", "time");
String text = "what are your names?";
Optional<String> opt = keywords.stream()
.filter(keyword -> text
.toLowerCase().contains(keyword))
.findFirst();
System.out.println(!opt.isPresent() ? "No keyword was found!" :
"keyword >>> " + opt.get()
+ " text >>> " + text);
Prints
No keyword was found!

For of to forEach -> lambda expression

I have a "for of" on my java code and Im trying to convert to a "for each" with a lambda expression.
for (Bicycle element : bicycleList) {
txtOutput.append("Model:" + element.getModel() + "\n");
}
I have tried to do this:
bicycleList.forEach(txtOutput.append -> ("Model:" + bicycle.getModel() + "\n"));
Can anyone help me?
bicycleList.forEach(element -> txtOutput.append ("Model:" + element.getModel() + "\n"));
You could alternatively use streams as :
String txtOutput = bicycleList.stream()
.map(element -> "Model:" + element.getModel() + "\n")
.collect(Collectors.joining("", "", ""));

Constructing list from multi level map in Java 8

I have a multi-level map as follows:
Map<String, Map<String, Student> outerMap =
{"cls1" : {"xyz" : Student(rollNumber=1, name="test1")},
"cls2" : {"abc" : Student(rollNumber=2, name="test2")}}
Now I want to construct a list of string from the above map as follows:
["In class cls1 xyz with roll number 1",
"In class cls2 abc with roll number 2"]
I have written as follows, but this is not working, in this context I have gone through the post as well: Java 8 Streams - Nested Maps to List, but did not get much idea.
List<String> classes = outerMap.keySet();
List<String> studentList = classes.stream()
.map(cls ->
outerMap.get(cls).keySet().stream()
.map(student -> "In class "+ cls +
student + " with roll number " +
outerMap.get(cls).get(student).getRollNum() +"\n"
).collect(Collectors.toList());
You can simply use Map.forEach for this operation as:
List<String> messages = new ArrayList<>();
outerMap.forEach((cls, students) ->
students.forEach((name, student) ->
messages.add(convertToMessage(cls, name, student.getRollNumber()))));
where convertToMessage is a util as :
// this could be made cleaner using 'format'
String convertToMessage(String cls, String studentName, String rollNumber) {
return "In class" + cls + "--> " + studentName + " with roll number: " + rollNumber;
}
You may do it like so,
List<String> formattedOutput = outerMap
.entrySet().stream()
.flatMap(e -> e.getValue().entrySet().stream().map(se -> "In class " + e.getKey()
+ " " + se.getKey() + " with roll number " + se.getValue().getRollNumber()))
.collect(Collectors.toList());
You have to use the flatMap operator instead of map operator.
One method use Java 8 Stream and lambda function:
String format = "In class %s %s with roll number %d";
List<String> result = new ArrayList<>();
outerMap.entrySet().stream()
.forEach(v -> {
String className = v.getKey();
v.getValue().entrySet().stream()
.forEach(stringStudentEntry -> result.add(String.format(format,className,stringStudentEntry.getKey(),stringStudentEntry.getValue().getRollNumber())));
});

Reactor Core - Mono - onErrorFlatmap

Is there a way in Mono to return a flatMap while there is an error(onErrorFlatMap)
My scenario is, i would need the SubscriberContext when there is an error after processing i need the same error propagated down the chain
String test = "test";
Mono.just(test)
.map(Integer::valueOf)
.onErrorMap(error -> Mono.subscriberContext()
.map(context -> {
System.out.println(error + " -- " + context.getOrDefault("APPID", null));
return error;
}))
.subscriberContext(of("APPID", "APP-101"))
.block();
This is the way, i found to fix it, but is there a better way?
String test = "test";
Mono.just(test)
.map(Integer::valueOf)
.onErrorResume(error -> Mono.subscriberContext()
.flatMap(context -> {
System.out.println(error + " -- " + context.getOrDefault("APPID", null));
return Mono.error(error);
}))
.subscriberContext(of("APPID", "APP-101"))
.block();
Using onErrorResume and ultimately returning a Mono.error is the correct and recommended pattern for this use case.

using system.out.print with java streams

I have the below code to map each entry to a print statement, But it shows error.
Is something wrong in the way I understood Stream().map()?
How do I use System.out.println() within the streams API? how do I correct the following code?
public static void main(String[] args) {
Properties p = new Properties();
p.setProperty("name", "XYX");
p.setProperty("email", "xyx#mail.com");
p.setProperty("address", "addr-street-city");
p.entrySet().stream().map(e ->
System.out.println(" " + e.getKey().toString() + " " + e.getValue().toString() + ""));
}
If you want to use map:
p.entrySet().stream()
.map(e -> " "+e.getKey()+" "+e.getValue())
.forEach(System.out::println);
p.entrySet().forEach(e -> System.out.println(e.getKey() + " " + e.getValue()));
or
p.forEach((key, value) -> System.out.println(key + " " + value));
properties.entrySet().stream()
.map(entry -> String.format("%s : %s", entry.getKey(), entry.getValue()))
.forEach(System.out::println);
.map(...) - convert you key-value to a string format
.forEach(...) - prints your string
Your output should look like that :
address : addr-street-city
email : xyx#mail.com
name : XYX
p.entrySet().forEach(System.out::println)
map() should be used only if you want to alter/modify stream otherwise
for printing to the console forEach would do the work as it accepts BiConsumer to print key and value

Categories