I get a list of object arrays that I need to group. The arrays contain different types of objects.
Here is an example:
List<Object[]> resultList = query.getResultList(); // call to DB
// Let's say the object array contains objects of type Student and Book
// and Student has a property Course.
// Now I want to group this list by Student.getCourse()
Map<String, Object[]> resultMap = resultList.stream().collect(Collectors.groupingBy(???));
What do I provide to the Collectors.groupingBy() method ?
The Student object is at index 0 in the object array.
groupingBy will by default give you a Map<String, List<Object[]>> in your case, because you will group arrays based on their student's course value.
So you need to group by the course value of the student in the array. The function you will apply will hence be:
o -> ((Student)o[0]).getCourse()
thus the grouping by implementation becomes:
Map<String, List<Object[]>> resultMap =
resultList.stream().collect(groupingBy(o -> ((Student)o[0]).getCourse()));
As an aside, you may want to use a class to have typed data and to avoid the cast, that could possibly throw an exception at runtime.
You could also perform the grouping by at the database level.
Related
I'm new to java stream API.
I have 2 lists, and if both their internal object ID matches wants to put some attributes to MAP.
Below is the implementation.
List<LookupMstEntity> examTypeDetails; //This list contains values init.
List<MarksMstEntity> marksDetailList; //This list contains values init.
//FYI above entities have lombok setter, getter, equals & hashcode.
Map<Long, Integer> marksDetailMap = new HashMap<>();
//need below implementation to changed using java 8.
for (LookupMstEntity examType : examTypeDetails) {
for (MarksMstEntity marks : marksDetailList) {
if (examType.getLookupId() == marks.getExamTypeId())
marksDetailMap.put(examType.getLookupId(), marks.getMarks());
}
}
Creating a set of lookupIds Set<Long> ids helps you to throw away duplicate values and to get rid of unnecessary checks.
Then you can filter marksDetailList accordingly with examTypeId values:
filter(m -> ids.contains(m.getExamTypeId()))
HashSet contains() method has constant time complexity O(1).
Try this:
Set<Long> ids = examTypeDetails.stream().map(LookupMstEntity::getLookupId)
.collect(Collectors.toCollection(HashSet::new));
Map<Long, Integer> marksDetailMap = marksDetailList.stream().filter(m -> ids.contains(m.getExamTypeId()))
.collect(Collectors.toMap(MarksMstEntity::getExamTypeId, MarksMstEntity::getMarks));
As long as you are looking for these with equal ID, it doesn't matter which ID you use then. I suggest you to start streaming the marksDetailList first since you need its getMarks(). The filtering method searches if there is a match in IDs. If so, collect the required key-values to the map.
Map<Long, Integer> marksDetailMap = marksDetailList.stream() // List<MarksMstEntity>
.filter(mark -> examTypeDetails.stream() // filtered those where ...
.map(LookupMstEntity::getLookupId) // ... the lookupId
.anyMatch(id -> id == mark.getExamTypeId())) // ... is present in the list
.collect(Collectors.toMap( // collected to Map ...
MarksMstEntity::getExamTypeId, // ... with ID as a key
MarksMstEntity::getMarks)); // ... and marks as a value
The .map(..).anyMatch(..) can be shrink into one:
.anyMatch(exam -> exam.getLookupId() == mark.getExamTypeId())
As stated in the comments, I'd rather go for the for-each iteration as you have already used for sake of brevity.
An observation:
First, your resultant map indicates that there can only be one match for ID types (otherwise you would have duplicate keys and the value would need to be a List or some other way of merging duplicate keys, not an Integer. So when you find the first one and insert it in the map, break out of the inner loop.
for (LookupMstEntity examType : examTypeDetails) {
for (MarksMstEntity marks : marksDetailList) {
if (examType.getLookupId() == marks.getExamTypeId()) {
marksDetailMap.put(examType.getLookupId(),
marks.getMarks());
// no need to keep on searching for this ID
break;
}
}
}
Also if your two classes were related by a parent class or a shared interface that had access to to the id, and the two classes were considered equal based on that id, then you could do something similar to this.
for (LookupMstEntity examType : examTypeDetails) {
int index = marksDetailList.indexOf(examType);
if (index > 0) {
marksDetailMap.put(examType.getLookupId(),
marksDetaiList.get(index).getMarks());
}
}
Of course the burden of locating the index is still there but it is now under the hood and you are relieved of that responsibility.
You can do it with O(N) time complexity using HashMap, first convert two lists into Map<Integer, LookupMstEntity> and Map<Integer, MarksMstEntity> with id as key
Map<Integer, LookupMstEntity> examTypes = examTypeDetails.stream()
.collect(Collectors.toMap(LookupMstEntity::getLookupId,
Function.identity()) //make sure you don't have any duplicate LookupMstEntity objects with same id
Map<Integer, MarksMstEntity> marks = marksDetailList.stream()
.collect(Collectors.toMap(MarksMstEntity::getExamTypeId,
Function.identity()) // make sure there are no duplicates
And then stream the examTypes map and then collect into map if MarksMstEntity exists with same id in marks map
Map<Integer, Integer> result = examTypes.entrySet()
.stream()
.map(entry->new AbstractMap.SimpleEntry<Integer, MarksMstEntity>(entry.getKey(), marks.get(entry.getKey())))
.filter(entry->entry.getValue()!=null)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
I have a bunch of objects stored in hashMap<Long,Person> i need to find the person object with a specific attribute without knowing its ID.
for example the person class:
public person{
long id;
String firstName;
String lastName;
String userName;
String password;
String address;
..
(around 7-10 attributes in total)
}
lets say i want to find the object with username = "mike". Is there any method to find it without actually iterating on the whole hash map like this :
for (Map.Entry<Long,Person> entry : map.entrySet()) {
if(entry.getValue().getUserName().equalsIgnoreCase("mike"));
the answers i found here was pretty old.
If you want speed and are always looking for one specific attribute, your best bet is to create another 'cache' hash-map keyed with that attribute.
The memory taken up will be insignificant for less than a million entries and the hash-map lookup will be much much faster than any other solution.
Alternatively you could put all search attributes into a single map (ie. names, and ids). Prefix the keys with something unique if you're concerned with collisions. Something like:
String ID_PREFIX = "^!^ID^!^";
String USERNAME_PREFIX = "^!^USERNAME^!^";
String FIRSTNAME_PREFIX = "^!^FIRSTNAME^!^";
Map<String,Person> personMap = new HashMap<String,Person>();
//add a person
void addPersonToMap(Person person)
{
personMap.put(ID_PREFIX+person.id, person);
personMap.put(USERNAME_PREFIX+person.username, person);
personMap.put(FIRSTNAME_PREFIX+person.firstname, person);
}
//search person
Person findPersonByID(long id)
{
return personMap.get(ID_PREFIX+id);
}
Person findPersonByUsername(String username)
{
return personMap.get(USERNAME_PREFIX+username);
}
//or a more generic version:
//Person foundPerson = findPersonByAttribute(FIRSTNAME_PREFIX, "mike");
Person findPersonByAttribute(String attr, String attr_value)
{
return personMap.get(attr+attr_value);
}
The above assumes that each attribute is unique amongst all the Persons. This might be true for ID and username, but the question specifies firstname=mike which is unlikely to be unique.
In that case you want to abstract with a list, so it would be more like this:
Map<String,List<Person>> personMap = new HashMap<String,List<Person>>();
//add a person
void addPersonToMap(Person person)
{
insertPersonIntoMap(ID_PREFIX+person.id, person);
insertPersonIntoMap(USERNAME_PREFIX+person.username, person);
insertPersonIntoMap(FIRSTNAME_PREFIX+person.firstname, person);
}
//note that List contains no duplicates, so can be called multiple times for the same person.
void insertPersonIntoMap(String key, Person person)
{
List<Person> personsList = personMap.get(key);
if(personsList==null)
personsList = new ArrayList<Person>();
personsList.add(person);
personMap.put(key,personsList);
}
//we know id is unique, so we can just get the only person in the list
Person findPersonByID(long id)
{
List<Person> personList = personMap.get(ID_PREFIX+id);
if(personList!=null)
return personList.get(0);
return null;
}
//get list of persons with firstname
List<Person> findPersonsByFirstName(String firstname)
{
return personMap.get(FIRSTNAME_PREFIX+firstname);
}
At that point you're really getting into a grab-bag design but still very efficient if you're not expecting millions of entries.
The best performance-wise method I can think of is to have another HashMap, with the key being the attribute you want to search for, and the value being a list of objects.
For your example this would be HashMap<String, List<Person>>, with the key being the username. The downside is that you have to maintain two maps.
Note: I've used a List<Person> as the value because we cannot guarantee that username is unique among all users. The same applies for any other field.
For example, to add a Person to this new map you could do:
Map<String, List<Person>> peopleByUsername = new HashMap<>();
// ...
Person p = ...;
peopleByUsername.computeIfAbsent(
p.getUsername(),
k -> new ArrayList<>())
.add(p);
Then, to return all people whose username is i.e. joesmith:
List<Person> matching = peopleByUsername.get("joesmith");
Getting one or a few entries from a volatile map
If the map you're operating on can change often and you only want to get a few entries then iterating over the map's entries is ok since you'd need space and time to build other structures or sort the data as well.
Getting many entries from a volatile map
If you need to get many entries from that map you might get better performance by either sorting the entries first (e.g. build a list and sort that) and then using binary search. Alternatively you could build an intermediate map that uses the attribute(s) you need to search for as its key.
Note, however, that both approaches at least need time so this only yields better performance when you're looking for many entries.
Getting entries multiple times from a "persistent" map
If your map and its valuies doesn't change (or not that often) you could maintain a map attribute -> person. This would mean some effort for the initial setup and updating the additional map (unless your data doesn't change) as well as some memory overhead but speeds up lookups tremendously later on. This is a worthwhile approach when you'd do very little "writes" compared to how often you do lookups and if you can spare the memory overhead (depends on how big those maps would be and how much memory you have to spare).
Consider one hashmap per alternate key.
This will have "high" setup cost,
but will result in quick retrieval by alternate key.
Setup the hashmap using the Long key value.
Run through the hashmap Person objects and create a second hashmap (HashMap<String, Person>) for which username is the key.
Perhaps, fill both hashmaps at the same time.
In your case,
you will end up with something like HashMap<Long, Person> idKeyedMap and HashMap<String, Person> usernameKeyedMap.
You can also put all the key values in the same map,
if you define the map as Map<Object, Person>.
Then,
when you add the
(id, person) pair,
you need to also add the (username, person) pair.
Caveat, this is not a great technique.
What is the best way to solve the problem?
There are many ways to tackle this as you can see in the answers and comments.
How is the Map is being used (and perhaps how it is created). If the Map is built from a select statement with the long id value from a column from a table we might think we should use HashMap<Long, Person>.
Another way to look at the problem is to consider usernames should also be unique (i.e. no two persons should ever share the same username). So instead create the map as a HashMap<String, Person>. With username as the key and the Person object as the value.
Using the latter:
Map<String, Person> users = new HashMap<>();
users = retrieveUsersFromDatabase(); // perform db select and build map
String username = "mike";
users.get(username).
This will be the fastest way to retrieve the object you want to find in a Map containing Person objects as its values.
You can simply convert Hashmap to List using:
List list = new ArrayList(map.values());
Now, you can iterate through the list object easily. This way you can search Hashmap values on any property of Person class not just limiting to firstname.
Only downside is you will end up creating a list object. But using stream api you can further improve code to convert Hashmap to list and iterate in single operation saving space and improved performance with parallel streams.
Sorting and finding of value object can be done by designing and using an appropriate Comparator class.
Comparator Class : Designing a Comparator with respect to a specific attribute can be done as follows:
class UserComparator implements Comparator<Person>{
#Override
public int compare(Person p1, Person p2) {
return p1.userName.compareTo(p2.userName);
}
}
Usage : Comparator designed above can be used as follows:
HashMap<Long, Person> personMap = new HashMap<Long, Person>();
.
.
.
ArrayList<Person> pAL = new ArrayList<Person>(personMap.values()); //create list of values
Collections.sort(pAL,new UserComparator()); // sort the list using comparator
Person p = new Person(); // create a dummy object
p.userName="mike"; // Only set the username
int i= Collections.binarySearch(pAL,p,new UserComparator()); // search the list using comparator
if(i>=0){
Person p1 = pAL.get(Collections.binarySearch(pAL,p,new UserComparator())); //Obtain object if username is present
}else{
System.out.println("Insertion point: "+ i); // Returns a negative value if username is not present
}
Say I have a class of Student contain in fields : firstName and surname
I then use this to create two lists
List<Student> classroomA = {["Ben","oreilly"], ["Jenna","Birch"]}
List<Student> classroomB = {["Alan","Messing"], ["Ben", "Mancini"], ["Helena","Wong"]}
How would I go about using these lists to get all Students with same name from the list :
List<Student> commonStudents = {["Ben","oreilly"],["Ben", "Mancini"]}
Would doing for loop on both list and doing a classroomA.getfirstName().equals(classroomB.getfirstName())
the only way ?
Use Java 8 Lambdas.
Below code gets all Bens from the list. If you want a particular field from the object (which is transformation), then use the map on filter stream.
List<Student> AllBens = classA.stream().filter(Objects::nonNull).
filter(k -> StringUtils.isNotEmpty(k.getName()) && k.getName().equalsIgnoreCase("Ben")).collect(Collectors.toList());
It seems that HashMap is limited to only one value, and I need a table of values like:
Joe(string) 25(Integer) 2.0(Double)
Steve(string) 41(Integer) 1.6(Double)
etc.
I want to store infomation similarly as in two-dimensional array, but I want it to have different variable types. I've look at various Map-implementing classes, but it seems that they only store value (assigned to a key), or two variables (I need at least three). What class should I use for this?
It sounds like you should be creating a separate class with a String field, an int field and a double field.
Then you can create a map with that as the value type, and whatever type you like as a key. For example:
Map<String, Person> map = new HashMap<>();
// What keys do you really want here?
map.put("foo", new Person("Joe", 25, 2.0));
map.put("bar", new Person("Steve", 41, 1.6));
Or it's possible that you don't even need a map at all at that point:
List<Person> list = new ArrayList<>();
list.add(new Person("Joe", 25, 2.0));
list.add(new Person("Steve", 41, 1.6));
Make class representing data you want to store, eg.
class Person {
String name;
//rest
}
and then make map like Map. Type of map is irrelevant
I would suggest that you create a simple class that stores the integer and double pair, which is then mapped to a String (I assume this is the desired outcome).
HashMap<String, Pair<Integer, Double>> map = new HashMap<String, Pair<Integer, Double>>;
map.put("Steve", new Pair<Integer, Double>(41, 1.6));
Where Pair is defined as
class Pair<T, K> {
public T val1;
public K val2;
public Pair(T val1, K val2){
this.val1 = val1;
this.val2 = val2;
}
}
There are a number of ways to do this.
The best way is the way that is suggested by Jon Skeet and #novy1234. Create a custom class that represents a person (or whatever the rows of the table are). Then use either a Map or a List of that class to represent the "table". (The Map allows you to select one of the fields / columns as a key ... if that is appropriate.)
So you might end up with a HashMap<String, Person> or an ArrayList<Person> ... where Person is your custom class.
A second way would be to represent each row as a Map<String,Object> so that (for example) "name" maps to "Joe", "age" maps to 25 and "height" maps to 2.0. (He is tall.) Then the table could be either a Map or a List of those maps.
A variation of the second way would be a Map<String, Map<String, Object>> where the keys of the outer map are each person's name, the keys of the inner map are the field names; e.g. "age" and "height".
However using a Map<String, Object> to represent a row is not a good Java solution when the set of columns is known. A custom class will use significantly less space than a Map (of any flavour), and a regular getter method is orders of magnitude faster that a Map.get(key) method. In addition, the Map.get(...) method is going to return you an Object that has to be cast to the expected type before it can be used. There is a risk that the typecast will fail at runtime, because you have (somehow) populated the row / map incorrectly.
You should only contemplate using a Map to represent a row in the table if the columns are not known at compile time, or if there are an unmanageably number of columns that are populated sparsely. (Neither is the case here ...)
So, which Map class should you use?
Your alternatives include HashMap, TreeMap, LinkedHashMap and ConcurrentHashMap. Each one has different properties and different target use-cases. However, if your table is small, and in the absence of specific requirements, it probably makes no real difference.
Make a node to store both the integer and double values?
Suppose I define the following statement, will an array of dictionaries (key-value pairs) be created, with all keys initialized to "stringvalue1" and values to stringvalue2?
String exampledatastruct[] = { "stringvalue1", stringvlaue2 };
Is the above statement a bad way of using?
The above Collection type is unsuitable for keyed access. Use a Map:
Map<String, String> map = new HashMap<>();
map.put("stringvalue1", stringvlaue2);
That would simply give you an array with two String elements in it. The first would be the string "stringvalue1", the second would be whatever String the variable stringvalue2 references. There'd be no relation between the two, other than the fact they're in the same array.
What you wrote is an array, not a dictionary. A usual representation of java dictionary is java.util.Map. For example:
Map<String, String> dictionary= new HashMap<String, String>();
you would put values in the dictionary in this way:
dictionary.put("key", "value");
and would get values from the dictionary in this way:
String value= dictionary.get("key");
You are creating a String array, not an associative array. You should use the java Map interface. Also, you can only have 1 key "stringvalue1".