Convert nested foreach loop into a stream - java

I'm trying to convert two foreach loops and an if statement into a stream.
Here is what I want to convert:
for (ViewFlightRouteAirportType associatedAirportType : etopsAndNonEtopsAssociatedAirportTypes) {
for (ViewFlightAirportDTO airport : flightRoute.getAirportsForType(associatedAirportType)) {
if ( airportIataCode.equals(airport.getIataCode()) ) {
addValueIfNotPresent(associatedFlights, associatedAirportType, flightData);
}
}
}
etopsAndNonEtopsAssociatedAirportTypes is an array.
airportIataCode is String
Here is what I wrote:
Arrays.stream(etopsAndNonEtopsAssociatedAirportTypes)
.forEach(associatedAirportType -> flightRoute.getAirportsForType(associatedAirportType)
.forEach(airport -> flightRoute.getAirportsForType(associatedAirportType)
.stream()
.filter(p -> p.equals(airport.getIataCode())).forEach(addValueIfNotPresent(associatedFlights,associatedAirportType,flightData);
It is not needed and doesn't work, but for me it looks ugly. How should it look like?

How about this? I expect your airport has a getViewFlightRouteAirportType() getter which makes this easier on us as we don't have to track the value of the current type of the etopsAndNonEtopsAssociatedAirportTypes array:
Arrays.stream(etopsAndNonEtopsAssociatedAirportTypes)
.map(flightRoute::getAirportsForType)
.flatMap(Collection::stream)
.filter(airport -> airportIataCode.equals(airport.getIataCode()))
.forEach(airport -> addValueIfNotPresent(associatedFlights, airport.getViewFlightRouteAirportType(), flightData));
EDIT: As discussed in the comments
for (ViewFlightRouteAirportType associatedAirportType : etopsAndNonEtopsAssociatedAirportTypes) {
flightRoute.getAirportsForType(associatedAirportType).stream()
.flatMap(Collection::stream)
.filter(airport -> airportIataCode.equals(airport.getIataCode()))
.forEach(airport -> addValueIfNotPresent(associatedFlights, associatedAirportType, flightData));
}

Here's an example. Since I don't have your classes, I tried to mock it this way. Imagine ll is your outer for loop data structure, and for each element in there you create another collection and loop through it. So, basically, we the list of those objects in the inner for loop as a whole collection so that we can filter them. That's where you can use flatMap which flattens a collection of collections. Then we just filter and collect those values.
List<String> ll = new ArrayList<>();
ll.stream()
.map(el-> Arrays.asList(el))
.flatMap(List::stream)
.filter(el->el.equals(str))
.collect(Collectors.toList());
Again, I want to mention, this is not the exact case of yours, but I think the situation is similar.

Related

Java 8: Filter list inside object with another list

I have a list named "foodList" which contains elements of type "Food". The object Food contains a List named "categories" of type "Category".
I am currently implementing a search algorithm to filter food by excluding certain categories.
Excluded Categories are stored inside a List named "excludedCategories".
How can I, using Java 8 and streams, filter the foodList by excluding Food objects whose categoryLists contain any element of the excludedCategories list?
Sample code with loops:
for (Food f: foodList)
{
for (Category c: f.categories)
{
if (excludedCategories.contains(c))
{
// REMOVE ITEM FROM foodList
}
}
}
Thank you!
Streams shouldn't be used to modify the List. Instead you should return a new List with only the appropriate elements in it. You could simply flip the logic a little and use filter:
foodList.stream().flatMap(e -> e.categories.stream())
.filter(c -> !excludedCategories.contains(c))
.collect(Collectors.toList());
However it would be much simpler to use the built in methods:
foodList.removeIf(e -> !Collections.disjoint(e.categories, excludedCategories));
Collections::disjoint
Collections::removeIf
Use the stream to filter the excluded categories as following
foodList.stream()
.filter(f -> f.categories.stream().noneMatch(c -> excludedCategories.contains(c)))
.collect(Collectors.toList());
you can do like this
foodList.stream().filter(f -> {
f.setCats(f.getCats().stream().filter(c -> (!excludedCategories.contains(c))).collect(Collectors.toList()));
return true;
}).collect(Collectors.toList()).forEach(System.out::println);
foodList.removeIf(f -> f.getCategories().stream().anyMatch(excludeCategories::contains));
you can use removeIf and anyMatch to achieve the desired result

Filter on nested list using Java 8 Stream API

I'm not able to convert below snippet in Java 8 stream format.
List<String> titles = Arrays.asList("First Name", "Last Name");
for (FirstClass first : firstClassList) {
for (SecondClass second : first.getSecondClassList()) {
for (ThirdClass third : second.getThirdClassList()) {
if(!titles.contains(third.getField())) {
second.getThirdClassList().remove(third);
}
}
}
}
I'm comparing third level nested list object against the input list of fields. If fields are not matching then I'm removing them from original list.
How can I achieve this using Java 8 syntax.
Edit: I want List of FirstClass to be returned.
I don't think streams win you anything in this case. All you do is iterate over the nested lists and either the enhanced for loop or forEach is more straightforward.
The improvements can come from using removeIf to modify the list and, possibly, from moving the rejection logic out of the loop:
Predicate<ThirdClass> reject = third -> !titles.contains(third.getField());
firstClassList.forEeach(first ->
first.getSecondClassList().forEach(second ->
second.getThirdClassList().removeIf(reject)
)
);
Get a stream of SecondClass objects by flattening the firstClassList and for each SecondClass get the filtered list of ThirdClass objects and set it back in the SecondClass
List<String> titles = Arrays.asList("First Name", "Last Name");
firstClassList
.stream()
.flatMap(firstClass -> firstClass.getSecondClassList().stream())
.forEach(secondClass -> {
List<ThirdClass> filteredThirdClasses = secondClass.getThirdClassList()
.stream()
.filter(thirdClass -> titles.contains(thirdClass.getField()))
.collect(toList());
secondClass.setThirdClassList(filteredThirdClasses);
}
);
First you can use Stream.map() and Stream.flatMap() to get a Stream containing a List of ThirdClass. To remove the items matching the condition you could use Collection.removeIf(), which removes all items from a collection matching the given condition:
firstClassList.stream() // Stream<FirstClass>
.map(FirstClass::getSecondClassList) // Stream<List<SecondClass>>
.flatMap(Collection::stream) // Stream<SecondClass>
.map(SecondClass::getThirdClassList) // Stream<List<ThirdClass>>
.forEach(thirdList -> thirdList.removeIf(third -> !titles.contains(third.getField())));
This modifies the original List, just like you did in your example. You then can use firstClassList as result for further processing.
Beside that I would recommend using a Set<String> instead of a List<String> for your titles, because it has a time complexity of O(1) instead of O(n):
Set<String> titles = new HashSet<>(Arrays.asList("First Name", "Last Name"));
Assuming the three levels in your data-structure are collections then a solution could be:
flatten the structure to a stream of the leaf level
filter by the required titles
final List<ThirdClassList> results = firstClassList
.stream()
.flatMap(FirstClassList::getSecondClassList)
.flatMap(FirstClassList::getThirdClassList)
.filter(third -> !titles.contains(third))
.collect(toList());
This will give you the leaf level objects to be removed, though this is only half the solution of course, you still want to remove them.
If you are the author of the list classes then perhaps you could have a reference from each third level to its 'parent' second level so removing is then a relatively simple second step:
results.forEach(third -> third.parent().remove(this));
where third.parent() returns the second level object.

Java 8 Streams for List iteration

I have a HashMap that contains List<Dto> and List<List<String>>:
Map<List<Dto>, List<List<String>>> mapData = new HashMap();
and an Arraylist<Dto>.
I want to iterate over this map, get the keys-key1, key2 etc and get the value out of it and set it to the Dto object and thereafter add it to a List. So i am able to successfully iterate using foreach and get it added to lists but not able to get it correctly done using Java 8. So i need some help on that. Here is the sample code
List<DTO> dtoList = new ArrayList();
DTO dto = new DTO();
mapData.entrySet().stream().filter(e->{
if(e.getKey().equals("key1")){
dto.setKey1(e.getValue())
}
if(e.getKey().equals("key2")){
dto.setKey2(e.getValue())
}
});
Here e.getValue() is from List<List<String>>()
so first thing is I need to iterate over it to set the value.
And second is I need to add dto to a Arraylist dtoList. So how to achieve this.
Basic Snippet that i tried without adding to a HashMap where List has keys, multiList has values and Dto list is where finally i add into
for(List<Dto> dtoList: column) {
if ("Key1".equalsIgnoreCase(column.getName())) {
index = dtoList.indexOf(column);
}
}
for(List<String> listoflists: multiList) {
if(listoflists.contains(index)) {
for(String s: listoflists) {
dto.setKey1(s);
}
dtoList.add(dto);
}
}
See https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html
Stream operations are divided into intermediate and terminal operations, and are combined to form stream pipelines. A stream pipeline consists of a source (such as a Collection, an array, a generator function, or an I/O channel); followed by zero or more intermediate operations such as Stream.filter or Stream.map; and a terminal operation such as Stream.forEach or Stream.reduce.
So in your snippet above, filter isn't really doing anything. To trigger it, you'd add a collect operation at the end. Notice that the filter lambda function needs to return a boolean for your code to compile in the first place.
mapData.entrySet().stream().filter(entry -> {
// do something here
return true;
}).collect(Collectors.toList());
Of course you don't need to abuse intermediate operations - or generate a bunch of new objects - for straightforward tasks, something like this should suffice:
mapData.entrySet().stream().forEach(entry -> {
// do something
});

Java 8: check for common elements in two lists using streams

I'm looking for a statment to check if there is any match in two lists of Users, according to Username.
List<User> a;
List<User> b;
for (User user : a) {
for (User newUser : b) {
if (user.getName().equals(newUser.getName())) {
}
}
}
How can I write this in java 8? Somthing like this:
List<User> intersect = a.stream()
.filter(User::getName)
.collect(Collectors.toList());
When User is correctly defined with a hashCode and equals (otherwise you might try TreeSet instead of HashSet), do set-operations:
Set<User> common = new HashSet<>(a);
common.retainAll(b);
If User.getName is not used for equality:
Set<User> common = new TreeSet<>(Comparator.comparing(User::getName));
common.addAll(a);
common.retainAll(b);
Two nested for loops on lists (also as streams) would have complexity O(N²),
whereas this is O(N.log N).
You can do something like below:
List<User> intersect = a.stream()
.filter(b::contains)
.collect(Collectors.toList());
You need to override equals and hashCode methods in User.
For optimization, you can convert b to HashSet first.
One way to do that using Stream.anyMatch(this would break within if) could be :
a.stream().filter(user -> b.stream().anyMatch(newUser -> user.getName().equals(newUser.getName())))
.map(User::getName)
.forEach(System.out::println); // logic inside 'if' here (print for e.g.)
If you want to repeat the loop(if logic) for all such matches :
a.forEach(user -> b.stream()
.filter(newUser -> user.getName().equals(newUser.getName()))
.map(newUser -> user.getName())
.forEach(System.out::println));

How to iterate nested for loops referring to parent elements using Java 8 streams?

I want to iterate nested lists using java8 streams, and extract some results of the lists on first match.
Unfortunately I have to also get a values from the parent content if a child element matches the filter.
How could I do this?
java7
Result result = new Result();
//find first match and pupulate the result object.
for (FirstNode first : response.getFirstNodes()) {
for (SndNode snd : first.getSndNodes()) {
if (snd.isValid()) {
result.setKey(first.getKey());
result.setContent(snd.getContent());
return;
}
}
}
java8
response.getFirstNodes().stream()
.flatMap(first -> first.getSndNodes())
.filter(snd -> snd.isValid())
.findFirst()
.ifPresent(???); //cannot access snd.getContent() here
When you need both values and want to use flatMap (as required when you want to perform a short-circuit operation like findFirst), you have to map to an object holding both values
response.getFirstNodes().stream()
.flatMap(first->first.getSndNodes().stream()
.map(snd->new AbstractMap.SimpleImmutableEntry<>(first, snd)))
.filter(e->e.getValue().isValid())
.findFirst().ifPresent(e-> {
result.setKey(e.getKey().getKey());
result.setContent(e.getValue().getContent());
});
In order to use standard classes only, I use a Map.Entry as Pair type whereas a real Pair type might look more concise.
In this specific use case, you can move the filter operation to the inner stream
response.getFirstNodes().stream()
.flatMap(first->first.getSndNodes().stream()
.filter(snd->snd.isValid())
.map(snd->new AbstractMap.SimpleImmutableEntry<>(first, snd)))
.findFirst().ifPresent(e-> {
result.setKey(e.getKey().getKey());
result.setContent(e.getValue().getContent());
});
which has the neat effect that only for the one matching item, a Map.Entry instance will be created (well, should as the current implementation is not as lazy as it should but even then it will still create lesser objects than with the first variant).
It should be like this:
Edit: Thanks Holger for pointing out that the code won't stop at the first valid FirstNode
response.getFirstNodes().stream()
.filter(it -> {it.getSndNodes().stream().filter(SndNode::isValid).findFirst(); return true;})
.findFirst()
.ifPresent(first -> first.getSndNodes().stream().filter(SndNode::isValid).findFirst().ifPresent(snd -> {
result.setKey(first.getKey());
result.setContent(snd.getContent());
}));
A test can be found here

Categories