I am trying to figure out how to do multiple predicate on a single filter in Java Lambda but not had much luck.
I have a list of Strings
List<String> namesList = new ArrayList(){};
namesList.add("John");
namesList.add("Jane");
namesList.add("Smith");
namesList.add("Roger");
I have two if statements below in pseudologic that i want to test but not sure how to do it with lambda (i can do it old school method but trying to learn here).
if nameslist contains John and Roger
print "John & Roger"
if nameslist contains Jane and Smith
print "Jane Smith"
Using Java lambda how can i test for both scenarios on the list?
I would do it as follows:
if (namesList.stream()
.filter(x -> (x.equals("John") || x.equals(("Roger"))))
.collect(Collectors.toSet())
.size() == 2) {
System.out.print("John & Roger");
}
if (namesList.stream()
.filter(x -> (x.equals("Jane") || x.equals(("Smith"))))
.collect(Collectors.toSet())
.size() == 2) {
System.out.print("Hane Smith");
}
Don't use streams: Just convert English to code:
if (namesList.contains("John") && namesList.contains("Roger"))
System.out.println("John & Roger");
Or
if (namesList.containsAll(Arrays.asList("John", "Roger")))
System.out.println("John & Roger");
It's easier to read and will likely perform as well or better than the stream-based approach.
A lambda is not the right approach.
You could combine distinct and count:
if (namesList.stream()
.filter(s -> s.equals("John") || s.equals("Roger"))
.distinct()
.count() == 2) {
System.out.print("John & Roger");
}
Related
I want to optimalize my code. I used to use for loops and ifs, but I know that there is more faster ways than this. I am still pretty new to the lambdas and streams. For practise, I decided I replace my old codes with them.
I am curious, how this code below could change.
int counter = 0;
List<Integer> points = new ArrayList<>();
for (String name : names) {
for (Car car : cars) {
if (counter != 0) {
points.add(counter);
}
counter= 0;
for (Driver driver : car.getDriversWhoDrivesIt()) {
if (driver.getYearsInMotorsport() == 15) {
if (!(names.contains(driver.getName()))) {
filteredCars.remove(car);
counter= 0;
break;
}
}
if (driver.getYearsInMotorsport() == 7 ) {
counter+= 7;
}
if (driver.getYearsInMotorsport() == 3) {
counter+= 3;
}
}
}
}
So the task here is that there is a list (names) with the drivers which earlier the user define. After that I iterate through all the drivers that drive that cars and if somebody has exactly 15 years of experience and the user not selected it (in the names list), than the car that the driver drived got eliminated (removed from the filteredCar and no need to continue with that car).
So for example I have 3 cars and the drivers with exp:
car : Lewis(15years), Marco(4), Sebastian(15)
car: Max(15), Amanda(7)
car: Bob(15), George(3), Lando(15)
Than the user defines the names:
Lewis, Bob, Amanda, Lando, Max
If the driver has 15 years of exp and the user not defined it, than I dont want that car in my filteredCars.
And if all the 15 years of exp drivers defined I want to collect the other drivers exp(counter)
So in the end I want my filteredCar list like this:
2. car - 7
3.car - 3
Explanation:
The first car got eliminated, because the user not defined Sebastian who has 15 years.
The second and third car got promoted, because the user defined all the 15 years experienced drivers, and the second car got 7 point(cuz Amanda), and the third got 3 (George).
I tried to solve this problem with flatMap. But I am got stucked with the if-s. My problem is that I need to use inline if in lambdas but my if-s dont have else part.
names.stream()
.flatMap(name -> cars.stream()
.flatMap(car -> car.getDriversWhoDrivesIt().stream()
// .flatMap(driver -> driver.getYearsInMotorsport() == 5 ? ) //?? now what?
)
);
I hope somebody can help me with this.
Instead of the list of names I would advise defining a Set. For each Car filter drivers that have exactly 15 year of experience and then check whether all they are present in the user-difined set of names using allMatch() operation.
Then collect all the Car objects remained in the stream into a map using collector toMap():
Set<String> names = Set.of("Lewis", "Bob", "Amanda", "Lando", "Max");
List<Car> cars = List.of(
new Car("Car1", List.of(new Driver("Lewis", 15),
new Driver("Marco", 4),
new Driver("Sebastian", 15))
),
new Car("Car2", List.of(new Driver("Max", 15),
new Driver("Amanda", 7))
),
new Car("Car3", List.of(new Driver("Bob", 15),
new Driver("George", 3),
new Driver("Lando", 15))
)
);
Map<Car, Integer> pointByCar = cars.stream()
.filter(car -> car.getDrivers().stream()
.filter(driver -> driver.getYearsInMotorsport() == 15)
.map(Driver::getName)
.allMatch(names::contains)
)
.collect(Collectors.toMap(
Function.identity(),
car -> car.getDrivers().stream()
.mapToInt(Driver::getYearsInMotorsport)
.filter(i -> i == 7 || i == 3)
.sum()
));
pointByCar.forEach((car, points) -> System.out.println(car + " -> " + points));
Output:
Car{name='Car2'} -> 7
Car{name='Car3'} -> 3
A link to Online Demo
I know that there is more faster ways than this
Only faster to write and some may find it more readable.
In this example I'm removing the cars, that have a driver with 15 years experience and aren't listed in the names list, from the stream. Then I just collect the result into a map. Key is the car. Value is the sum of the drivers years - the drivers that have 15 years of experience.
Map<Car, Integer> filteredCars = cars.stream()
.filter(car -> car.driversWhoDrivesIt().stream().allMatch(driver -> driver.yearsInMotorsport() != 15 || names.contains(driver.name())))
.collect(Collectors.toMap(
Function.identity(),
car -> car.driversWhoDrivesIt().stream()
.mapToInt(Driver::yearsInMotorsport)
.filter(y -> y != 15)
.sum()));
I have two different list. I want to find and filter by field not on the other list. For example.
List<ObjectOne> List<ObjectTwo>
field | value field | value
{id=5, name="aaa"} {xId=4, text="aaa"}
{id=6, name="bbb"} {xId=6, text="bbb"}
{id=7, name="ccc"} {xId=5, text="ccc"}
If I want to filter one list, I am using org.springframework.cglib.core.CollectionUtils like that
CollectionUtils.filter(objectOne, s -> (
(ObjectOne) s).getId() == anyObject.getXId()
&& (ObjectOne) s).getName() == anyObject.getText());
But I want to compare two List, and I want to find noncontains value like that
objectOne = {id=5, name="aaa"} , {id=7, name="ccc"}
How am I filter with streamApi or any third-party libraries ?
noneMatch helps you here.
objectOnes.stream()
.filter(x -> objectTwos.stream()
.noneMatch(y -> y.text.equals(x.name) && y.xId == x.id))
.collect(Collectors.toList());
You can create a list of ObjectOne from the list of ObjectTwo as this:
List<ObjectOne> objectOne = listTwo.stream()
.map(x -> new ObjectOne(x.getxId(), x.getText()))
.collect(Collectors.toList());
And then you can use retainAll to find the common elements:
listOne.retainAll(objectOne);
if you wont modify the list of ObjectOne, then you can create a second list from listOne
List<ObjectOne> listOne2 = new ArrayList<>(listOne);
listOne2.retainAll(objectOne);
Note, this solution need to use hashcode and equals in ObjectOne.
I don't know how to do this with just one stream, but at least I got a solution for two.
List<ObjectOne> list1 = new ArrayList<>();
List<ObjectTwo> list2 = new ArrayList<>();
list1.stream()
.filter(o1 -> isInObjectTwoList(list2, o1))
.collect(Collectors.toList());
private boolean isInObjectTwoList(List<ObjectTwo> objectTwoList, ObjectOne o1) {
return objectTwoList.stream()
.filter(o2 -> o2.getText().equals(o1.getValue()))
.findAny()
.isPresent();
}
List<Account> list1 = new ArrayList<>();
List<Order> list2 = new ArrayList<>();
list1.stream().forEach(l1 -> list2.stream()
.forEach(l2 -> {
if (l1.getOrderId() == l2.getOrderId())
l1.setStatus(l2.getStatus());
}));
I was doing like this. It worked fine but now I have another situation where if orderId is not present in list2 set the status as "invalid" for that particular l1.
OrderId is unique in both the tables.
Hope this gives better understanding.
Edit Thanks for the edit of your question, it makes a lot more sense now. I think that this should do what you’re after:
list1.forEach(acc -> acc.setStatus(list2.stream()
.filter(o -> o.getOrderId() == acc.getOrderId())
.findAny()
.map(Order::getStatus)
.orElse("invalid")));
I am using Iterable.forEach() and then Collection.stream() and Optional.map() for a functional way of calculating the status to be set.
I'm trying to reach a lambda expression avoiding doing this:
for (OrderEntity o: onEntryL) {
for(GeoFenceEventEntity g: o.getGeoFenceEvent()){
if(null != g.getEndAt() && g.getDynamoGeofenceType().equalsIgnoreCase("WAREHOUSE")){
//all of them, get data
}
}
}
And on Lambda trying something like this (with errors):
List<OrderEntity> chargingL = onEntryL.stream()
.map(o->o.getGeoFenceEvent().stream()
.map(g->null != g.getEndAt() && g.getDynamoGeofenceType().equalsIgnoreCase("WAREHOUSE"))
.collect(Collectors.toList()));
Appreciate any help, regards.
OK, update for comment. Assuming you take the OrderEntry if any GeoFenceEventEntity meets your conditions then you can use
List<OrderEntity> chargingL = onEntryL
.stream()
.filter(o -> o.getGeoFenceEvent().stream().anyMatch(g -> null != g.getEndAt() && g.getDynamoGeofenceType().equalsIgnoreCase("WAREHOUSE")))
.collect(Collectors.toList());
I think you want flatMap with filter.
onEntryL.stream()
.map(OrderEntity::getGeoFenceEvent)
.flatMap(e -> e.stream().filter(g -> null != g.getEndAt() && g.getDynamoGeofenceType().equalsIgnoreCase("WAREHOUSE")))
.flatMap(g -> g.getData().stream())
.collect(Collectors.toList());
Hello I'm beginner when it comes to Java 8 so please be patient for me :)
I have a method that returns custom list of objects. What I need to do: I have got a list of disabledPaymentTypesStrings - and I don't know how many elements it has got. How can I change my code in order to not write every condition like !paymentType.getName().equalsIgnoreCase(disabledPaymentTypesStrings.get(1))? I would like to have somehow my whole list "disabledPaymentTypesStrings" placed here as a condition but I have no idea how to do that. Please give me some hints or advices :)
private List<PaymentType> listOfPaymentTypesForChangePayment(OrderPaymentTypeParameters paymentTypeParameters) {
List<String> disabledPaymentTypesStrings = newArrayList(Splitter.on(COMMA).split(systemUtils.getChangePaymentTypeDisabled()));
return paymentTypeSelector.availablePaymentTypesForChangePayment(paymentTypeParameters).stream()
.filter(paymentType ->
!paymentType.getName().equalsIgnoreCase(disabledPaymentTypesStrings.get(0)) &&
!paymentType.getName().equalsIgnoreCase(disabledPaymentTypesStrings.get(1)) &&
!paymentType.getName().equalsIgnoreCase(disabledPaymentTypesStrings.get(2)))
.collect(toList());
}
A stream approach could consist in the filter() to stream the List of String and keep PaymentType elements where paymentType.getName() don't match with any elements of the List of String :
return paymentTypeSelector.availablePaymentTypesForChangePayment(paymentTypeParameters)
.stream()
.filter(paymentType -> disabledPaymentTypesStrings.stream()
.allMatch(ref -> !ref.equalsIgnoreCase(paymentType.getName())))
.collect(toList());
But you could also compare Strings by using the same case. For example lowercase. It will simplify the filtering.
You can convert the reference list elements to lowercase :
List<String> disabledPaymentTypesStrings = newArrayList(Splitter.on(COMMA).split(systemUtils.getChangePaymentTypeDisabled()))
.stream()
.map(String::toLowerCase)
.collect(toList());
And you can so use List.contains() in the filter() :
return paymentTypeSelector.availablePaymentTypesForChangePayment(paymentTypeParameters)
.stream()
.filter(paymentType -> !disabledPaymentTypesStrings.contains(paymentType.getName().toLowerCase()))
.collect(toList());
Note that for big lists, using a Set would be more efficient.
Use contains(). But you have to think about case sensitivity ignoring
private List<PaymentType> listOfPaymentTypesForChangePayment(OrderPaymentTypeParameters paymentTypeParameters) {
List<String> disabledPaymentTypesStrings = newArrayList(Splitter.on(COMMA).split(systemUtils.getChangePaymentTypeDisabled()));
return paymentTypeSelector.availablePaymentTypesForChangePayment(paymentTypeParameters).stream()
.filter(paymentType -> !disabledPaymentTypesStrings.contains(paymentType)
.collect(toList());
}
Both the steps need to have values in common case(either in uppercase or in lowercase, I preferred lowercase)
List<String> disabledPaymentTypesStringsLowerCase = newArrayList(Splitter.on(COMMA).split(systemUtils.getChangePaymentTypeDisabled()))
.stream()
.map(String::toLowerCase)
.collect(toList());
return paymentTypeSelector.availablePaymentTypesForChangePayment(paymentTypeParameters)
.stream()
.map(paymentType -> paymentType.getName())
.map(String::toLowerCase)
.filter(disabledPaymentTypesStrings::contains)
.collect(toList());
This code can further be refactored if paymentType class is known, assuming class of paymentType is PaymentType code would look like below,
return paymentTypeSelector.availablePaymentTypesForChangePayment(paymentTypeParameters)
.stream()
.map(PaymentType::getName)
.map(String::toLowerCase)
.filter(disabledPaymentTypesStrings::contains)
.collect(toList());