I need to get the data back similar to flatMap but it is not working in some scenario. Following are the details. Below is the pseudo code
class Employee {
String name;
int age;
}
Employee emp1 = new Employee("Peter",30);
Employee emp2 = new Employee("Bob",25);
Set<Employee> empSet1 = new HashSet();
empSet1.add(emp1);
empSet1.add(emp2);
Employee emp3 = new Employee("Jack",31);
Employee emp4 = new Employee("Laura",27);
Set<Employee> empSet2 = new HashSet();
empSet2.add(emp3);
empSet2.add(emp4);
Map<String,Set<Employee>> empLocationMap = new HashMap();
empLocationMap.put("location1",empSet1);
empLocationMap.put("location2",empSet2);
Set<Employee> empSet = getEmployeeSetForLocation("location1",empLocationMap);
private static Set getEmployeeSetForLocation(String location,Map<String,Set<Employee>> locationEmpMap) {
Object filteredObject = locationMap.entrySet().stream().filter(element-> element.getKey().equals(location)).flatMap(element-> Stream.of(element)).collect(Collectors.toSet());
return new HashSet(filteredObject );
}
The filteredObject in the method getEmployeeSetForLocation on inspection shows containing 1 element and that element is of type Set containing 2 elements. I want to know, what modification can I make in the above logic to flatten the structure further so that filteredObject shows a set with 2 elements. Any pointers will be helpful. I am using Java 8.
Regards
Use flatMap, mapping the stream of MapEntry to stream of Employee
Set<Employee> filteredObject = locationMap.entrySet().stream()
-- Here you have stream of Map.Entry, where value is employee
.filter(element -> element.getKey().equals(location))
-- And here is how to convert this stream to Stream<Employee>
.flatMap(s -> s.getValue().stream())
.collect(Collectors.toSet());
Use Set::stream) in .flatMap():
Object filteredObject = locationMap.entrySet().stream()
.filter(entry -> entry.getKey().equals(location))
.map(Map.Entry::getValue)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
You already organized your employees by location in a map. Why are you going to the trouble to stream and filter the map when you already have access to the employees per location in the location map? Why not just do the following:
Set<Employee> set = empLocationMap.get("location1");
It gives the exact same result had you done the streaming and filtering. And since location is being used as a key you can only have one set of employees per location anyway.
Related
I have classes Employee and Address as follows:
public static class Employee {
private List<Address> address;
// getters, etc.
}
public static class Address {
private String city;
// getters, etc.
}
I am learning streams, I am trying to iterate over the objects from list of list and create a set of unique values. I was able to get it working by using nested for loop. How can I convert below code to streams?
public static Set<String> getCityUniquName(List<Employee> emp){
Set<String> cityUniquName = new HashSet<>();
for(Employee e: emp){
List<Address> addList = e.getAddress();
for(Address add: addList){
cityUniquName.add(add.getCity());
}
}
return cityUniquName;
}
Since each Employee is associated with a collection of Address instances, you need to apply of the stream operations that allow to flatten the data, namely either flatMap(), or mapMulty().
Note that flatMap() expects a function producing a Stream,not a Collection like shown in another answer.
Stream.flatMap
To turn a Stream of Employee into a Stream of Address in the mapper function of flatMap() we need to extract the collection of addresses and create a stream over it.
The only thing left is to get the city name and collect the result into a set.
public static Set<String> getCityUniqueName(List<Employee> emp) {
return emp.stream()
.flatMap(e -> e.getAddress().stream())
.map(Address::getCity)
.collect(Collectors.toSet());
}
Stream.mapMulti
mapMulti() expects a BiConsumer, which in turn takes two arguments: stream element (having an initial type) and a Consumer of the resulting type. Every object offered to the consumer would appear in the resulting stream.
public static Set<String> getCityUniqueName1(List<Employee> emp) {
return emp.stream()
.<Address>mapMulti((e, c) -> e.getAddress().forEach(c))
.map(Address::getCity)
.collect(Collectors.toSet());
}
Use flatMap to flatten the Address list and then use map to get the city from each Address object and then collect into Set
Set<String> cityUniquName = emp.stream()
.map(Employee::getAddress)
.flatMap(List::stream)
.map(Address::getCity)
.collect(Collectors.toSet());
I can use the below snippet to retrieve the name if there is 1 entry in the list by retrieving element 0 in the list, however, each NameResponse can have several names (e.g. a first name, a middle name and a surname). How can I retrieve x names associated with one customer? There could be 20 names for argument's sake. I would like to implement using a stream since I am using Java 8, but I am unsure how to implement this. Any suggestions?
private List<String> getNames(Customer customer) {
List<NameResponse> nameResponses = new ArrayList<>();
NameResponse nameResponse = new NameResponse();
nameResponse.setName("Test Name");
nameResponses.add(nameResponse);
customer.setNames(nameResponses);
return List.of(customer.getNames().get(0).getName());
}
Customer class:
private List<NameResponse> names;
NameResponse class:
private String name;
Something like below assuming you have the appropriate getters:
return customer.getNames()
.stream()
.map(NameResponse::getName)
.collect(Collectors.toList());
You could do that using the map operator on the stream and then collect to output a list:
return customer.getNames().stream()
.map(nameResponse -> nameResponse.getName())
.collect(Collectors.toList()));
There are two classes:
public class Company {
String name;
int regId;
List<Employee> employee;
//getters and setters
//constructor
}
public class Employee {
int id;
String name;
// getters and setters
// constructor
}
There is a Map<String, Company>, String as key and object of type Company as value.
Company contains List of Employee as one of its member objects.
Now I have to iterate over the Map and get List of Employee from each of the Company objects and add it to a separate ArrayList object.
I am not sure how to get Employee List of Object by iterating through the map using the Streams concept and store it as a separate ArrayList.
Any suggestions on the same?
You can do:
Map<String, Company> stringCompanyMap = new HashMap<>();
stringCompanyMap.put("Dsa", new Company("Nike", 1, Arrays.asList(
new Employee(1, "Jordan"),
new Employee(2, "Mike"))));
stringCompanyMap.put("Sd", new Company("Adidas", 1, Arrays.asList(
new Employee(3, "Tyson"),
new Employee(4, "Russel"))));
List<Employee> allEmployees = stringCompanyMap.values().stream()
.flatMap(company -> company.employee.stream())
.collect(Collectors.toList());
System.out.println(allEmployees);
Output:
[Employee(id=3, name=Tyson), Employee(id=4, name=Russel), Employee(id=1, name=Jordan), Employee(id=2, name=Mike)]
It can be solved using this as well :-
List<List<Employee>> tempEmployeesList; //Temporary list for holding list of list of Employees
List<Employee> empList = new ArrayList<>(); //Actual list for holding list of all employees in the Companies present in map values.
tempEmployeesList = map.values().stream().map(x -> x.getEmployee()).collect(Collectors.toList());
tempEmployeesList.forEach(empList::addAll);
System.out.println(empList);
I am trying to learn how to use the lambda functions for sleeker code but struggling to make this work.
I have two lists. The "old" list is always shorter or the same length as the "updated list".
I want to take the objects from the "updated list" and overwrite the "stale objects" in the shorter "old list".
The lists have a unique field for each object.
For example, it is a bit like updating books in a library with new editions. The UUID (title+author) remains the same but the new object replaces the old on the shelf with a new book/object.
I know I could do it the "long way" and make a HashMap<MyUniqueFieldInMyObject, MyObject> and then take the new List<MyUpdatedObjects> and do the same.
I.e. Have HashMap<UniqueField, MyOldObject> and HashMap<UniqueField, MyUpdatedObject>, then iterate over the old objects with a pseudo "if updated objects have an entry with the same key, overwrite the value with the updated value"...
But...
Is there a "nicer" shorted way to do this with functional lambda statements?
I was thinking along the lines of:
List<MyObject> updatedList;
List<MyObject> oldList;
updatedList.forEach(MyObject -> {
String id = MyObject.getId();
if (oldList.stream().anyMatcher(MyObject ->
MyObject.getId().matches(id)) {
//Do the replacement here? If so...how?
}
}
Which is where I am lost!
Thanks for any guidance.
If you want to update the list in place rather than making a new list, you can use List.replaceAll:
oldList.replaceAll(old ->
updateListe.stream()
.filter(updated -> updated.getId().equals(old.getId())
.findFirst()
.orElse(old)
);
The main problem with this solution is that its complexity is O(size-of-old*size-of-updated). The approach you described as "long way" can protect you from having to iterate over the entire updated list for every entry in the old list:
// note that this will throw if there are multiple entries with the same id
Map<String, MyObject> updatedMap = updatedList.stream()
.collect(toMap(MyObject::getId, x->x));
oldList.replaceAll(old -> updatedMap.getOrDefault(old.getId(), old));
I recommend you to iterate over the oldList - the one you want to update. For each of the object iterated match the equivalent one by its id and replace it using Stream::map. If an object is not found, replace it with self (doesn't change the object) using Optional::orElse.
List<MyObject> newList = oldList
.stream() // Change values with map()
.map(old -> updatedList.stream() // Iterate each to find...
.filter(updated -> old.getId() == updated.getId()) // ...by the same id
.findFirst() // Get new one to replace
.orElse(old)) // Else keep the old one
.collect(Collectors.toList()); // Back to List
List<Foo> updatedList = List.of(new Foo(1L, "new name", "new desc."));
List<Foo> oldList = List.of(new Foo(1L, "old name", "old desc."));
List<Foo> collect = Stream.concat(updatedList.stream(), oldList.stream())
.collect(collectingAndThen(toMap(Foo::getId, identity(), Foo::merge),
map -> new ArrayList(map.values())));
System.out.println(collect);
This will print out:
[Foo{id=1, name='new name', details='old desc.'}]
In Foo::merge you can define which fields need update:
class Foo {
private Long id;
private String name;
private String details;
/*All args constructor*/
/*getters*/
public static Foo merge(Foo newFoo, Foo oldFoo) {
return new Foo(oldFoo.id, newFoo.name, oldFoo.details);
}
}
I think it's best to add the objects to be updated into a new list to avoid changing a list you are streaming on and then you can simply replace the old with the new list
private List<MyObject> update(List<MyObject> updatedList, List<MyObject> oldList) {
List<MyObject> newList = new ArrayList<>();
updatedList.forEach(object -> {
if (oldList.stream().anyMatch(old -> old.getUniqueId().equals(object.getUniqueId()))) {
newList.add(object);
}
}
return newList;
}
I have a list:
List<UserItem> userList = new ArrayList<>();
Where I add the following:
User father = new User();
father.setName("Peter");
UserItem parent = new UserItem(father, null);
userList.add(parent);
I then create another user:
User daughter = new User();
daughter.setName("Emma");
UserItem child = new UserItem(daughter, <OBJECT IN LIST WHERE NAME IS "PETER">);
userList.add(child);
However, I need to change the text wrapped in <> above to the parent object I added before (the father), specified by the name ("Peter" in this case).
How can I find an object in a List by a specific attribute? In my case, how can I find the object in the List that has the name "Peter"?
Please note that I add hundreds, sometimes thousands, of different users like this to the list. Each "parent" has a unique name.
The obvious solution would be iterating on the list and when the condition is met, return the object:
for (User user : userList) {
if ("peter".equals(user.getName()) {
return user;
}
}
And you can use filter (Java 8):
List<User> l = list.stream()
.filter(s -> "peter".equals(s.getUser()))
.collect(Collectors.toList());
to get a list with all "peter" users.
As suggested in comments, I think using Map is a better option here.
Answer to your question is here: https://stackoverflow.com/a/1385698/2068880
Stream peters = userList.stream().filter(p -> p.user.name.equals("Peter"))
However, as ruakh suggested, it's more reasonable to use Map<String, UserItem> to make it faster. Otherwise, it will iterate all the objects in the list to find users with name "Peter".
Other way with parallelStream with findAny
Optional<UserItem> optional = userList.parallelStream().findAny(p -> p.user.getName().equalsIgnoreCase("Peter"));
UserItem user = optional.isPresent() ? optional.get() : null;