I have a methodA which takes an argument and returns a result. I am writing a reactive method to invoke the function in bulk. But Not able to get my head around reactive syntax.
My code looks like this
List<GetResult> successfulResults =
Collections.synchronizedList(new ArrayList<>());
Map<String, Throwable> erroredResults = new ConcurrentHashMap<>();
Flux.fromIterable(docsToFetch).flatMap(key -> reactiveCollection.getAndTouch(key, Duration.ofMinutes(extendExpiryInMin))
.onErrorResume(e -> {
erroredResults.put(key, e);
return Mono.empty();
})
).doOnNext(successfulResults::add).last().block();
The current implementation calls the method but collects the result in list. Collecting result in list does not make sense to my use case. I want to collect the result in a hashmap of key and result.
The solution is
List<String> docsToFetch = Arrays.asList("airline_112", "airline_1191", "airline_1203");
Map<String, GetResult> successfulResults = new ConcurrentHashMap<>();
Map<String, Throwable> erroredResults = new ConcurrentHashMap<>();
Flux.fromIterable(docsToFetch).flatMap(key -> reactiveCollection.get(key).onErrorResume(e -> {
erroredResults.put(key, e);
return Mono.empty();
}).doOnNext(getResult -> successfulResults.put(key, getResult))).last().block();
Related
I am currently getting a map data from an external api call.
I want to ensure the data is not null or empty and perform a set of operations on it
by filtering to a specific key in the map and capturing results into another object.
The key itself is comma separated.
Example key / value in map.
"key1,key2,key3,id100" : {
"val1: "",
"val2: "",
"val3: "",
... others
}
I am filtering to capture all values under this key (so data cal1, val2, val3 and others)
and then perform some operations.
But when I perform the filter as shown, I end up with a stream.
Thus Instead of just a Map<String, Object>, I end up with Stream<Map.Entry<String, Object>>.
Tried flatmap and getting following error:
no instance(s) of type variable(s) U exist so that
Stream<Entry<String, Object>> conforms to Optional
How could I convert it back to a Map from the Stream or a better way to filter this? Thanks.
Could have just done this via a for loop without Streams but trying to see how
I could achieve this in a Stream implementation thus not looking for a for loop solution. Please advice. Thanks.
private NewObject get() {
Map<String, Object> data = // data filled in by an external rest call;
return Optional.ofNullable(data)
// using flatmap here throws above error
.map(Map::entrySet)
.map(entries -> entries.stream()
.filter(entry -> entry.getKey().contains("id100))
// I wish to carry on further operations from here by using the filtered map.
// having issues cos capturedData is a Stream
// even if using flatmap at this stage, capturedData is still a Stream.
// wanting to do the following but can't due to it being a Stream and not a map
).map(capturedData -> {
Map<String, Object> at = (Map<String, Object>) capturedData;
NewObject newObject = new NewObject();
newObject.setName((String) at.get("val1"));
return newObject;
}).orElse(null);
}
Use map to construct the NewObject and use findFirst to get the first value (as per your comment, there will be only one entry whose key has substring id100). Finally use flatMap to unwrap the Optional<NewObject>.
return Optional.ofNullable(data)
.map(Map::entrySet)
.flatMap(entries -> entries.stream()
.filter(entry -> entry.getKey().contains("id100"))
.map(entry -> {
NewObject newObject = new NewObject();
Map<String, String> nestedMap = (Map<String, String>) entry.getValue();
newObject.setName(nestedMap.get("val1"));
return newObject;
})
.findFirst())
.orElse(null);
This code below filters the entryset in data, collects it to a set before performing the next set of operations. findFirst is used so that there is only ever a single entry to deal with.
Optional.ofNullable(data)
.map(Map::entrySet)
.map(entries ->
entries
.stream()
.filter(e -> e.getKey().contains("id1000")).collect(Collectors.toSet()))
.stream()
.findFirst()
.map(capturedData -> {
Map<String, Object> map = (Map<String, Object>) capturedData;
NewObject newObject = new NewObject();
newObject.setName((String) at.get("val1"));
return newObject;
})
.orElse(null);
How do I put/add eServiceReportsMapByBatchFile with key oldReportId to eServiceReportMap without side-effects?
Map<String, Map<String, Set<EServiceReport>>> eServiceReportMap = new HashMap<>();
reports.forEach(report -> {
String oldReportId = report.getOldId();
Map<String, Set<EServiceReport>> eServiceReportsMapByBatchFile = // processing of batch files
...
eServiceReportMap.put(oldReportId, eServiceReportsMapByBatchFile);
});
return eServiceReportMap;
That is, I want it to become like this:
return reports.stream()
.map(report -> {
String oldReportId = report.getOldId();
Map<String, Set<EServiceReport>> eServiceReportsMapByBatchFile = // processing of batch files
...
// I don't know how and what to return here
}).collect(// I don't know what to do here);
Thank you.
You're looking forward mostly to Collectors.toMap which can be used as :
return reports.stream()
.collect(Collectors.toMap(report -> report.getOldId(),
report -> {
// batch processing for eServiceReportsMapByBatchFile
return eServiceReportsMapByBatchFile;
}));
since you call stream on reports, I assume it is a collection of some kind; in such case there is nothing wrong with your side-effects. Notice that someCollection.stream().forEach and someCollection.forEach are very different things, you are more than OK to have side-effects with SomeCollection::forEach - which is nothing but a plain old loop internally.
You could transform that that to a stream solution, but it's going to be a lot less readable:
reports.stream()
.map(r -> {
String oldReportId = report.getOldId();
Map<String, Set<EServiceReport>> eServiceReportsMapByBatchFile =....
return new SimpleEntry<>(oldReportId, eServiceReportsMapByBatchFile);
})
.collect(Collectors.toMap(
Entry::getKey,
Entry::getValue,
(left, right) -> right; // to match whatever you had until now
))
I made collector who can reduce a stream to a map which has the keys as the items that can be bought by certain customers and the names of customers as values, my implementation is working proberly in sequential stream
but when i try to use parallel it's not working at all, the resulting sets always contain one customer name.
List<Customer> customerList = this.mall.getCustomerList();
Supplier<Object> supplier = ConcurrentHashMap<String,Set<String>>::new;
BiConsumer<Object, Customer> accumulator = ((o, customer) -> customer.getWantToBuy().stream().map(Item::getName).forEach(
item -> ((ConcurrentHashMap<String,Set<String>>)o)
.merge(item,new HashSet<String>(Collections.singleton(customer.getName())),
(s,s2) -> {
HashSet<String> res = new HashSet<>(s);
res.addAll(s2);
return res;
})
));
BinaryOperator<Object> combiner = (o,o2) -> {
ConcurrentHashMap<String,Set<String>> res = new ConcurrentHashMap<>((ConcurrentHashMap<String,Set<String>>)o);
res.putAll((ConcurrentHashMap<String,Set<String>>)o2);
return res;
};
Function<Object, Map<String, Set<String>>> finisher = (o) -> new HashMap<>((ConcurrentHashMap<String,Set<String>>)o);
Collector<Customer, ?, Map<String, Set<String>>> toItemAsKey =
new CollectorImpl<>(supplier, accumulator, combiner, finisher, EnumSet.of(
Collector.Characteristics.CONCURRENT,
Collector.Characteristics.IDENTITY_FINISH));
Map<String, Set<String>> itemMap = customerList.stream().parallel().collect(toItemAsKey);
There is certainly a problem in my accumulator implementation or another Function but I cannot figure it out! could anyone suggest what should i do ?
Your combiner is not correctly implemented.
You overwrite all entries that has the same key. What you want is adding values to existing keys.
BinaryOperator<ConcurrentHashMap<String,Set<String>>> combiner = (o,o2) -> {
ConcurrentHashMap<String,Set<String>> res = new ConcurrentHashMap<>(o);
o2.forEach((key, set) -> set.forEach(string -> res.computeIfAbsent(key, k -> new HashSet<>())
.add(string)));
return res;
};
How do I change Stream<List<Pair<A, B>>> to Pair<List<A>, List<B>> ?
I have done it by:
1.using collect(Collectors.toList()) to collect the List<Pair<A, B>> result
2.iterating the list result and adding As and Bs to two separate lists
3.creating a new Pair<List<A>, List<B>>
But I got really paranoid about getting this done while still in the stream.
Need help. Thank you.
To achieve wanted result within the Stream itself, you can do it using reduce():
final Pair<List<A>, List<B>> result = stream
.map(s -> {
Pair<List<A>, List<B>> p = new Pair<>(new ArrayList<>(), new ArrayList<>());
p.getKey().add(s.getKey());
p.getValue().add(s.getValue());
return p;
})
.reduce((pairA, pairB) -> {
pairA.getKey().addAll(pairB.getKey());
pairA.getValue().addAll(pairB.getValue());
return pairA;
})
.get();
However, more elegant solution would be to iterate trough your Stream and fill both lists, create Pair at the end, like so:
final Stream<Pair<A, B>> stream = // your stream
final List<A> listA = new ArrayList<>();
final List<B> listB = new ArrayList<>();
stream.forEach(p -> {
listA.add(p.getKey());
listB.add(p.getValue());
});
final Pair<List<A>, List<B>> result = new Pair<>(listA, listB);
Depends on why you want to transform it into a Pair inside a Stream.
EDIT: Just note that using first example, you are creating a lot new object instances for every element in initial Stream. It is far from optimal solution. But if you want to achieve this inside a Stream, I don't see any other option.
using collect(Collectors.toList()) to collect the List<Pair<A, B>> result
You can write custom collector, use Collector.of() (in this example javafx.util.Pair):
List<Pair<A, B>> pairs = // list of Pait
Pair<List<A>,List<B>> pairOfLists = pairs.stream()
.collect(
Collector.of(
//init accumulator
() -> new Pair<>(
new ArrayList<>(),
new ArrayList<>()
),
//processing each element
(pairOfLists, pair) -> {
pairOfLists.getKey().add(pair.getKey());
pairOfLists.getValue().add(pair.getValue());
},
//confluence of two accumulators in parallel execution
(pairOfLists1, pairOfLists2) ->{
pairOfLists1.getKey().addAll(pairOfLists2.getKey());
pairOfLists1.getValue()addAll(pairOfLists2.getValue());
return pairOfLists1;
}
)
);
Mind the existence of the three-argument version of Stream.collect(), which makes the Collector.of() obsolete and the combiner simpler for such use cases.
Pair<List<A>, List<Integer>> p2 = p.stream()
.collect(
() -> new Pair<>(
new ArrayList<>(),
new ArrayList<>()
),
(pairOfLists, pair) -> {
pairOfLists.getKey().add(pair.getKey());
pairOfLists.getValue().add(pair.getValue());
},
(pairOfLists1, pairOfLists2) -> {
pairOfLists1.getKey().addAll(pairOfLists2.getKey());
pairOfLists1.getValue().addAll(pairOfLists2.getValue());
}
);
This is the most straightforward solution, yet effective, I have found:
Pair<List<T1>, List<T2>> target = new Pair<>(
pairStream.stream().map(i -> i.getT1()).collect(Collectors.toList()),
pairStream.stream().map(i -> i.getT2()).collect(Collectors.toList())
);
I assume the class Pair has a constructor public Pair(T1 t1, T2 t2).
I am not aware of any solution being able to return the desired result using a single stream.
I'm trying to collect in a Map the results from the process a list of objects and that it returns a map. I think that I should do it with a Collectors.toMap but I haven't found the way.
This is the code:
public class Car {
List<VersionCar> versions;
public List<VersionCar> getVersions() {
return versions;
}
}
public class VersionCar {
private String wheelsKey;
private String engineKey;
public String getWheelsKey() {
return wheelsKey;
}
public String getEngineKey() {
return engineKey;
}
}
process method:
private static Map<String,Set<String>> processObjects(VersionCar version) {
Map<String,Set<String>> mapItems = new HashMap<>();
mapItems.put("engine", new HashSet<>(Arrays.asList(version.getEngineKey())));
mapItems.put("wheels", new HashSet<>(Arrays.asList(version.getWheelsKey())));
return mapItems;
}
My final code is:
Map<String,Set<String>> mapAllItems =
car.getVersions().stream()
.map(versionCar -> processObjects(versionCar))
.collect(Collectors.toMap()); // here I don't know like collect the map.
My idea is to process the list of versions and in the end get a Map with two items: wheels and engine but with a set<> with all different items for all versions. Do you have any ideas as can I do that with Collectors.toMap or another option?
The operator you want to use in this case is probably "reduce"
car.getVersions().stream()
.map(versionCar -> processObjects(versionCar))
.reduce((map1, map2) -> {
map2.forEach((key, subset) -> map1.get(key).addAll(subset));
return map1;
})
.orElse(new HashMap<>());
The lambda used in "reduce" is a BinaryOperator, that merges 2 maps and return the merged map.
The "orElse" is just here to return something in the case your initial collection (versions) is empty.
From a type point of view it gets rid of the "Optional"
You can use Collectors.toMap(keyMapper, valueMapper, mergeFunction). Last argument is used to resolve collisions between values associated with the same key.
For example:
Map<String, Set<String>> mapAllItems =
car.getVersions().stream()
.map(versionCar -> processObjects(versionCar))
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
(firstSet, secondSet) -> {
Set<String> result = new HashSet<>();
result.addAll(firstSet);
result.addAll(secondSet);
return result;
}
));
To get the mapAllItems, we don't need and should not define processObjects method:
Map<String, Set<String>> mapAllItems = new HashMap<>();
mapAllItems.put("engine", car.getVersions().stream().map(v -> v.getEngineKey()).collect(Collectors.toSet()));
mapAllItems.put("wheels", car.getVersions().stream().map(v -> v.getWheelsKey()).collect(Collectors.toSet()));
Or by AbstractMap.SimpleEntry which is lighter than the Map created byprocessObjects`:
mapAllItems = car.getVersions().stream()
.flatMap(v -> Stream.of(new SimpleEntry<>("engine", v.getEngineKey()), new SimpleEntry<>("wheels", v.getWheelsKey())))
.collect(Collectors.groupingBy(e -> e.getKey(), Collectors.mapping(e -> e.getValue(), Collectors.toSet())));