Multi Key Hashmap - java

Recently I had an interview to save the huge count of employee details in DS.
I gave the solution as Hashmap with emp Id as key.
The follow up question was if the user wants to search based on name how to implement it. I suggested to use emp name as key and save all the employees with same name as Arraylist.
The next follow up question was tricky, need to create ONE map where user can search based on emp Id or emp name. How to implement this in map?
Implement it in memory efficient way.

This is a dirty solution (yes--very dirty, never do it on production!), but it will work if keys are of different types and one is not subtype of another (e.g. long and String). Put every employee by both keys, and get by provided key, either id or name:
Map<?, List<Employee>> map = new HashMap<>();
public void putEmployee(Employee e) {
map.put(e.id, Arrays.asList(e)); // put by id
if (!map.containsKey(e.name)) {
map.put(e.name, new ArrayList<>());
}
map.get(e.name).add(e); // put by name
}
public Employee getById(long id) {
return map.containsKey(id) ? map.get(id).get(0) : null;
}
public List<Employee> getByName(String name) {
return map.containsKey(name) ? map.get(name) : Collections.emptyList();
}
In production code, I'd use two separate maps or custom dictionary class.

I have come up with a solution. Please post your suggestions.
Step 1: Form the hashmap with emp id as key and emp object as value.
Step 2: For the same name create a list of emp id who matches the name Ex: name = 'XYZ' id={101,102,103,...}
Step 3: Insert this name as key and arraylist as value to the same map
Here we are not storing complete employee detail twice. Just trying to maintain a relationship between name and id. So comparatively it could be memory efficient.

This is a pretty easy question to answer: Just convert the IDs to Strings and store employees twice - once under the name and again under the id-as-string.
Your idea of using a List as the value is fine - for IDs, the list would be of size 1.
Note that it would be better to use two maps, because you only ever have one employee per ID and you wouldn't have to deal with a list of size 1 as a degenerate case, so:
Map<Integer, Employee> employeesById;
Map<String, Set<Employee>> employeesByName;
Especially note that you wouldn't use less memory by using just one map. In fact, you would use more memory than storing employees in separate maps for ID keys and name keys.

One way to do this would be to create a Key object that can be searched by either the name or the id:
public enum KeyType {
ID, NAME;
}
public class SearchKey {
private KeyType keyType;
private String value;
// constructor and getters snipped for brevity's sake
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SearchKey searchKey = (SearchKey) o;
return keyType == searchKey.keyType && value.equals(searchKey.value);
}
#Override
public int hashCode() {
int result = keyType.hashCode();
result = 31 * result + value.hashCode();
return result;
}
public class Directory {
private Map<SearchKey, Set<Employee>> directory = new HashMap<>();
public void addEmployee(Employee e) {
// Add search by id
directory.put
(new SearchKey(KeyType.ID, e.getId()), Collections.singleton(e));
// Add search by name
SearchKey searchByName = new SearchKey(KeyType.NAME, e.getName());
Set<Employee> employees = directory.get(searchByName);
if (employees == null) {
employees = new HashSet<>();
directory.put(searchByName, employees);
}
employees.add(e);
}
public Employee getById (String id) {
// Assume that the ID is unique
return directory.get(new SearchKey(KeyType.ID, id)).iterator().next();
}
public Set<Employee> getByName (String name) {
return directory.get(new SearchKey(KeyType.NAME, name));
}
}

Related

Retrieving Values from having a particular property from a Map using Java 8 Stream

I have a class UserCourseEntity with a property userId
#AllArgsConstructor
#Getter
#ToString
public static class UserCourseEntity {
private String userId;
}
And I have a map with UserCourseEntity objects as values.
public final Map<String, UserCourseEntity> userCourses;
Method getUserCoursesByUserID receives userId property of the UserCourseEntity as a parameter.
I want to check if there are values in the userCourses map having userId that matches the giving id in the case-insensitive manner (i.e. using equalsIgnoreCase()).
If there are such values, I need to store them into a list, and throw an exception otherwise.
I'm wonder is it possible to reimplement this code using streams?
public List<UserCourseEntity> getUserCoursesByUserID(String userId) {
List<UserCourseEntity> list = new ArrayList<>();
for (Map.Entry<String, UserCourseEntity> entry : userCourses.entrySet()) {
UserCourseEntity userCourseEntityValue = entry.getValue();
String key = entry.getKey();
boolean isExist = userCourseEntityValue.getUserId().equalsIgnoreCase(userId);
if (!isExist) {
continue;
} else {
if (userCourseEntityValue.getUserId().equalsIgnoreCase(userId)) {
list.add(userCourses.get(key));
}
}
}
if (list.isEmpty()) {
logger.error("No data found");
throw new SpecificException("No data found with the given details");
}
return list;
}
We can achieve it using streams.
For that, we need to create a stream over the map-entries. Filter the entries that have values with matching userId. That transform the stream by extracting the value from each entry and collect them into a list.
Note: there's no way to throw an exception from inside the stream, hence if-statement responsible for that remains on its place.
That's how it can be implemented:
public List<UserCourseEntity> getUserCoursesByUserID(String userId) {
List<UserCourseEntity> courses = userCourses.entrySet().stream()
.filter(entry -> entry.getValue().getUserId().equalsIgnoreCase(userId))
.map(Map.Entry::getValue)
.collect(Collectors.toList()); // or .toList() for Java 16+
if (courses.isEmpty()) {
logger.error("No data found");
throw new SpecificException("No data found with the given details");
}
return courses;
}
Sidenote: from the perspective of class design, it would be cleaner if you had a user object responsible for storing and manipulate information (retrieving and changing) regarding their courses.
And you can maintain a collection of user, for instance a HashMap associating id with a user. That would allow accessing a list of courses in a convenient way.
Iterating over the HashMaps entries ins't the best way of using it.

How to iterate over a map and return all the ones that match?

I have made various methods for someone to add a key which then includes various values from another created object.
I need to then allow a user to search using a method name which will then return all the people that match their search.
public Set findPerson(String aTrade)
{
Set<String> suitablePeople = new HashSet<>();
for (String test : this.contractors.keySet())
{
System.out.println(contractors.get(test));
if (contractors.containsValue(aTrade))
{
suitablePeople.add(test);
}
}
return suitablePeople;
}
I know this code is wrong but I'm just not sure how I can simply go through and find a value and return all the people that have this value within a range of values. For instance, their age, job, location.
Some assumptions, because your question is rather unclear:
contractors is a Map<String, ContractorData> field. Possibly ContractorData is some collection type (such as MyList<Contractor>), or named differently. The String represents a username.
aTrade is a string, and you want to search for it within the various ContractorData objects stored in your map. Then you want to return a collection of all username strings that are mapped to a ContractorData object that contains a trade that matches aTrade.
Whatever ContractorData might be, it has method containsValue(String) which returns true if the contractordata is considered a match. (If that was pseudocode and it's actually a List<String>, just .contains() would do the job. If it's something else you're going to have to elaborate in your question.
Then, there is no fast search available; maps allow you to do quick searches on their key (and not any particular property of their key, and not on their value or any particular property of their value). Thus, any search inherently implies you go through all the key/value mappings and check for each, individually, if it matches or not. If this is not an acceptable performance cost, you'd have to make another map, one that maps this property to something. This may have to be a multimap, and is considerably more complicated.
The performance cost is not important
Okay, then, just.. loop, but note that the .entrySet() gives you both key (which you'll need in case it's a match) and value (which you need to check if it matches), so that's considerably simpler.
var out = new ArrayList<String>();
for (var e : contracts.entrySet()) {
if (e.getValue().containsValue(aTrade)) out.add(e.getKey());
}
return out;
or if you prefer stream syntax:
return contracts.entrySet().stream()
.filter(e -> e.getValue().containsValue(aTrade))
.map(Map.Entry::getKey)
.toList();
The performance cost is important
Then it gets complicated. You'd need a single object that 'wraps' around at least two maps, and you need this because you want these maps to never go 'out of sync'. You need one map for each thing you want to have a find method for.
Thus, if you want a getTradesForUser(String username) as well as a findAllUsersWithTrade(String aTrade), you need two maps; one that maps users to trades, one that maps trades to users. In addition, you need the concept of a multimap: A map that maps one key to (potentially) more than one value.
You can use guava's MultiMaps (guava is a third party library with some useful stuff, such as multimaps), or, you roll your own, which is trivial:
given:
class ContractData {
private List<String> trades;
public boolean containsValue(String trade) {
return trades.contains(trade);
}
public List<String> getTrades() {
return trades;
}
}
then:
class TradesStore {
Map<String, ContractData> usersToTrades = new HashMap<>();
Map<String, List<String>> tradesToUsers = new HashMap<>();
public void put(String username, ContractData contract) {
usersToTrades.put(username, contract);
for (String trade : contract.getTrades()) {
tradesToUsers.computeIfAbsent(username, k -> new ArrayList<>()).add(username);
}
}
public Collection<String> getUsersForTrade(String trade) {
return tradesToUsers.getOrDefault(trade, List.of());
}
}
The getOrDefault method lets you specify a default value in case the trade isn't in the map. Thus, if you ask for 'get me all users which have trade [SOME_VALUE_NOBODY_IS_TRADING]', this returns an empty list (List.of() gives you an empty list), which is the right answer (null would be wrong - there IS an answer, and it is: Nobody. null is means: Unknown / irrelevant, and is therefore incorrect here).
The computeIfAbsent method just gets you the value associated with a key, but, if there is no such key/value mapping yet, you also give it the code required to make it. Here, we pass a function (k -> new ArrayList<>()) which says: just.. make a new arraylist first if I ask for a key that isn't yet in there, put it in the map, and then return that (k is the key, which we don't need to make the initial value).
Thus, computeIfAbsent and getOrDefault in combination make the concept of a multimap easy to write.
Assuming that your Map's values are instances of Contractor and the Contractor class has a Set<String> of trades (implied by the contains method call) and a getTrades() method that returns the list you could do it like this. Not certain what value the Map key would play in this.
get the values from the map and stream them.
filter only those Contractors that have the appropriate trade.
aggregate to a set of able contractors.
Set<Contractor> suitablePeople =
contractors.values()
.stream()
.filter(c->c.getTrades().contains(aTrade))
.collect(Collectors.toSet());
Note that a possible improvement would be to have a map like the following.
Map<String, Set<Contractors>> // where the key is the desired trade.
Then you could just get the Contractors with a single lookup up for each desired trade.
Set<Contractors> plumbers = mapByTrade.get("plumbers"); // all done.
Here is how you would set it up. The Contractor class is at the end. It takes a name and a variable array of trades.
Set<Contractor> contractors = Set.of(
new Contractor("Acme", "plumbing", "electrical", "masonry", "roofing", "carpet"),
new Contractor("Joe's plumbing", "plumbing"),
new Contractor("Smith", "HVAC", "electrical"),
new Contractor("Ace", "electrical"));
Then, iterate over the list of contractors to create the map. Then those are grouped by trade, and each contractor that matches is put in the associated set for that trade.
Map<String,Set<Contractor>> mapByTrade = new HashMap<>();
for (Contractor c : contractors) {
for (String trade : c.getTrades()) {
mapByTrade.computeIfAbsent(trade, v->new HashSet<>()).add(c);
}
}
And here it is in action.
Set<Contractor> plumbers = mapByTrade.get("plumbing");
System.out.println(plumbers);
System.out.println(mapByTrade.get("electrical"));
System.out.println(mapByTrade.get("HVAC"));
prints
[Acme, Joe's plumbing]
[Ace, Acme, Smith]
[Smith]
And here is the Contractor class.
class Contractor {
private Set<String> trades;
private String name;
#Override
public int hashCode() {
return name.hashCode();
}
#Override
public boolean equals(Object ob) {
if (ob == name) {
return true;
}
if (ob == null) {
return false;
}
if (ob instanceof Contractor) {
return ((Contractor)ob).name.equals(this.name);
}
return false;
}
public Contractor(String name, String...trades) {
this.name = name;
this.trades = new HashSet<>(Arrays.asList(trades));
}
public Set<String> getTrades() {
return trades;
}
#Override
public String toString() {
return name;
}
}

Searching through an ArrayList to find an object with a certain field value

I have the array list ArrayList doctors which stores objects that store the details of some doctors. Each doctor has a unique id field. Is there a way to search through the array list for a doctor with a certain ID value?
You can just use streams on the ArrayList like so:
Optional<Doctor> getUniqueDoctorById(List<Doctor> list, String id) {
return list.stream()
.filter(doctor -> doctor.getId().equals(id))
.findFirst();
}
Here you see streaming the list and filtering all doctors where doctor id equals the id you are searching for.
Try something like this.
private static Doctor queryDoctorById(List<Doctor> doctors, int id) {
Doctor doctor = null;
for (Doctor doc : doctors) {
if (doc.id == id) {
doctor = doc;
break;
}
}
return doctor;
}
// is a sample object representing doctor
protected static class Doctor {
public String name;
public int id;
}
Simplest, but probably not the most efficient solution, I'm assuming you set up "doctors" with setters/getters for all the fields, otherwise you would use d.id instead of d.getId() but that's not a good practice:
I'm also assuming ID might contain letters and numbers and is represented as a string. If it is a number, you would use == instead of .equals
public Doctor findDoctorById(desiredID) {
for(Doctor d : doctors) {
if (d.getId().equals(desiredID) {
return d;
}
}
System.out.println("No doctors with that ID!");
return null;
}

How to write my own comparator class in java?

I didn't find proper solution for the below scenario. I have employee names and location. In each location many employees can work.
Example: assume that employee names are unique so I consider it as a key and value as location.
TreeMap<String,String> t=new TreeMap<String,String>();
t.put(mike, Houston);
t.put(arian, Houston);
t.put(John, Atlanta);
Well my scenario is i have to write my own comparator where location is sorted first and when there are multiple locations of same name then they need to be sorted by employees. Any kind of help is appreciated.
you need a structure, and compareTo:
public class EmpLoc implements Comparable<EmpLoc> {
String employee;
String location;
public EmpLoc (String _employee, String _location)
{
employee=_employee;
location=_location;
}
#Override
public int compareTo(EmpLoc other)
{
int last = this.location.compareTo(other.location);
return last == 0 ? this.employee.compareTo(other.employee) : last;
}
}
The problem is in your data structure. TreeMap ensure your keys are always sorted in an order, but your key doesn't have full information you need to sort. Instead what you need is probably
TreeSet<Employee> employees = new TreeSet<>(employeeComparator);
where Employee is:
public class Employee {
private String name;
private String location;
/* getters & setters omitted */
}
Now you can create a comparator for Employee
You can use similar structure:
Map<String, List<String>> map = new TreeMap<>(<your_own_comparator_for_locations_or_default_one>);
This is Multimap, and this is implementation by conventional means, but also there are third-party implementation, e.g. Guava. Guava has some sorted, synchronized and immutable implementations of multimaps, you can use them by default or to see how to do some things.
You can put values like below:
public void putEmployees(String location, String employee) {
List<String> employees = map.get(location);
if (employee == null) {
employees = new ArrayList<>();
}
employees.add(employee);
Collections.sort(employees, <your_own_comparator_for_employees_or_default_one>);
map.put(location, employees);
}

Find most common Object from a list

Let's say I have a List of Employee Objects. The Employee Objects have a getDepartment method which returns a Department Object. I want to iterate through that list to find the department with the most Employees (i.e. the Department Object returned most often from getDepartment). What is the fastest way to do this?
public class Employee{
static allEmployees = new ArrayList<Employee>();
int id;
Department department;
public Employee(int id, Department department){
this.id = id;
this.department = department;
allEmployees.add(this);
}
public Department getDepartment(){
return department;
}
public static List<Employee> getAllEmployees(){
return allEmployees;
}
}
public class Department{
int id;
String name;
public Department(int id){
this.id = id;
}
public String getName(){
return name;
}
}
If there's two departments with an equal number of employees it doesn't matter which is returned.
Thanks!
create a map of department id -> counts.
that way you get a collection of all the counts by id. You can also maintain a max item that is a reference to the map entry with the highest count.
the algorithm would be something like:
1) Initialize a Map and a currentMax
2) loop through employees
3) for each employee, get its department id
4) do something like map.get(currentId)
a) if the current count is null, initialize it
5) increment the count
6) if the incremented count is > currentMax, update currentMax
This algorithm will run in O(n); I don't think you can get any better than that. Its space complexity is also O(n) because the number of counts is proportional to the size of the input.
If you wanted, you could create a class that uses composition (i.e. contains a Map and a List) and also manages keeping pointers to the Entry with the highest count. That way this part of your functionality is properly encapsulated. The stronger benefit of this approach is it allows you to maintain the count as you enter items into the list (you would proxy the methods that add employees to the list so they update the map counter). Might be overkill though.
Here's a vanilla Java 8 solution:
Employee.getAllEmployees()
.stream()
.collect(Collectors.groupingBy(Employee::getDepartment, Collectors.counting()))
.entrySet()
.stream()
.max(Comparator.comparing(Entry::getValue))
.ifPresent(System.out::println);
It passes through the list of employees at most twice. An equivalent solution using jOOλ, if you're willing to add a third-party dependency, is this:
Seq.seq(Employee.getAllEmployees())
.grouped(Employee::getDepartment, Agg.count())
.maxBy(Tuple2::v2)
.ifPresent(System.out::println);
(Disclaimer: I work for the company behind jOOλ)
I would do something like this using Guava:
Multiset<Department> departments = HashMultiset.create();
for (Employee employee : employees) {
departments.add(employee.getDepartment());
}
Multiset.Entry<Department> max = null;
for (Multiset.Entry<Department> department : departments.entrySet()) {
if (max == null || department.getCount() > max.getCount()) {
max = department;
}
}
You would need a correct implementation of equals and hashCode on Department for this to work.
There's also an issue here that mentions the possibility of creating a "leaderboard" type Multiset in the future that would maintain an order based on the count of each entry it contains.
Since you just want to count employees, it's relatively easy to make a map.
HashMap<Department, Integer> departmentCounter;
that maps Departments to the number of employees (you increment the count for every employee). Alternatively, you can store the whole Employee in the map with a list:
HashMap<Department, List<Employee>> departmentCounter;
and look at the size of your lists instead.
Then you can look at the HashMap documentation if you don't know how to use the class:
http://download.oracle.com/javase/1.4.2/docs/api/java/util/HashMap.html
Hint: you will need to use HashMap.keySet() to see which departments have been entered.
I would do it like that, modulo == null and isEmpty checks:
public static <C> Multimap<Integer, C> getFrequencyMultimap(final Collection<C> collection,
final Ordering<Integer> ordering) {
#SuppressWarnings("unchecked")
Multimap<Integer, C> result = TreeMultimap.create(ordering, (Comparator<C>) Ordering.natural());
for (C element : collection) {
result.put(Collections.frequency(collection, element), element);
}
return result;
}
public static <C> Collection<C> getMostFrequentElements(final Collection<C> collection) {
Ordering<Integer> reverseIntegerOrdering = Ordering.natural().reverse();
Multimap<Integer, C> frequencyMap = getFrequencyMultimap(collection, reverseIntegerOrdering);
return frequencyMap.get(Iterables.getFirst(frequencyMap.keySet(), null));
}
There is also CollectionUtils.getCardinalityMap() which will do the job of the first method, but this is more flexible and more guavish.
Just keep in mind that the C class should be well implemented, that is have equals(), hashCode() and implement Comparable.
This is how you can use it:
Collection<Dummy> result = LambdaUtils.getMostFrequentElements(list);
As a bonus, you can also get the less frequent element with a similar method, just, feed the first method with Ordering.natural() and do not reverse it.

Categories