I want to log error messages if the map rdsDesksByName.get(e.getKey()) does not have any value for the key.
How can I handle this within streams?
Currently I return createDeskWithScope(rdsDesksByName.get(e.getKey()),ds.outOfScope()), but how can I enhance this function to log an error message if rdsDesksByName does not have any value for the key, otherwise it should process as usual
List<Desk> desks = deskScopesByName.entrySet()
.stream()
.flatMap(e -> {
return deskScopesByName.get(e.getKey())
.stream()
.map(ds -> {
return createDeskWithScope(rdsDesksByName.get(e.getKey()), ds.outOfScope());
});
})
.collect(Collectors.toList());
In the flatmapping operation, find an object by the key and use the if-else condition:
If the object is null (no value for such key), log a message and return Stream.empty().
Otherwise, continue processing using createDeskWithScope method.
Here is how the code might look like (untested, but should give you an idea):
List<Desk> desks = deskScopesByName.entrySet()
.stream()
.flatMap(entry -> {
String key = entry.getKey();
Desk rdsDesk = rdsDesksByName.get(key);
if (rdsDesk == null) {
log.warn("There is no Desk present for {}", key);
return Stream.empty();
} else {
return entry.getValue()
.stream()
.map(ds -> createDeskWithScope(rdsDesk, ds.outOfScope()));
}
})
.collect(Collectors.toList());
I recommend wrapping the flatmapping operation inside a method.
Related
I am trying to convert an iterative block of code in Java 8 to functional. The functional approach is unable to find the matching message in the set shared.
List<Optional<Message>> allMessages = new ArrayList<>();
Set<Status> allStatuses = getAllStatuses();
//Iterative : Working
Set<StatusMessage> set = new HashSet<>(STATUS_MESSAGE.values());
for (StatusMessage statusMessage : set) {
for (Status status : statusMessage.getStatusAndInfo().keySet()) {
Optional<Message> message = MessageBuilder.createMessage(allStatuses, status, this::createMessage);
if (message.isPresent()) {
allMessages.add(message);
break;
}
}
}
//Functional : Not working - Never adds anything to the
//map even when matching status is present
STATUS_MESSAGE.values().stream()
.distinct()
.map(statusMessage -> statusMessage.getStatusAndInfo().keySet())
.flatMap(Collection::stream)
.map(key -> MessageBuilder.createMessage(allStatuses, key, this::createMessage))
.anyMatch(allMessages::add);
The MessageBuilder.createMessage looks like this:
Optional<Status> matchingStatus = statuses.stream()
.filter(matchingStatus::equals)
.findFirst();
System.out.println("Found : " + matchingStatus.toString());
return matchingStatus.flatMap(creator);
Also, for debugging purposes, how can I see what is happening at each step of the stream? The stack in the debugger in intellij wasn't showing anything in the stream.
This should do it:
STATUS_MESSAGE.values().stream()
.distinct()
.forEach(statusMessage ->
statusMessage.getStatusAndInfo().keySet().stream()
.map(status -> MessageBuilder.createMessage(allStatuses, status, this::createMessage))
.filter(Optional::isPresent)
.findFirst()
.ifPresent(allMessages::add)
);
UPDATE
To build the result list using toList instead of adding to a list:
List<Optional<Message>> allMessages = STATUS_MESSAGE.values().stream()
.distinct()
.flatMap(statusMessage ->
statusMessage.getStatusAndInfo().keySet().stream()
.map(status -> MessageBuilder.createMessage(allStatuses, status, this::createMessage))
.filter(Optional::isPresent)
.limit(1)
)
.collect(Collectors.toList());
This should be a comment, but it's too long...
Seems like your MessageBuilder.createMessage method is overcomplicated.
Check below a simplified and more readable version of the same logic:
if (allStatuses.contains(status)) {
System.out.println("Found : " + status.toString());
return creator.apply(status);
}
return Optional.empty();
You should not use forEach for accumulating operations, so this should be more idiomatic:
Function<StatusInfo, Optional<Message>> messageForStatus = statusInfo ->
statusInfo().keySet().stream()
.map(status -> MessageBuilder.createMessage(allStatuses, status, this::createMessage))
.filter(Optional::isPresent)
.findFirst()
.orElse(Optional.empty());
allMessages = STATUS_MESSAGE.values().stream()
.distinct()
.map(StatusMessage::getStatusAndInfo)
.map(messageForStatus)
.filter(Optional::isPresent)
.collect(toList());
As a side note, you have too many optionals, you may want to consider unwrapping some earlier, as a list of optionals may just as well be the list of only the present values.
I want this stream to throw an exception if there are more than 1 results that survive the filter. Is there any way to achieve this?
One idea is to use .count(), but I'll have to create the stream again.
Optional<Result> filteredresultSet = results.stream()
.filter(c -> c.equals("TOMATO))
.findAny();
You can replace findAny() by reduce() and throw an exception in the accumulator:
Optional<String> filteredresultSet = results.stream()
.filter("TOMATO"::equals)
.reduce((s1, s2) -> {
throw new MyException();
});
An alternative to find out exactly how many elements are left and still retrieve the first one is to use a small array or custom object to store the count and first element:
results.stream().filter("TOMATO"::equals).collect(Container::new, (c, e) -> {
c.count++;
if (c.value == null)
{
c.value = e;
}
}, (c1, c2) -> c1.count += c2.count);
This would return you an instance of Container, or null if the stream is empty. You can then check the count and return an Optional of the value.
A simple way is to use .limit(2) on the stream to short-circuit after two matches and then collect to a list or array.
List<Result> filtered = results.stream()
.filter(c -> c.equals("TOMATO"))
.limit(2) // we don't need to know if there are more than 2 matches
.collect(toList());
if (filtered.size() > 1) {
// throw or log
}
return filtered.isEmpty() ? Optional.empty() : Optional.of(filtered.get(0));
I am trying to modify a Map's keys based on conditional logic and struggling. I'm new to Java 8 streams API. Let's say I have a map like this:
Map<String, String> map = new HashMap<>();
map.put("PLACEHOLDER", "some_data1");
map.put("Google", "some_data2");
map.put("Facebook", "some_data3");
map.put("Microsoft", "some_data4");
When I would like to do is find the references of PLACEHOLDER and conditionally change that key to something else based on a boolean condition. I feel like it should be something like the below, but this doesn't even compile of course.
boolean condition = foo();
map = map.entrySet().stream().filter(entry -> "PLACEHOLDER".equals(entry.getKey()))
.map(key -> {
if (condition) {
return "Apple";
} else {
return "Netflix";
}
}).collect(Collectors.toMap(e -> e.getKey(), Map.Entry::getValue));
I found this question which kind of makes me think maybe I can't do this with Java 8 stream APIs. Hopefully someone better at this than me knows how to do this. Ideone link if you want to play with it.
You've filtered out all elements that aren't PLACEHOLDER. You need to add that filter logic to your map operation:
final Map<String, String> output = input.entrySet().stream()
.map(e -> {
if (!e.getKey().equals("PLACEHOLDER")) {
return e;
}
if (condition) {
return new AbstractMap.SimpleImmutableEntry<>("Apple", e.getValue());
}
return new AbstractMap.SimpleImmutableEntry<>("Netflix", e.getValue());
}).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
But as you are guaranteed to only have a single instance of PLACEHOLDER in the Map, you can just do
String placeholderData = input.remove("PLACEHOLDER");
if (placeholderData != null) {
input.put(condition ? "Apple" : "Netflix", placeholderData);
}
If you really want to do it using Streams, you just need to move the conditional logic to the collection phase, like that:
boolean condition = true;
map.entrySet().stream().collect(Collectors.toMap(
entry -> mapKey(entry.getKey(), condition), Map.Entry::getValue
));
where:
private static String mapKey(String key, boolean condition) {
if (!"PLACEHOLDER".equals(key)) {
return key;
}
if (condition) {
return "Apple";
} else {
return "Netflix";
}
}
However, the second part of Boris the Spider's answer using Map.remove and Map.put seems the best way to go.
I have the following usecase. I have a nested map with following structure:
Map<String, Map<WorkType, List<CostLineItem>>>
I have to iterate over the map and get the list of CLObject. If the single entry in the list has identifier as null. I have to generate the unique identifier per EnumType. I am not sure how to do it with streams? Following iteration logic will make clear what i want to accomplish
for(Map.Entry<String, Map<WorkType, List<CostLineItem>>> cliByWorkTypeIterator: clisByWorkType.entrySet()) {
Map<WorkType, List<CostLineItem>> entryValue = cliByWorkTypeIterator.getValue();
for(Map.Entry<WorkType, List<CostLineItem>>cliListIterator : entryValue.entrySet()) {
List<CostLineItem> clis = cliListIterator.getValue();
//if any CLI settlementNumber is zero this means we are in standard upload
//TODO: Should we use documentType here? Revisit this check while doing dispute file upload
if(clis.get(0).getSettlementNumber() == null) {
clis.forEach(f -> f.toBuilder().settlementNumber(UUID.randomUUID().toString()).build());
}
}
}
Nested loop makes the code bit boiler plate and dirty. Can someone help me with streams here?
You can use flatMap to iterate over all the List<CostLineItem> values of all the inner Maps.
clisByWorkType.values() // returns Collection<Map<WorkType, List<CostLineItem>>>
.stream() // returns Stream<Map<WorkType, List<CostLineItem>>>
.flatMap(v->v.values().stream()) // returns Stream<List<CostLineItem>>
.filter(clis -> clis.get(0).getSettlementNumber() == null) // filters that Stream
.forEach(clis -> {do whatever logic you need to perform on the List<CostLineItem>});
The following is equivalent to your for-loop:
clisByWorkType.entrySet()
.map(Map.Entry::getValue) // cliByWorkTypeIterator.getValue();
.flatMap(m -> m.entrySet().stream())
.map(Map.Entry::getValue)
.map(CostLineItem::getValue)
.filter(clis.get(0).getSettlementNumber() == null) //filter before flattening
.flatMap(List::stream)
.forEach(f -> f.toBuilder().settlementNumber(UUID.randomUUID().toString()).build());
clisByWorkType.values()
.stream()
.flatMap(e -> e.values().stream())
.filter(clis -> clis.get(0).getSettlementNumber() == null)
.flatMap(Collection::stream)
.forEach(f -> f.toBuilder().settlementNumber(UUID.randomUUID().toString()).build());
This is class Item.
public class Item {
String id;
String name;
Integer value;
Boolean status;
}
I have a Map(String, Set(Item)). I want to write a method that returns a Map(String, Set(Item)) such that only Items with status = false or status = null are present in the resulting map. I don't want a set-wide operation. I want the resulting subsets to only contain those Item that have status == Boolean.FALSE OR status == null. I don't want the entire set to get included or excluded. I only want those individual items included or excluded as per the status value.
Here's what I've tried so far.
public Map<String,Set<Item>> filterByStatus(Map<String, Set<Item>> changes) {
return changes.entrySet()
.stream()
.filter(p -> p.getValue()
.stream()
.anyMatch(item -> BooleanUtils.isNotTrue(item.isStatus())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
It didn't work! I get back the same results as I would if I didn't call filterByStatus.
UPDATE
public Map<String,Set<Item>> filterByStatus(Map<String, Set<Item>> changes) {
return changes.entrySet()
.stream()
.map(p -> p.getValue()
.stream()
.filter(item -> BooleanUtils.isNotTrue(item.isStatus())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
Result: There's an error in the collect(Collectors.toMap()) line saying Non-static method cannot be referenced from static context.
public Map<String, Set<Item>> filterByStatus(Map<String, Set<Item>> changes) {
return changes.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry ->
entry.getValue()
.stream()
.filter(item -> item.status == null || item.status == Boolean.FALSE)
.collect(Collectors.toSet())
));
}
Alternatively to a Stream solution, you may use
public Map<String, Set<Item>> filterByStatus(Map<String, Set<Item>> changes) {
Map<String, Set<Item>> result = new HashMap<>(changes);
result.replaceAll((key, set) -> {
set = new HashSet<>(set);
set.removeIf(item -> Boolean.TRUE.equals(item.status));
return set;
});
// if you want to remove empty sets afterwards:
result.values().removeIf(Set::isEmpty);
return result;
}
You could even do the operation in-place if the sets are mutable and you don’t need the old state anymore:
changes.values().forEach(set -> set.removeIf(item -> Boolean.TRUE.equals(item.status)));
// if you want to remove empty sets afterwards (and the map is mutable):
changes.values().removeIf(Set::isEmpty);
you could even remove these items, followed by removing the set only if they became empty due to the removal, in one statement:
changes.values().removeIf(set ->
set.removeIf(item -> Boolean.TRUE.equals(item.status)) && set.isEmpty());
Judging from your description you are looking for allMatch rather than anyMatch.
Currently you get all the sets which contain at least one non-True value. What you seem to want is having only sets that consist of non-True values only.
If you are rather looking for filtering out the negative values from all sets, you should use a mapping, not just filter, on the Map. In the mapping you could create copies of the sets with True values excluded.
This avoid include in new Map entrys with 0 items.
private Map<String,Set<Item>> filterByStatus(Map<String, Set<Item>> changes) {
return changes.entrySet()
.stream()
.filter(entry -> entry.getValue()
.stream()
.anyMatch(item -> item.status == null || item.status == false))
.collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue()
.stream()
.filter(item -> item.status == null || item.status == false)
.collect(Collectors.toSet()))
);
}