Replace IFs and FOR LOOPs with STREAMs and LAMBDAs - java

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()));

Related

Filter stream distinct

I have a stream over a simple Java data class like:
class Developer{
private Long id;
private String name;
private Integer codePost;
private Integer codeLevel;
}
I would like to apply this filter to my stream :
if 2 dev has the same codePost with different codeExperience keep the dev with codeLevel = 5
keep all devs if Developers has the same codePost with the same codeLevel
Example
ID
name
codePost
codeExperience
1
Alan stonly
30
4
2
Peter Zola
20
4
3
Camilia Frim
30
5
4
Antonio Alcant
40
4
or in java
Developer dev1 = new Developer (1,"Alan stonly",30,4);
Developer dev2 = new Developer (2,"Peter Zola",20,4);
Developer dev3 = new Developer (3,"Camilia Frim ",30,5);
Developer dev4 = new Developer (4,"Antonio Alcant",40,4);
Stream<Developer> Developers = Stream.of(dev1, dev2, dev3 , dev4);
As mentioned in the comments, Collectors.toMap should be used here with the merge function (and optionally a map supplier, e.g. LinkedHashMap::new to keep insertion order):
Stream.of(dev1, dev2, dev3, dev4)
.collect(Collectors.toMap(
Developer::getCodePost,
dev -> dev,
(d1, d2) -> Stream.of(d1, d2)
.filter(d -> d.getCodeLevel() == 5)
.findFirst()
.orElse(d1),
LinkedHashMap::new // keep insertion order
))
.values()
.forEach(System.out::println);
The merge function may be implemented with ternary operator too:
(d1, d2) -> d1.getCodeLevel() == 5 ? d1 : d2.codeLevel() == 5 ? d2 : d1
Output:
Developer(id=3, name=Camilia Frim , codePost=30, codeLevel=5)
Developer(id=2, name=Peter Zola, codePost=20, codeLevel=4)
Developer(id=4, name=Antonio Alcant, codePost=40, codeLevel=4)
If the output needs to be sorted in another order, values() should be sorted as values().stream().sorted(DeveloperComparator) with a custom developer comparator, e.g. Comparator.comparingLong(Developer::getId) or Comparator.comparing(Developer::getName) etc.
Update
As the devs sharing the same codeLevel should NOT be filtered out, the following (a bit clumsy) solution is possible on the basis of Collectors.collectingAndThen and Collectors.groupingBy:
input list is grouped into a map of codePost to the list of developers
then the List<Developer> values in the map are filtered to keep the devs with max codeLevel
// added two more devs
Developer dev5 = new Developer (5L,"Donkey Hot",40,3);
Developer dev6 = new Developer (6L,"Miguel Servantes",40,4);
Stream.of(dev1, dev2, dev3, dev4, dev5, dev6)
.collect(Collectors.collectingAndThen(Collectors.groupingBy(
Developer::getCodePost
), map -> {
map.values()
.stream()
.filter(devs -> devs.size() > 1)
.forEach(devs -> {
int maxLevel = devs.stream()
.mapToInt(Developer::getCodeLevel)
.max().orElse(5);
devs.removeIf(x -> x.getCodeLevel() != maxLevel);
});
return map;
}))
.values()
.stream()
.flatMap(List::stream)
.sorted(Comparator.comparingLong(Developer::getId))
.forEach(System.out::println);
Output:
Developer(id=2, name=Peter Zola, codePost=20, codeLevel=4)
Developer(id=3, name=Camilia Frim , codePost=30, codeLevel=5)
Developer(id=4, name=Antonio Alcant, codePost=40, codeLevel=4)
Developer(id=6, name=Miguel Servantes, codePost=40, codeLevel=4)

lambdas in removeIf

HashSet<Integer> liczby = new HashSet<Integer>();
liczby.add(1);
liczby.add(2);
liczby.add(3);
liczby.add(4);
liczby.removeIf ((Integer any) -> { return liczby.contains(3); });
for(Iterator<Integer> it = liczby.iterator(); it.hasNext();){
Integer l2 = it.next();
System.out.println(l2);
}
I can't understand why removeIf deletes not only 3 but also 1 and 2 condition should be satisfied only by 3...
Think of it this way... as long as the set contains 3 it will keep removing hence the current outcome.
If you want to remove the 3 only then do this:
liczby.removeIf(e -> e == 3);
The lambda is applied on each element and check if 3 is present, if yes it will delete the element :
1 -> 3 is present -> delete 1
2 -> 3 is present -> delete 2
3 -> 3 is present -> delete 3
4 -> 3 is not present -> don't delete 4
To remove all 3 element, you can use one of those solutions :
liczby.removeIf(any -> any.equals(3));
//-------------------------------------------------
liczby.removeIf(new Integer(3)::equals);
//-------------------------------------------------
Integer toRemove = 3;
liczby.removeIf(toRemove::equals);
TIPS
Your lambda can be simplified as :
liczby.removeIf(any -> liczby.contains(3));
For-each loop might be easier to use for simple iteration :
for(Integer i : liczby){
System.out.println(i);
}

Java predicate - match against first predicate [duplicate]

I've just started playing with Java 8 lambdas and I'm trying to implement some of the things that I'm used to in functional languages.
For example, most functional languages have some kind of find function that operates on sequences, or lists that returns the first element, for which the predicate is true. The only way I can see to achieve this in Java 8 is:
lst.stream()
.filter(x -> x > 5)
.findFirst()
However this seems inefficient to me, as the filter will scan the whole list, at least to my understanding (which could be wrong). Is there a better way?
No, filter does not scan the whole stream. It's an intermediate operation, which returns a lazy stream (actually all intermediate operations return a lazy stream). To convince you, you can simply do the following test:
List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);
int a = list.stream()
.peek(num -> System.out.println("will filter " + num))
.filter(x -> x > 5)
.findFirst()
.get();
System.out.println(a);
Which outputs:
will filter 1
will filter 10
10
You see that only the two first elements of the stream are actually processed.
So you can go with your approach which is perfectly fine.
However this seems inefficient to me, as the filter will scan the whole list
No it won't - it will "break" as soon as the first element satisfying the predicate is found. You can read more about laziness in the stream package javadoc, in particular (emphasis mine):
Many stream operations, such as filtering, mapping, or duplicate removal, can be implemented lazily, exposing opportunities for optimization. For example, "find the first String with three consecutive vowels" need not examine all the input strings. Stream operations are divided into intermediate (Stream-producing) operations and terminal (value- or side-effect-producing) operations. Intermediate operations are always lazy.
return dataSource.getParkingLots()
.stream()
.filter(parkingLot -> Objects.equals(parkingLot.getId(), id))
.findFirst()
.orElse(null);
I had to filter out only one object from a list of objects. So i used this, hope it helps.
In addition to Alexis C's answer, If you are working with an array list, in which you are not sure whether the element you are searching for exists, use this.
Integer a = list.stream()
.peek(num -> System.out.println("will filter " + num))
.filter(x -> x > 5)
.findFirst()
.orElse(null);
Then you could simply check whether a is null.
Already answered by #AjaxLeung, but in comments and hard to find.
For check only
lst.stream()
.filter(x -> x > 5)
.findFirst()
.isPresent()
is simplified to
lst.stream()
.anyMatch(x -> x > 5)
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
// Stream is ~30 times slower for same operation...
public class StreamPerfTest {
int iterations = 100;
List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);
// 55 ms
#Test
public void stream() {
for (int i = 0; i < iterations; i++) {
Optional<Integer> result = list.stream()
.filter(x -> x > 5)
.findFirst();
System.out.println(result.orElse(null));
}
}
// 2 ms
#Test
public void loop() {
for (int i = 0; i < iterations; i++) {
Integer result = null;
for (Integer walk : list) {
if (walk > 5) {
result = walk;
break;
}
}
System.out.println(result);
}
}
}
A generic utility function with looping seems a lot cleaner to me:
static public <T> T find(List<T> elements, Predicate<T> p) {
for (T item : elements) if (p.test(item)) return item;
return null;
}
static public <T> T find(T[] elements, Predicate<T> p) {
for (T item : elements) if (p.test(item)) return item;
return null;
}
In use:
List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5);
Integer[] intArr = new Integer[]{1, 2, 3, 4, 5};
System.out.println(find(intList, i -> i % 2 == 0)); // 2
System.out.println(find(intArr, i -> i % 2 != 0)); // 1
System.out.println(find(intList, i -> i > 5)); // null
Improved One-Liner answer: If you are looking for a boolean return value, we can do it better by adding isPresent:
return dataSource.getParkingLots().stream().filter(parkingLot -> Objects.equals(parkingLot.getId(), id)).findFirst().isPresent();

How to find if list contains multiple predicates Java Lambda

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");
}

Library method to partition a collection by a predicate

I have a collection of objects that I would like to partition into two collections, one of which passes a predicate and one of which fails a predicate. I was hoping there would be a Guava method to do this, but the closest they come is filter, which doesn't give me the other collection.
I would image the signature of the method would be something like this:
public static <E> Pair<Collection<E>, Collection<E>> partition(Collection<E> source, Predicate<? super E> predicate)
I realize this is super fast to code myself, but I'm looking for an existing library method that does what I want.
Use Guava's Multimaps.index.
Here is an example, which partitions a list of words into two parts: those which have length > 3 and those that don't.
List<String> words = Arrays.asList("foo", "bar", "hello", "world");
ImmutableListMultimap<Boolean, String> partitionedMap = Multimaps.index(words, new Function<String, Boolean>(){
#Override
public Boolean apply(String input) {
return input.length() > 3;
}
});
System.out.println(partitionedMap);
prints:
false=[foo, bar], true=[hello, world]
With the new java 8 features(stream and lambda epressions), you could write:
List<String> words = Arrays.asList("foo", "bar", "hello", "world");
Map<Boolean, List<String>> partitionedMap =
words.stream().collect(
Collectors.partitioningBy(word -> word.length() > 3));
System.out.println(partitionedMap);
If you're using Eclipse Collections (formerly GS Collections), you can use the partition method on all RichIterables.
MutableList<Integer> integers = FastList.newListWith(-3, -2, -1, 0, 1, 2, 3);
PartitionMutableList<Integer> result = integers.partition(IntegerPredicates.isEven());
Assert.assertEquals(FastList.newListWith(-2, 0, 2), result.getSelected());
Assert.assertEquals(FastList.newListWith(-3, -1, 1, 3), result.getRejected());
The reason for using a custom type, PartitionMutableList, instead of Pair is to allow covariant return types for getSelected() and getRejected(). For example, partitioning a MutableCollection gives two collections instead of lists.
MutableCollection<Integer> integers = ...;
PartitionMutableCollection<Integer> result = integers.partition(IntegerPredicates.isEven());
MutableCollection<Integer> selected = result.getSelected();
If your collection isn't a RichIterable, you can still use the static utility in Eclipse Collections.
PartitionIterable<Integer> partitionIterable = Iterate.partition(integers, IntegerPredicates.isEven());
PartitionMutableList<Integer> partitionList = ListIterate.partition(integers, IntegerPredicates.isEven());
Note: I am a committer for Eclipse Collections.
seems like a good job for the new Java 12 Collectors::teeing:
var dividedStrings = Stream.of("foo", "hello", "bar", "world")
.collect(Collectors.teeing(
Collectors.filtering(s -> s.length() <= 3, Collectors.toList()),
Collectors.filtering(s -> s.length() > 3, Collectors.toList()),
List::of
));
System.out.println(dividedStrings.get(0)); //[foo, bar]
System.out.println(dividedStrings.get(1)); //[hello, world]
You can find more examples here.
Apache Commons Collections IterableUtils provides methods for partitioning Iterable objects based on one or more predicates. (Look for the partition(...) methods.)
Note that in case of limited set of known in advance partiotion keys it may be much more efficient just to iterate the collection once more for each partition key skipping all different-key items on each iteration. As this would not allocate many new objects for Garbage Collector.
LocalDate start = LocalDate.now().with(TemporalAdjusters.firstDayOfYear());
LocalDate endExclusive = LocalDate.now().plusYears(1);
List<LocalDate> daysCollection = Stream.iterate(start, date -> date.plusDays(1))
.limit(ChronoUnit.DAYS.between(start, endExclusive))
.collect(Collectors.toList());
List<DayOfWeek> keys = Arrays.asList(DayOfWeek.values());
for (DayOfWeek key : keys) {
int count = 0;
for (LocalDate day : daysCollection) {
if (key == day.getDayOfWeek()) {
++count;
}
}
System.out.println(String.format("%s: %d days in this year", key, count));
}
Another both GC-friendly and encapsulated approach is using Java 8 filtering wrapper streams around the original collection:
List<AbstractMap.SimpleEntry<DayOfWeek, Stream<LocalDate>>> partitions = keys.stream().map(
key -> new AbstractMap.SimpleEntry<>(
key, daysCollection.stream().filter(
day -> key == day.getDayOfWeek())))
.collect(Collectors.toList());
// partitions could be passed somewhere before being used
partitions.forEach(pair -> System.out.println(
String.format("%s: %d days in this year", pair.getKey(), pair.getValue().count())));
Both snippets print this:
MONDAY: 57 days in this year
TUESDAY: 57 days in this year
WEDNESDAY: 57 days in this year
THURSDAY: 57 days in this year
FRIDAY: 56 days in this year
SATURDAY: 56 days in this year
SUNDAY: 56 days in this year

Categories