Grouping a List by two fields in Java - java

I have a Student class. It contains the city, name and country fields.I have an ArrayList that contains Student objects. What I want to do is to group student objects with city and country fields. I can do it with one attribute like this
But how can i do both name and country? Do you have any idea? I am using java 7.
for (Student student: studentList) {
Long key = student.country;
if(map.containsKey(key)){
List<Student> list = map.get(key);
list.add(student);
}else{
List<Student> list = new ArrayList<Student>();
list.add(student);
map.put(key, list);
}
}

Use some key type which allows you to support multiple values, for example:
List<Object> key = Arrays.asList(city, country);
Replace your Long key ... line with this (and update map's type accordingly), e.g.
Map<List<Object>, List<Student>> map = new HashMap<>();
(List<Object> is actually not a great choice here, it's merely expedient: I'd use something like a specific CityAndCountry class, defined for example using Google's AutoValue)

Related

JAVA : Best performance-wise method to find an object stored in hashMap

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
}

How to get the common field from a list of object

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());

Iterate through hash maps and object

I am gradually learning how to use Hashmaps , I have a map where i would be adding
HashMap( student id, other student records as object() ) .
I am trying to retrieve the values with their keys.
Please see below.
HashMap<String, Object> studentinfo = new HashMap<String, Object>();
List<Object> lstObject = new ArrayList<Object>();
studentinfo.put("student height", 5.01); //Put data in studentinfo hashmap
lstObject.add(studentinfo);
// New items hashmap
HashMap<String, Object> items = new HashMap<String, Object>();
//Put object in items hashmap
items.put("student id 001",lstObject); //Store data for 1st id student with 001
items.put("student id 002",lstObject); //Store data for 2nd id student with 002
for (Map.Entry<String, Object> entry : items.entrySet()) {
if(entry.getKey().equals("student id 001")){
//Get student id for only student id 001
System.out.println(entry.getValue());
// When i print this out , this is what i get
// [{student height=5.01}]
}
}
How can i loop thru to get the value only
Like this, for example:
5.01 // Student id 001
To get the value 5.01 which is stored in a map for the key "student height" within a list that is stored in another map under the key "student id 001", you'll need to code the access to that information. The outer map itself only knows of values and if you define the value type to be Object you'd have to cast yourself.
A better design might be to replace that list with a Student object which has a property height and access that.
But just for information, if you'd define your map like Map<String, List<Map<String, Object>> you'd be able to directly access the lists and their elements as values, e.g. (not handling null for simplicity):
Double height = (Double)items.get("student id 001").get(0).get("student height");
Just to reiterate: this is awkward design and should be changed, you'll probably realize that whith increasing experience and knowledge.
Better:
class Student {
double height;
Student(double h) {
height = h;
}
}
Map<String, Student> students = ...;
students.put("student 001", new Student(5.01) );
double height = students.get("student 001").height;
Note that I left out a lot of code for simplicity reasons such as modifiers, getters/setters, null checks etc.
Another note (added here instead of a comment for formatting reasons):
items.put("student id 001",lstObject); //Store data for 1st id student with 001
items.put("student id 002",lstObject); //Store data for 2nd id student with 002
This will put the same list as the value for both keys, i.e. if you change the list elements you'll change it for both student ids.

Value of HashMap inside a Hashmap in Java

I have this hashmap of students which stores the id, name and last name.
So I created this :
Map<Interger, HashMap<String,String>> students = new HashMap<>();
where the second hashmap stores the name and lastname.
My goal is to look for a student in a swing application, I succeed in searching with id because it's the key of the first hashmap, but i'd like to use all of them like this:
So my question is : If I want to search by name or last name, how can i get the value of the first hashmap and put it in a new hashmap ?
You can iterate on the hashmap like this :
private int searchByName(String s) {
for(Map.Entry<Integer, HashMap<String, String>> entry : students.entrySet()) {
HashMap student = entry.getValue(); // Hashmap containing first and lastname
if (student.containsKey(s)) // If name match
return entry.getKey(); // Return the student ID
}
return 0; // Student not found
}
For the lastname just use containsValue(s) instead of containsKey(s)
You can use the Google Collections API Guava for that specifically a BiMap
A bimap (or "bidirectional map") is a map that preserves the
uniqueness of its values as well as that of its keys. This constraint
enables bimaps to support an "inverse view", which is another bimap
containing the same entries as this bimap but with reversed keys and
values.
With this you'will be able to search using first name and last name. This BiMap will be value to your first parent hashmap.
I am not sure if the data structure is the best for your use-case but answering to your question, you should try using values() method which would return you the collection of values of the Map
http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html#values()
Collection res = studensts.values();
Iterator<HashMap<String, String>> i = res.iterator();
Map<String,String> resM = null;
while(i.hasNext()){
resM = i.next();
}

how to implement below data structure in java using collection framework

I have below data structure like this
Country,StateID1 where StateID1 contains "City1","City2","City3" etc
Country,StateID2 where StateID2 contains "City1","City2","City3" etc
I know i can't use HashMap to implement above data structure because if i add StateID2 to same Country StateID1 will be replace with StateID2
for eg
map.put("1","1111");
map.put("1","2222");
output
key:value
1:2222`
i am facing hard time to figure out how to do this. I need some support from you guys
You need some wrapping object for storing your 'state' data. Then you can have a structure like this: Map<String, List<StateBean>>. This way you can handle a list of state for every country.
If data are just Strings use Map<String, List<String>>
You can have a Map<String, Set<String>>.
Store the StationIDs in an ArrayList object and add those object in a HashMap using key-value pair .. where key is the Country against the StationId ArrayList Object.
StateID1 = ["City1","City2"] // ArrayList
StateID2 = ["City1","City2"]
We could have the map as Country,ListOfStates
ListOfStates could be a list that contains StateIds
Or StateIds as a Map with StateId as key and list of cities as value
You could create a class for the same.
class YourClass
{
String country;
State state;
}
class State
{
Set<String> cities;
}
You can then use this class as a data structure. You don't really need to use Collections framework for the same.
OR
If you really want to do it using Collections, then you can use a combination of Country and StateId as the key, and the list of cities as the value in a Map. For ex:
String country = "1";
String state = "1";
String separator = "-" // You could use any separator
String key = country + separator + state;
Set<String> cities = new HashSet<String>();
cities.add("1");
cities.add("2");
Map<String, Set<String>> map = new HashMap<>();
map.put(key, cities);
So your key would be 1-1 and value would be 12.
Use can Data Structure map < String,vector < String >> , map < class T,vector < class U > >

Categories