5

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.

user2962142
  • 1,916
  • 7
  • 28
  • 42
  • 2
    is the map sorted? – XtremeBaumer Jan 18 '18 at 15:29
  • its sorted on the keys, not the values , the attribute is username ( or any other attribute, doesn't matter) – user2962142 Jan 18 '18 at 15:31
  • 1
    @XtremeBaumer OP wrote "stored in `hashMap`" so it won't be sorted. – Thomas Jan 18 '18 at 15:31
  • @Thomas you can sort `HashMap`s (https://stackoverflow.com/questions/780541/how-to-sort-a-hashmap-in-java). if you don't want to iterate the whole map every time, then you have to sort it every time too. i am not quite sure if that makes sense – XtremeBaumer Jan 18 '18 at 15:33
  • 1
    @XtremeBaumer no, you can't sort hashmaps. You can create a `TreeMap` or any other sorted map or a list based on the hashmap's values and sort that list though. – Thomas Jan 18 '18 at 15:35
  • @Thomas ahhh yeah nvm. didn't read the answer properly lol – XtremeBaumer Jan 18 '18 at 15:36

7 Answers7

5

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.

Vice
  • 312
  • 2
  • 5
  • Thanks, but the attribute are around 10, 10 hashmaps doesn't look like a good idea ? i don't fully understand the second method though, could you explain more please? – user2962142 Jan 18 '18 at 15:43
  • 1
    10 hashmaps is fine.... 100 or 1000 is fine, hashmaps are very cheap! I'll update my answer to explain the second method. – Vice Jan 18 '18 at 15:47
1

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");
fps
  • 33,623
  • 8
  • 55
  • 110
  • The downside is that it is not guarantee that `Person.userName` is unique. Please update your answer reflecting this issue like @George Andrews. – tsolakp Jan 18 '18 at 17:23
  • @tsolakp Thanks for pointing that out. I've edited as per your suggestion. – fps Jan 18 '18 at 17:30
0

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).

Thomas
  • 87,414
  • 12
  • 119
  • 157
  • what about the streams ? is it the same as iterating the whole map? – user2962142 Jan 18 '18 at 15:38
  • 1
    @user2962142 basically yes, though you could do it in parallel and thus be somewhat faster. But after all your code has to check elements in the map until the one you need is found. – Thomas Jan 18 '18 at 15:45
0

Consider one hashmap per alternate key. This will have "high" setup cost, but will result in quick retrieval by alternate key.

  1. Setup the hashmap using the Long key value.
  2. 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.

DwB
  • 37,124
  • 11
  • 56
  • 82
  • `Person.userName` might not be (and probably is not) unique. This will only put last `Person` with same `userName` into the map and is not what OP might want. – tsolakp Jan 18 '18 at 17:20
0

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.

George Andrews
  • 279
  • 3
  • 9
0

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.

Jayzb73
  • 32
  • 4
0

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

    }
Nithin
  • 748
  • 1
  • 10
  • 27