Java Stream - String replace if match using Streams? - java

I have if condition where I am checking for String equality, if they match I store them in Set. Then I am looping through Set to check if ENUM3 value is present, if yes I replace that particular string with value String Java . I am using Iterator to loop and check for equality. I am looking for same functionality with use of streams where
1. I can loop through Set
2. Check for String equality
3. If ENUM3 found then replace with Java
4. Save all the matched String
Here is my code
{
Set<String> only = new HashSet<String>();
Iterator<Mark> itr = Marks.iterator();
while (itr.hasNext()) {
Mark find = itr.next();
if (ENUM1.getData().equals(find.search())||ENUM3.getData().equals(find.search())) {
only.add(find.search());
only = only.stream()
.map(macro -> macro.equals(ENUM3.getData()) ? "Java" : macro).collect(Collectors.toSet());
}
}
}
Here is what I tried using Stream
only = only.stream()
.map(macro -> macro.equals(ENUM3.getData()) ? "Java" : macro)
.collect(Collectors.toSet());

This should perform your entire operation:
Set<String> only = Marks.stream()
.map(Mark::search)
.filter(mark -> ENUM1.getData().equals(mark)
|| ENUM2.getData().equals(mark)
|| ENUM3.getData().equals(mark))
.map(macro -> macro.equals(ENUM3.getData()) ? "Java" : macro)
.collect(Collectors.toSet());
You seem to be unnecessarily doing these 2 (both of which are avoided in the pipeline above)
iterating through only in each iteration to replace ENUM3.getData() with Java
repeatedly calling Mark.search()

Related

Java 8 Filter List of POJO based on a condition

I need to extract a sublist where one of the attribute in list of POJO matches with the input at first occurrence.
class POJO {
String name;
#Nullable
String location;
}
Given a list of POJO, I need to get the List where location matches to input location. Once we find the first occurrence of the location, We need to extract the list from that point to end of list.
List<POJO> ans = Lists.newArrayList();
Iterator<POJO> iterator = input
.iterator();
while (iterator.hasNext()) {
POJO pojo = iterator.next();
if (Objects.nonNull(pojo.location)) {
String location = pojo.location
//Got the first location matching with input, From here, Get all the elements from List
if (inputLocation.equals(location) {
ans.add(pojo);
break;
}
}
}
while (iterator.hasNext()) {
POJO pojo = iterator.next();
if (Objects.nonNull(pojo.location)) {
ans.add(pojo);
}
}
Can anyone suggest any better apporach? (if possible using Streams)?
If you're on Java 9+, you can use dropWhile:
List<POJO> ans = input.stream().dropWhile(pojo -> pojo.location == null
|| !inputLocation.equals(pojo.location))
.filter(loc -> loc.location != null)
.collect(Collectors.toList());
On Java 8, you'd have to do it in two steps: find the index of your element and then pull the sub-list. This combines the two in one statement:
List<POJO> ans = input.subList(IntStream.range(0, input.size())
.filter(i -> input.get(i).location != null &&
input.get(i).location.equals("inputLocation"))
.findFirst()
.orElseGet(input::size), input.size());
Just be aware of subList's behavior. If you want to break the link from input, you may want to construct a new list from the result.
You can get the index of element using IntStream like:
int index = IntStream.range(0, answers.size())
.filter(i -> inputLocation.equals(answers.get(i).getLocation())).findFirst().orElse(-1);
Then extract a sub list using above index like below if index is greater than -1 :
answers.subList(index, answers.size())
You are looking for filter and dropWhile operations:
List<Pojo> list = List.of(new Pojo("a","USA"),new Pojo("b","UK"),
new Pojo("c","USA"), new Pojo("d","UK"));
List<Pojo> remaining = list.stream()
.filter(p->p.location()!=null)
.dropWhile(p1->!p1.location().equals("UK"))
.collect(Collectors.toList());
System.out.println(remaining);
Output:
[Pojo[name=b, location=UK], Pojo[name=c, location=USA], Pojo[name=d, location=UK]]
In Java9 dropWhile was added in streams that will drop elements until the condition is true, and then return the subsequent elements
https://docs.oracle.com/javase/9/docs/api/java/util/stream/Stream.html#dropWhile-java.util.function.Predicate-
input.stream().dropWhile(item -> ...).collect(...);
This is not an elegant solution compared to the rest of the answer but just a different flavor
Picked input data from this answer
Logic is once we encounter the first matching condition, set the flag, and use it in the stream filter stage.
Since we need variable to be effective final in lambda, final Boolean[] isFirstEncounter = {false}; one of the bad workaround.
Also following code should be use in sequential stream and not in parallel stream.
final Boolean[] isFirstEncounter = {false};
List<POJO> input = List.of(new POJO("a", "USA"), new POJO("b", "UK"), new POJO("c", "USA"), new POJO("d", "UK"));
List<POJO> ans = input.stream()
.filter(Objects::nonNull)
.filter(p -> {
if (!isFirstEncounter[0] && "UK".equals(p.location) {
isFirstEncounter[0] = true; // true means accept all the elements
}
return isFirstEncounter[0]; // false means filter all the elements
}
).collect(Collectors.toList());
System.out.println(ans);
//output
// [Solution.POJO(name=b, location=UK), Solution.POJO(name=c, location=USA), Solution.POJO(name=d, location=UK)]

How to iterate over a map of lists and return a single value with Java lambdas? [duplicate]

This question already has answers here:
Filter Java Stream to 1 and only 1 element
(24 answers)
Closed 2 years ago.
I have this brainer.
I need to iterate over a Map<UUID, List<Items>> groups and return a single value!
Can I do this with Lambda and how? (how do I get the id of the group that has an item of TYPE_12? The item of TYPE_12 is going to be only one across all groups)
Thanks in advance this is my for code:
String theId = null;
for(Map.Entry<UUID, List<Item>> group : groupsOfItems) {
for (Item item : group.getValue()) {
if (item.getType() == Types.TYPE_12) {
theId = group.getKey().toString();
break;
}
}
}
If you want to use functional style, you can create a stream from you map entry set, then expand it to get a stream of each item in underlying lists :
Optional<String> result = groupOfItems.entrySet().stream()
.flatMap(entry -> entry.getValue().stream())
.filter(item -> Types.TYPE_12.equals(item.getType))
.map(Item::getId)
.findAny();
result.ifPresent(id -> System.out.println("A match has been extracted: "+id));
As is, the functional style way is not more performant than the imperative one, but is more easily adaptable. Let's say you want to know if there's more than one match, you can replace findAny by a collector with a limit :
List<String> matchedIds = groupOfItems.entrySet().stream()
.flatMap(entry -> entry.getValue().stream())
.filter(item -> Types.TYPE_12.equals(item.getType))
.map(Item::getId)
.limit(2)
.collect(Collectors.toList());
if (matched.isEmpty()) System.out.println("No match found");
else if (matched.size() == 1) System.out.println("Exactly one match found: "+matched.get(0));
else System.out.println("At least two matches exist");
Stream usage also allow parallelization if necessary, by simply adding parallel() step to your pipeline.
Here is a solution using lambdas. The difference is this one does not use flatMap and throws an exception if the required value is not found (based on the question stating that there should be one and only one TYPE_12 in the whole value set of the map).
UUID result = groupsOfItems.entrySet().stream()
.filter(e -> e.getValue().stream()
.anyMatch(item -> item.getType() == TYPE.TYPE_12))
.findAny().orElseThrow().getKey();
String theId = null;
find:
for (Map.Entry<UUID, List<Item>> group : groupsOfItems.entrySet()) {
for (Item item : group.getValue()) {
if (item.getType() == Types.TYPE_12) {
theId = group.getKey().toString();
break find;
}
}
}
No don't use lambda for this.
Just use java labels to break out of both for loops after you found your entry. (Notice the find: before the first for loop. That's a label and once you call break find; it will break out of the block marked with that label)
Also you need to use .entrySet in the first for loop. Just passing groupsOfItems won't be enough

Are there ways set a local variable of a function inside stream java 8

I am new to java 8 and exploring different possibilities with it .I have a block of code that looks like this
String latestDeliveryAddress = "";
List<String> deliveryAddresses =
compositeEvent.getData().getShopperProfiles().getDeliveryAddressIDs();
List<Addresses> addresses =
compositeEvent.getData().getPersons().getContactInfo().getAddresses();
for(String deliveryAddress : deliveryAddresses){
for(Addresses address : addresses) {
if(deliveryAddress.equalsIgnoreCase(address.getId())){
if(address.getModified().compareTo(latestDeliveryAddress)>0){
latestDeliveryAddress = deliveryAddress;
}
}
}
}
I want to know if its even possible to do this using java stream and lambda expressions?
I can get to point where I can find the address but can't find a way to set local variable with it
Stream<String> stringStream = deliveryAddresses.stream().filter({
deliveryAddress -> addresses.stream().anyMatch(address -> address.getId().equalsIgnoreCase(deliveryAddress))
I am now trying to do this
Map<String,String> dates =
deliveryAddresses.stream()
.filter (
deliveryAddress -> addresses.stream()
.allMatch(address -> address.getId().equals(deliveryAddress)))
.collect(Collectors.toMap(addresses.get(0).getId(),addresses.get(0).getModified()));
but to Map method is throwing an error .
My final solution is this. It works but please let me know if there is a better way to do this
final String[] latestDeliveryAddress = {""};
deliveryAddresses.stream().forEach (
deliveryAddress -> addresses.stream().forEach(addresses1 ->
{
if(addresses1.getId().equals(deliveryAddress)){
if(addresses1.getModified().compareTo(deliveryAddress)<0){
latestDeliveryAddress[0] = addresses1.getId();
}
}
})
);
It’s generally recommend to study the Stream API and all of its operations. forEach should be the last resort or, well, if you try to rewrite a loop solution and only find forEach suitable, it may be a sign that it’s better to stay with the loop.
You can use
List<String> deliveryAddresses =
compositeEvent.getData().getShopperProfiles().getDeliveryAddressIDs();
List<Addresses> addresses =
compositeEvent.getData().getPersons().getContactInfo().getAddresses();
String latestDeliveryAddress = addresses.stream()
.filter(a -> deliveryAddresses.contains(a.getId()))
.max(Comparator.comparing(Addresses::getModified))
.map(Addresses::getId)
.orElseThrow(() -> new IllegalStateException("no matching address"));
If you truly want to match IDs case insensitive, you may use
Set<String> deliveryAddresses = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
deliveryAddresses.addAll(
compositeEvent.getData().getShopperProfiles().getDeliveryAddressIDs());
List<Addresses> addresses =
compositeEvent.getData().getPersons().getContactInfo().getAddresses();
String latestDeliveryAddress = addresses.stream()
.filter(a -> deliveryAddresses.contains(a.getId()))
.max(Comparator.comparing(Addresses::getModified))
.map(Addresses::getId)
.orElseThrow(() -> new IllegalStateException("no matching address"));
Generally, it’s recommended to use a Set for deliveryAddresses if the size can get large. For case sensitive matches, you can use a HashSet, which will provide an even faster lookup.
Though, it’s strange that you have to perform such match operation of two collections at this place. Whatever compositeEvent.getData() returns, should provide an API to get only the applicable addresses in the first place.
You can do it like this, however I am using a class attribute instead of a local variable inside a method:
private String variable;
void testStream() {
List<String> elements = List.of("Fire", "Wind", "Rain");
elements.stream().anyMatch(element -> {
variable = element;
return element.startsWith("Ra");
});
}

How to translate a lambda expression to normal code?

I was writing in java 1.8 and now I need to downgrade to 1.6. I have a problem writing this code in the normal way. Any suggestions?
return Stream.of(field.getText().split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())**
.map(Integer::valueOf)
.collect(Collectors.toSet());
That is what I tried:
if(!splitted[i].isEmpty()) {
set.add(Integer.valueOf(splitted[i]));
}
Set<Integer> results = new HashSet<>();
for (String part : field.getText().split(",")) {
String trimmed = part.trim();
if (trimmed.isEmpty()) continue;
results.add(Integer.valueOf(trimmed));
}
Your code does the following:
split the text of field along ','
call trim() on each of these
only work on all those which are not empty
parse this Strings to Integers
put them in a Set
this could be done like this:
EDIT: for-each is nicer and can be used since java 1.5
Set<Integer> set = new HashSet<>();
for(String split : field.getText().split(",")) {
split = split.trim();
if(!split.isEmpty()) {
set.add(Integer.valueOf(split));
}
}

Get unique Object from a Stream if present

Starting with a bean class MyBean with a single relevant propterty:
#Data
class MyBean {
private String myProperty;
}
Now I have got a set of these beans Set<MyBean> mySet usually with 0, 1, or 2 elements.
The question is: How do I retrieve myProperty from this set if it is equal for all elements, or else null. Preferably in a single line with effort O(n).
I found several examples to determine the boolean if all properties are equal. But I want to know the corresponding property.
Is there something smarter than this?
String uniqueProperty = mySet.stream().map(MyBean::getMyProperty).distinct().count() == 1
? mySet.stream().map(MyBean::getMyProperty).findAny().orElse(null)
: null;
Your version is already O(n).
It's possible to do this with a one-liner (although yours is too depending on how you write it).
String uniqueProperty = mySet.stream()
.map(MyBean::getMyProperty)
.map(Optional::ofNullable)
.reduce((a, b) -> a.equals(b) ? a : Optional.empty()) // Note: equals compares 2 Optionals here
.get() // unwraps first Optional layer
.orElse(null); // unwraps second layer
The only case this doesn't work for is when all property values are null. You cannot distinguish the set (null, null) from (null, "A") for example, they both return null.
Just a single iteration without the use of streams looks much better for such a use case :
Iterator<MyBean> iterator = mySet.iterator();
String uniqueProperty = iterator.next().getMyProperty();
while (iterator.hasNext()) {
if (!iterator.next().getMyProperty().equals(uniqueProperty)) {
uniqueProperty = null; // some default value possibly
break;
}
}
You use the findAny() first and check mySet again with allMatch() to require all items to match the first one in a filter():
String uniqueProperty = mySet.stream().findAny().map(MyBean::getMyProperty)
.filter(s -> mySet.stream().map(MyBean::getMyProperty).allMatch(s::equals))
.orElse(null);
The advantage of this is, that allMatch() will only evaluate all elements if necessary (docs).

Categories

Resources