flatMap triple nested streams - java

I'm trying to flatMap and get a result of list of Strings from three Lists. I somehow was able to do by the following code. The code is working but somehow I feel I have over complicated it. Can someone please give me some advice which can improve it in a better way
countries.stream()
.map(country -> titles.stream()
.map(title -> games.stream()
.map(game -> Arrays.asList(game, country + "_" + title + "_" + game))
.collect(toList()))
.collect(toList()))
.collect(toList())
.stream()
.flatMap(Collection::stream)
.flatMap(Collection::stream)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
For clarification of the logic, a traditional approach would look like:
Set<List<String>> results = new HashSet<>();
for (String country : countries) {
for (String title : titles) {
for (String game : games) {
results.add(Arrays.asList(game, country + "_" + title + "_" + game));
}
}
}

You can do this in two steps:
first create concatenation of countries and titles list:
List<String> countriesTitle = countries.stream()
.flatMap(country -> titles.stream()
.map(title -> country + "_" + title))
.collect(Collectors.toList());
then by previous result create list of concatenation country+"_"+title+"_"+game string:
Stream.concat(games.stream(),
games.stream()
.flatMap(game -> countriesTitle.stream()
.map(countryTitle -> countryTitle + "_" + game)))
.collect(Collectors.toList());
Updated answer:
games.stream()
.flatMap(game -> countries.stream()
.flatMap(country -> titles.stream()
.flatMap(title -> Stream.of(game, country + "_" + title + "_" + game))))
.collect(Collectors.toSet());

Related

How I can convert Map to String?

This is my map:
Map<String, Object> params = new HashMap<>();
params.put("topicRef", "update-123456-78925-new-u1z1w3");
params.put("parentRef", "update-123456-78925-new-u1z1w3");
Script script = new Script(ScriptType.INLINE, "painless",
String.format("ctx._source.parentRef = params.parentRef; ctx._source.topicRef = params.topicRef"),
params);
request.setScript(script);
I want to convert my map into a string but I would like to change the pattern for e.g.:
"ctx._source.key = value;ctx._source.key = value"
I want to add to key value a prefix ctx._source.key and a suffix " =" (space and equal), then I would like to separate each entry with a semicolon.
String formattedMap = params.entrySet().
stream()
.map(e -> "ctx._source." + e.getKey() + " = " + e.getValue())
.collect(Collectors.joining(","));
Try something like this:
Map<String, String> yourMap = /*...*/;
StringBuilder bob = new StringBuilder();
yourMap.forEach((key, value) -> bob.append(key).append("=").append(value).append(";"));
String result = bob.toString();
If necessary you could remove the last ; on result via String.concat().
You could stream your Map's entries, then use the map operation to map each entry to the formatted String and ultimately join each element with the collect(Collectors.joining(";")) operation.
Map<String, Object> params = new HashMap<>();
params.put("topicRef", "update-123456-78925-new-u1z1w3");
params.put("parentRef", "update-123456-78925-new-u1z1w3");
String result = params.entrySet().stream()
.map(entry -> String.format("%s%s%s%s", "ctx._source.", entry.getKey(), " =", entry.getValue()))
.collect(Collectors.joining(";"));
System.out.println(result);
Here is a link to test the code
https://www.jdoodle.com/iembed/v0/rrK
Output
String result = params.entrySet()
.stream()
.map(x -> "ctx._source." + x.getKey() + " = " + x.getValue())
.reduce((x, y) -> x + ";" + y).get();

Data handling of HashMultimap

I have a HashMultimap
Multimap<String, String> map = HashMultimap.create();
The data I put into the map is
map.put("cpu", "i9");
map.put("hang", "MSI");
map.put("hang", "DELL");
map.put("hang", "DELL");
map.put("cpu", "i5");
map.put("hang", "HP");
map.put("cpu", "i7");
I have a stream
String joinString = map.entries().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining(" OR "));
I need the output to be
(hang=HP OR hang=MSI OR hang=DELL) AND (cpu=i9 OR cpu=i5 OR cpu=i7)
I need an AND in between the keys. How can I do that?
Use the Map view:
String joined = map.asMap()
.entrySet()
.stream()
.map(e -> e.getValue()
.stream()
.map(v -> e.getKey() + "=" + v)
.collect(Collectors.joining(" OR ", "(", ")")))
.collect(Collectors.joining(" AND "));
Of course schmosel beat me, but here a slightly different api/usage:
String joined = map.keySet() // keySet() instead of asMap()
.stream().map(k
-> String.format( // string.format instead of concatenation ;)
"(%s)",
map.get(k).stream() // map.get(k) instead of e.getValue()
.map(v
-> String.format("%s=%s", k, v))
.collect(Collectors.joining(" OR "))
)
).collect(Collectors.joining(" AND "));

Collectors.groupingBy throwing NullPointerException

Why is Collectors.groupingBy throwing NullPointerException?
Caused by: java.lang.NullPointerException
at java.util.stream.Collectors.lambda$groupingBy$45(Collectors.java:907)
at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
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.collect(ReferencePipeline.java:499)
at com.evry.integrator.terpcrm.schedular.CustomerTeamsJmsServiceImpl.importDataFromTerpAndNotifyTopic(CustomerTeamsJmsServiceImpl.java:90)
Composite Key:
//Entity with Getter and setters
public String getCompositeKey() {
return getRegistryId() + "_" + getPersonPartyId() + "_" + getSource()+ "_" + getRoleName();
}
//Earlier following was composite key and it works fine; when added another field...NPE but when i revert to following no issues
//public String getCompositeKey() {
// return getRegistryId() + "_" + getPersonPartyId() + "_" + getSource();
//}
Code to find duplicates in table based on composite key which was working fine earlier, but now adding another field throws an NPE: (there are no nulls present in DB)
Set<String> duplicates = xxedgeCrtV.stream()
.collect(Collectors.groupingBy(XxedgeCrtV::getCompositeKey, Collectors.counting()))
.entrySet().stream()
.filter(e -> e.getValue() > 1L)
.map(e -> e.getKey())
.collect(Collectors.toSet());
More data:
Out of 40,000 records; 10 records are there with getRoleName as null,
but when I query the count by grouping by all four, then it does not return any record, which means all are.
In void main, when I try following, no NullPointerException:
String a = null;
String b = "gigi";
String c = "migi";
String d = "figi";
System.out.println(d+ "_" + c+ "_" +b + "_"+a);
Output: figi_migi_gigi_null
Note:
I have handled getRoleName null case, but still same issue.

Writing data to a .CSV file using a Java 8 Stream

I am trying to output 2 collections of data to a .csv file in Java.
Collection 1 = customer names
Collection 2 = customer references
I want the .csv to present as:
Smith:839393,
Johnson:283940,
Collins:293845
My code:
private void writeDataToFile() throws IOException {
FileWriter writer = new FileWriter("src/test/resources/custData.csv");
List<String> customers = new ArrayList<>(customers);
List<String> references = new ArrayList<>(references);
String collect1 = customers.stream().collect(Collectors.joining(",\n" + ":"));
String collect2 = references.stream().collect(Collectors.joining(",\n" + ":"));
writer.write(collect1 + collect2);
writer.close();
}
My output:
Smith,
:Johnson,
:Collins839393,
:283940,
:293845
How can I achieve the desired output?
You can do this way if both lists have the same size. Use IntStream.range to iterate the lists and then map the data. Then collect joining ,\n
String res = IntStream.range(0, customers.size())
.mapToObj(i -> customers.get(i) + ":" + references.get(i))
.collect(Collectors.joining(",\n"));
Assuming both of your collections have same number of elements you can try this
String output =
IntStream.rangeClosed(0, customers.size()-1)
.boxed()
.map(i -> customers.get(i) + ":" + references.get(i))
.collect(Collectors.joining("\n"));
writer.write(output);
I assume customers and references have the same size. You can iterate between 0 and customers.size() and combine the elements of both lists:
customers.get(i) + ":" + references.get(i) + ",\n"
Try this:
String output = IntStream.range(0, customers.size()).boxed()
.map(i -> customers.get(i) + ":" + references.get(i) + ",\n").collect(Collectors.joining());
What you are trying to do is called collection zipping.
See full article here
In pure java you can do the solutions
IntStream.range(0, Math.min(customers.size(), references.size()))
.mapToObj(i -> customers.get(i) + ":" + references.get(i))
.collect(Collectors.joining(",\n"));
If you have guava you can do it bit nicer
Streams
.zip(customers.stream(), references.stream(), (customer, reference) -> customer + ":" + reference)
.collect(Collectors.joining(",\n"));

How to print all the keys and values in a map to a single string?

I have written the following code:
String reqParams = null;
Map<String, String[]> params = request.getParameterMap();
for (Object key : params.keySet()) {
String keyStr = (String) key;
String[] value = params.get(keyStr);
reqParams = reqParams + ((String) key + "=" + Arrays.toString(value) + " ");
}
System.out.println(reqParams);
I have the following output:
nullorderId=[01] orderStatus=[delivered]
How can I get rid of the null that prints at the beginning?
Is it possible to avoid the [ ] from printing?
How can I do this using streams?
Use map operation and the joining collector:
String resultSet =
params.entrySet()
.stream()
.map(e -> e.getKey() + "=" + String.join(", ", e.getValue()))
.collect(Collectors.joining(" "));
", " is the delimiter if the array has more than one element.
" " in the joining is the delimiter to separate the elements
from the source.
Initialize reqParams as an empty string. That is,
String reqParams = null;
becomes
String reqParams = "";
Initialize reqParams like this : String reqParams = "" or don't append your string to redParams and just do this reqParams = ((String) key + "=" + value[0] + " ");
Just get the element in the array instead of turning the array into a String: reqParams = reqParams + ((String) key + "=" + value[0] + " ");
You could stream params.keySet().stream().forEach(key -> .... ) and add a function in the forEach that appends the key and the value to a String like you're doing in the for loop.
You could use the join method of the String class to turn the array into a String, avoiding the [ ] characters. Also, you don't need all the casting and extra parentheses that you have in your original code, if you use Map.Entry with type parameters, and iterate through the entry set instead of the key set.
for( Map.Entry<String,String[]> entry : params.entrySet()) {
reqParams = reqParams + entry.getKey() + "=" + String.join(",", entry.getValue()) + " ";
}
So here, join takes your array and separates all the elements with commas.
Lastly, if you initialise reqParams to "" instead of null, then the word null won't print.
String reqParams = "";

Categories

Resources