Optimizing Stream with lambda - java

What would be the best way of optimizing the below code further
public List<GroupDTOv2> getAllGroups(String xTenantId, CourseType courseType, String courseId, ContextType contextType, String contextId) throws AuthenticationException {
final List<GroupV2> groups = groupV2Repository.findByTenantIdAndCourseTypeAndCourseIdAndContextTypeAndContextId(xTenantId, courseType, courseId, contextType, contextId);
final RosterDTOv2 roster = rosterServiceFacade.getRoster(xTenantId, courseType, courseId, contextType, contextId);
final ArrayList<GroupDTOv2> groupDtoList=new ArrayList<>();
groups.stream().forEach(group -> {
final GroupDTOv2 groupDTO=new GroupDTOv2();
BeanUtils.copyProperties(group,groupDTO);
roster.getUsers().forEach(userDTOv2 -> {
if(userDTOv2.getUserId().equalsIgnoreCase(group.getTeamLeadId())){
groupDTO.setTeamLead(userDTOv2);
}
if(group.getTeamMemberIds().contains(userDTOv2.getUserId())){
groupDTO.getTeamMembers().add(userDTOv2);
}
});
groupDtoList.add(groupDTO);
});
return groupDtoList;
}
If we use stream twice to set the team-lead object and the team members i think the cost would be high,In that case what would be the mostappropriate way

You seem to have quadratic complexity1) for finding the matching leaders and team members. Consider putting those into a Map, mapping user IDs to actual users:
Map<String, UserDTOv2> userMap = roster.getUsers().stream()
.collect(Collectors.toMap(user -> user.getUserId().toLowerCase(),
user -> user));
Then, you do not need the inner loops and can instead just look up the leader and members. Also, instead of forEach and then groupDtoList.add, you could just usemap and collect.
List<GroupDTOv2> groupDtoList = groups.stream().map(group -> {
GroupDTOv2 groupDTO = new GroupDTOv2();
BeanUtils.copyProperties(group, groupDTO);
groupDTO.setTeamLead(userMap.get(group.getTeamLeadId().toLowerCase()));
group.getTeamMemberIds().forEach(id -> {
groupDTO.getTeamMembers().add(userMap.get(id.toLowerCase()));
});
return groupDTO;
}).collect(Collectors.toList());
Note, however, that the behaviour is not exactly the same as in your code. This assumes that (a) no two users have the same ID, and (b) that the roster will actually contain a matching user for the group's leader and each of its members. Yours would allow for duplicate IDs or no matching users and would just pick the last matching leader or omit members if no matching user can be found.
1) Not really quadratic, but O(n*m), with n being the number of groups and m the number of users.

Related

Appending to a list within a stream to a map

I'm attempting to consolidate multiple unnecessary web requests into a map, with the key connected to a location's ID, and the value being a list of products at that location.
The idea is to reduce the amount of requests to my flask server by creating a single request for each location, with a list of required products mapped to it.
I have tried to find others who has faced a similar problem using Java 8's streaming functionality, but I cannot find anyone who is trying to append to a list within a map.
Example;
public class Product {
public Integer productNumber();
public Integer locationNumber();
}
List<Product> products = ... (imagine many products in this list)
Map<Integer, List<Integer>> results = products.stream()
.collect(Collectors.toMap(p -> p.locationNumber, p -> Arrays.asList(p.productNumber));
Also, the second p parameter cannot access the current product in stream.
Because of this, I have been unable to test if I can append to a List when the location number matches a pre-existing list. I don't believe I can use Arrays.asList(), as I believe its immutable.
At the end, the map should have many product numbers in a list per location. Is it possible to append Integers to a pre-existing list within a map?
You may do it like so,
Map<Integer, List<Integer>> res = products.stream()
.collect(Collectors.groupingBy(Product::locationNumber,
Collectors.mapping(Product::productNumber, Collectors.toList())));
The java collectors API is pretty powerful and have lots of nice utility method to solve this.
public class Learn {
static class Product {
final Integer productNumber;
final Integer locationNumber;
Product(Integer productNumber, Integer locationNumber) {
this.productNumber = productNumber;
this.locationNumber = locationNumber;
}
Integer getProductNumber() {
return productNumber;
}
Integer getLocationNumber() {
return locationNumber;
}
}
public static Product of(int i, int j){
return new Product(i,j);
}
public static void main(String[] args) {
List productList = Arrays.asList(of(1,1),of(2,1),of(3,1),
of(7,2),of(8,2),of(9,2));
Map> results = productList.stream().collect(Collectors.groupingBy(Product::getLocationNumber,
Collectors.collectingAndThen(Collectors.toList(), pl->pl.stream().map(Product::getProductNumber).collect(Collectors.toList()))));
System.out.println(results);
}
}
So, what we are doing here is we are streaming the product list and grouping the stream by the location attribute but with the twist that we want to transform the collected list of products to list of product numbers.
Collectors.collectingAndThen is precisely the method for this which will let you specify a main collector toList() and a transformer function which is nothing but again a stream to map product to product numbers. IN java API doc the main collector and transformer are labeled as downstream collector and finisher.
Please go through the Collectors source code to have a complete understanding as to how all these different collectors are defined.

Java-8 filtering two sets with lambdas

I Have two sets of Roles. One from a database, another from UI. I also wrote comparing method of two Roles based on the giving name of the Role.
Problem: I'd like to extract role (keep one from database) if the compare value returns true using lambdas expression.
The matchingRoleNames work well, but I'm looking the best lambdas practices to do that without for loop.
This is my set result which worked fine:
Set<Role> rolesFromUI = user.getRolesFromUI();
Stream <Role> roleStreamFromDB = roleService.getAllRolesStreamFromDatabase();
Set<Role> matchingRoleNames = roleStreamFromDB.filter(role->
{
for(Role roleTmp:rolesFromUI){
if(roleTmp.compareTo(role)==0){
return true;
}
}
return false;
})
public int compareTo(Role role){
return this.roleName.compareTo(role.getRoleName());
}
Thank you
instead of using the for each loop inside the filter intermediate operation of the first stream, you can just stream over the other set and see if any of the elements in that match any element of the first stream.
i.e:
Set<Role> matchingRoleNames = roleStreamFromDB.filter(e -> rolesFromUI.stream().anyMatch( x -> x.compareTo(e) == 0))
.collect(Collectors.toSet());
As you are saying that you want to compare roles by name, you can do what you want by first collecting the names of the UI roles to a Set and then, when filtering the DB roles, just check if the name of the current role is in the set:
// Collect UI role names
Set<String> uiRoleNames = user.getRolesFromUI().stream()
.map(Role::getRoleName)
.collect(Collectors.toSet());
// Filter DB roles
Set<Role> filteredRoles = roleService.getAllRolesStreamFromDatabase()
.filter(role -> uiRoleNames.contains(role.getRoleName()))
.collect(Collectors.toSet());
This is both simple and efficient, because the Set returned by Collectors.toSet() is expected to be efficient for lookups (this means that its contains method will be O(1) for the average case).
If you want to be 100% sure that the returned set will be O(1) for contains, you might collect the UI role names as follows:
Set<String> uiRoleNames = user.getRolesFromUI().stream()
.map(Role::getRoleName)
.collect(Collectors.toCollection(HashSet::new));

lambda Java 8, how to map a list that is a filed of the result of filter operation

I have a catalog-like object hierarchy where every object has a name field.
class A {
List<A> list;
String name;
}
A{A{A{A...}AA},A{AAA},A{AAA}} // the depth is finite (~4)
I would like to provide a set of methods that return a list of child names (a a.getName()) of any parent element for a given name.
So for level 1 I have
a.getAs().stream().map(a1 -> a1.getName()).collect(Collectors.toList());
Level 2 I have already troubles with:
a1.getAs().stream().filter(a2 -> a2.getName() == name)
now I want to access the As and map them to their names but I don't know how
EDIT:
I have just realized that from the third level on it wouldn't be possible to find the list with just providing a single name. I would need a name for each level to be able to navigate to the node where the child list could be collected.
On one hand I could keep all the objects in one Set and access them with an id. They would still have references to each other. On the other hand by not knowing the root element I couldn't get the structure right.
I think I have to rethink the problem.
You can do it like this:
public static List<String> getChildNames(A node, String... path) {
Stream<A> s = node.getAs().stream();
for(String name: path)
s = s.filter(a -> a.getName().equals(name)).flatMap(a -> a.getAs().stream());
return s.map(A::getName).collect(Collectors.toList());
}
but if the names beneath an A node are unique, you should consider maintaining a Map<String,A>, mapping from child name to actual child, instead of a List<A>. That would make traversing a path via unique name/ID as simple as node.get(name1).get(name2). The logic of the method above would still be useful if you incorporate pattern matching, which doesn’t need to have a unique result.
public static List<String> getChildNames(A node, String... pathPatterns) {
Stream<A> s = node.getAs().stream();
for(String namePattern: pathPatterns) {
Pattern compiledPattern = Pattern.compile(namePattern);
s = s.filter( a -> compiledPattern.matcher(a.getName()).find())
.flatMap(a -> a.getAs().stream());
}
return s.map(A::getName).collect(Collectors.toList());
}
It works only for one level of the hierarchy:
public List<A> getSubcategoriesByParentName(A category, String name) {
return category.getSubcategories()
.stream()
.filter(subcategory -> subcategory.getName().equals(name))
.collect(Collectors.toList());
}
To achieve the next level, you could use a flatMap:
category.getSubcategories().stream()
.flatMap(s -> s.getSubcategories().stream())
.filter(s -> s.getName().equals(name))
.collect(Collectors.toList());
As you can see, there is a need of recursion, it is not a work for Stream API.
Of course, being aware of the depth, we could access to all levels (by using a flatMap(s -> s.getSubcategories().stream()) several times), but it will look ugly.

Which collections to use?

Suppose I want to store phone numbers of persons. Which kind of collection should I use for key value pairs? And it should be helpful for searching. The name may get repeated, so there may be the same name having different phone numbers.
In case you want to use key value pair. Good choice is to use Map instead of collection.
So what should that map store ?
As far it goes for key. First thing you want to assure is that your key is unique to avoid collisions.
class Person {
long uniqueID;
String name;
String lastname;
}
So we will use the uniqueID of Person for key.
What about value ?
In this case is harder. As the single Person can have many phone numbers. But for simple task lest assume that a person can have only one phone number. Then what you look is
class PhoneNumberRegistry {
Map<Long,String> phoneRegistry = new HashMap<>();
}
Where the long is taken from person. When you deal with Maps, you should implement the hashCode and equals methods.
Then your registry could look like
class PhoneNumberRegistry {
Map<Person,String> phoneRegistry = new HashMap<>();
}
In case when you want to store more then one number for person, you will need to change the type of value in the map.
You can use Set<String> to store multiple numbers that will not duplicate. But to have full control you should introduce new type that not only store the number but also what king of that number is.
class PhoneNumberRegistry {
Map<Person,HashSet<String>> phoneRegistry = new HashMap<>();
}
But then you will have to solve various problems like, what phone number should i return ?
Your problem has different solutions. For example, I'll go with a LIST: List<Person>, where Person is a class like this:
public class Person{
private String name;
private List<String> phoneNumbers;
// ...
}
For collections searching/filtering I suggest Guava Collections2.filter method.
You should use this:
Hashtable<String, ArrayList<String>> addressbook = new Hashtable<>();
ArrayList<String> persons = new ArrayList<String>()
persons.add("Tom Butterfly");
persons.add("Maria Wanderlust");
addressbook.put("+0490301234567", persons);
addressbook.put("+0490301234560", persons);
Hashtable are save to not have empty elements, the ArrayList is fast in collect small elements. Know that multiple persons with different names may have same numbers.
Know that 2 persons can have the same number and the same Name!
String name = "Tom Butterfly";
String[] array = addressbook.keySet().toArray(new String[] {});
int firstElement = Collections.binarySearch(Arrays.asList(array),
name, new Comparator<String>() {
#Override
public int compare(String top, String bottom) {
if (addressbook.get(top).contains(bottom)) {
return 0;
}
return -1;
}
});
System.out.println("Number is " + array[firstElement]);
Maybe
List<Pair<String, String> (for one number per person)
or
List<Pair<String, String[]> (for multiple numbers per person)
will fit your needs.

How to check if a string is in an arrayList inside a hashmap

So I have a hashmap
HashMap<String, ArrayList<String> gMap = new HashMap<String, ArrayList<String>();
And when someone creates a group, the group leader is added to the key of the hashmap and then all the users inside the group are added to the arraylist
gMap.get(groupLeader).add(user);
I'm trying to make it so that only group leaders are allowed to invite players, but if a player is not part of any group and invites another user then a group is automatically created and the player becomes the group leader.
So, normally I would just do this
for(ArrayList<String> list : gMap.values()){
if(list.contains(user)){
//do something since the player is not part of the list
}
}
But I cannot do that since there could be multiple arrayLists, so even though the user is not part of one arrayList it does not mean that they aren't inside another.
So I'm curios how I would check all the arrayLists and do something only if the user is not part of any of them.
You're actually creating a Multimap (a mapping of keys to collections of values) data structure here, and you'll find if you use one directly you won't have to reinvent the wheel as you are now. Guava defines a very nice Multimap interface, including an ArrayListMultimap that stores the data you want, and has a containsValue() method that cleanly does what you need. As mentioned by others, contains checks against a list are slow, you could use HashMultimap to do these contains checks more efficiently if you don't actually care about order, or LinkedHashMultimap if you really do.
And if you aren't already using Guava, you're missing out - it provides countless excellent utilities and good practices.
Use a boolean value that you only change the value of if a list contains the user - and break out of the loop if it does.
boolean notInAnyList = true;
for(ArrayList<String> list : gMap.values()){
if(list.contains(user)){
notInAnyList = false;
break; // No point in iterating any further
}
}
if (notInAnyList) {
// Handle user not being in any of the lists here
}
So here is a code sample
boolean userIsPartOfGroup = false
for(ArrayList<String> list : gMap.values()){
if(list.contains(user)){
userIsPartOfGroup = true;
break;
}
}
if(!userIsPartOfGroup){
gMap.add(user).add(new ArrayList(user));
}
First, I would use a set instead of a list (Java 7 syntax):
Map<String, Set<String> gMap = new HashMap<>();
When I understand your goal right, this may be a solution:
if (isLeader(groupLeader, gMap)) {
gMap.get(groupLeader).add(user);
} else if (isMember(groupLeader, gMap)) {
throw new UnsupportedOperationException("Member " + groupLeader + " is not a leader and must not invite " + user);
} else {
addNewGroup(groupLeader, gMap).add(user);
}
Here are the helper methods:
private boolean isLeader(String player, Map<String, Set<String> gMap) {
return gMap.keys().contains(player);
}
private boolean isMember(String player, Map<String, Set<String> gMap) {
for (Set<String> members : gMap.values()) {
if (members.contains(player)) {
return true;
}
}
return false;
}
private Set<String> addNewGroup(String leader, Map<String, Set<String> gMap) {
Set<String> players = new HashSet<>();
gmap.put(leader, players);
return players;
}

Categories

Resources