java8 stream - filter map and aggregate - java-8

Lets say I have a list of rooms
List<Room> rooms;
And each room has a list of persons.
Using java8 streams I want to iterate the list of rooms, get all persons, execute some method on each node (doSomething()) and get a list of all the filtered objects.
Is this the best practive using java 8?
List<Asset> pesonsList= new ArrayList<>();
for (Room room : rooms)
room.getPersonsList().stream()
.filter(person -> person.isTall())
.forEach(person -> {
doSomething(person);
pesonsList.add(person);
});

You'd probably better do it like this:
List<Person> persons =
rooms.stream()
.flatMap(room -> room.getPersonsList().stream())
.filter(Person::isTall)
.peek(this::doSomething)
.collect(Collectors.toList());

In addition to the #JBNizet answer I'd suggest to replace getPersonsList() in Room class with persons() method: instead of
List<Person> getPersonsList() {...}
Create this method:
Stream<Person> persons() {...}
The first advantage is that streaming operations will become shorter:
List<Person> persons = rooms.stream()
.flatMap(Room::persons)
.filter(Person::isTall)
.peek(this::doSomething)
.collect(Collectors.toList());
The second advantage is that it might be more implementation independent, thus more efficient. Suppose that internally you store persons in the Set or in array. When having getPersonsList() method you will need to copy all the persons to the new list first. When having persons() method, you can easily create a stream directly from your internal data structure whatever it is. I believe, it's Java 8 way to return the stream of internal objects instead of some specific data structure were it List or array or whatever.

Related

How to convert a list of objects containing other objects into HashMap

hope you are doing well.
I have some list of other object which again contains two different objects. I have tried couple of ways using Collectors.groupingBy() but didn't get expected output. So help me to figure out solution to this.
Suppose I following three entities:
class FruitFlower {
Fruit fruit;
Flower flower;
}
class Fruit {
String name;
}
class Flower {
String name;
}
And I have a list like this
List<FruitFlower> fruitFlowers = new ArrayList<>();
fruitFlowers.addAll(
new FruitFlower(
new Fruit("Apple"),
new Flower("Rose")
),
new FruitFlower(
new Fruit("Banana"),
new Flower("Lily")
),
new FruitFlower(
new Fruit("Apple"),
new Flower("Sunflower")
),
new FruitFlower(
new Fruit("Banana"),
new Flower("Purple")
),
new FruitFlower(
new Fruit("Banana"),
new Flower("Rose")
)
);
Now, I want to order this list by some filter such that it returns HashMap like this
HashMap<Fruit,List<Flower>> hashMap=new HashMap<>();
Resultant Object:
{
"Apple":["Rose","Sunflower"]
"Banana":["Lily","Purple","Rose"]
}
Where
Expected output is a HadhMap that contains only unique values and a list of other values connected to that particular object. You can consider Objects of String for simplicity.
This is Java question and expected answer should be in Java 8 using stream API.
Thank you in advance.
As you suspected, groupingBy is the right approach.
fruitFlowers.stream().collect(Collectors.groupingBy(
i -> i.fruit,
Collectors.mapping(i -> i.flower, Collectors.toList())
));
The first parameter to groupingBy defines how you want to group them. In this case, we want to group by fruits.
The second parameter describes what the value of the resulting map should be. In this case, we want the values to be the list of flowers.
Note that this approach only works if groupingBy can deduce that two fruits are equivalent. This requires that the Fruit class contains an appropriate implementation of equals and hashCode.
If the Fruit class cannot be extended to add those methods, one can change the first lambda to i -> i.fruit.name, but that would make the resulting Map to have String keys instead of Fruit.

How to find the duplicates values in a Map with in a stream of list?

Say I have this array list:
List<Map<String,String>> fileDataList= new ArrayList<>();
fileDataList.stream().forEach(t->{
//find duplicate map values
});
The list may contain duplicate values like these:
[
{age:12,name:"john"},
{age:11,name:"Mary"},
{age:12,name:"john"}
]
Now,I would like to find the duplicate map values which match both name and age inside the stream without removing them.
I tried with HashSet, But I couldn't understand.
Holger in comments brings a point I missed. If your Map contains only of those name and age properties, you could simply do:
fileDataList.stream()
.distinct()
.collect(Collectors.toList())
And this will be enough. If, on the other hand, you have more properties and
what to filter by only some of them, you could use this utility:
fileDataList.stream()
.filter(distinctByKey(x -> Arrays.asList(x.get("name"), x.get("age")))
.collect(Collectors.toList());
You can transfre your list if Map to list of EntrySet.
List<Map.Entry<String, String>> entries = fileDataList.stream()
.flatMap(e -> e.entrySet().stream())
.collect(toList());

Java 8 Collections - Filtering on values in nested list

I'm new to Java 8 and the Stream API.
If I have a list of Employee objects:
List<Employee> employees;
public class Employee {
private String name;
private List<Project> involvedInProjects;
}
public class Project {
private int projectId;
}
I want to filter on employees beeing involved in certain projects, how would I go about doing this with the stream API in java 8?
Would it be easier if I had a Map where the key was an unique employeeID, instead of a List?
So you can get access to the nested list inside of a stream operation and then work with that. In this case we can use a nested stream as our predicate for the filter
employees.stream().filter(
employee -> employee.involvedInProjects.stream()
.anyMatch(proj -> proj.projectId == myTargetId ))
This will give you a stream of all of the employees that have at least one project matching you targetId. From here you can operate further on the stream or collect the stream into a list with .collect(Collectors.toList())
If you don't mind modifying the list in place, you can use removeIf and the predicate you will give as parameter will check if the projects where an employee is involved does not match the given id(s).
For instance,
employees.removeIf(e -> e.getInvolvedInProjects().stream().anyMatch(p -> p.getProjectId() == someId));
If the list does not support the removal of elements, grab the stream from the employees list and filter it with the opposite predicate (in this case you could use .noneMatch(p -> p.getProjectId() == someId)) as in removeIf and collect the result using Collectors.toList().

Java 8 list manipulation

I'm trying to do some basic map/filter operations on a list in a ListChangeListener.onChanged(Change<? extends Place>) method and I can get it working using the old-fashioned "iterate and do some ifs" way, but I wanted to try to write it using the stream() method from java 8. The commented part doesn't give the same result though, it fails to filter out the categories correctly (and yes, I have a working implementation of equals(Object) for Category
for (Place p : change.getAddedSubList()) {
if (!categories.contains(p.getCategory())) {
categories.add(p.getCategory());
}
}
// List<Category> addedCategories = change.getAddedSubList().stream()
//                      .map(Place::getCategory)
//                      .filter((c) -> { return !categories.contains(c); })
//   .collect(Collectors.toList());
// categories.addAll(addedCategories);
That's because in the first version, once you have added a category to the list, a subsequent occurrence of this category isn't added a second time: you have already added it to the list. The second version doesn't do the same thing. So you need to make sure categories are unique in the stream:
change.getAddedSubList().stream()
.map(Place::getCategory)
.distinct()
.filter(c -> !categories.contains(c))
.forEachOrdered(c -> categories.add(c));
Note that you also don't need to collect to a temporary list.
Duplicates in you stream may lead to duplicates in the categories list, when they are not contained in the categories list beforehand, since the filter method is applied for all items, before one of them is inserted.
One solution would be to insert a call to .distinct() in your Stream, another way to collect via Collectors.toSet().

Can LINQ ToArray return a strongly-typed array in this example?

I've contrived this example because it's an easily digested version of the actual problem I'm trying to solve. Here are the classes and their relationships.
First we have a Country class that contains a Dictionary of State objects indexed by a string (their name or abbreviation for example). The contents of the State class are irrelevant:
class Country
{
Dictionary<string, State> states;
}
class State { ... }
We also have a Company class which contains a Dictionary of zero or more BranchOffice objects also indexed by state names or abbreviations.
class Company
{
Dictionary<string, BranchOffice> branches;
}
class BranchOffice { ... }
The instances we're working with are one Country object and an array of Company objects:
Country usa;
Company companies[];
What I want is an array of the State objects which contain a branch. The LINQ I wrote is below. First it grabs all the companies which actually contain a branch, then joins to the list of states by comparing the keys of both lists.
The problem is that ToArray returns an anonymous type. I understand why anonymous types can't be cast to strong types. I'm trying to figure out whether I could change something to get back a strongly typed array. (And I'm open to suggestions about better ways to write the LINQ overall.)
I've tried casting to BranchOffice all over the place (up front, at list2, at the final select, and other less-likely candidates).
BranchOffice[] offices =
(from cm in companies
where cm.branches.Count > 0
select new {
list2 =
(from br in cm.branches
join st in usa.states on br.Key equals st.Key
select st.Value
)
}
).ToArray();
You can do:
select new MyClassOfSomeType {
..
)
For selection, you can give it a custom class type. You can also then use ToList. With ArrayList, if you need to keep it loosely typed, you can then make it strongly typed later using Cast<>, though only for any select result that doesn't generate an anonymous class.
HTH.
If i understand the problem correctly, the you want just the states that have office brances in them, not the branches too. If so, one posible linq is the following:
State[] offices =
(from cm in companies
where cm.branches.Count > 0
from br in cm.branches
join st in usa.states on br.Key equals st.Key
select st.Value
).Distinct().ToArray();
If you want both the states and the branches, then you will have to do a group by, and the result will be an IEnumerable>, which you can process after.
var statesAndBranches =
from cm in companies
where cm.branches.Count > 0
from br in cm.branches
join st in usa.states on br.Key equals st.Key
group br.Value by st.Value into g
select g;
Just one more thing, even though you have countries and branches declared as dictionaries, they are used as IEnumerable (from keyValuePair in dictionary) so you will not get any perf benefit form them.

Resources