Java HashMap indexed on 2 keys - java

I want to create a HashMap in java for users with preferences. This would be easy to do in a database, but unfortunately I can't use a database. What I need is a way to find a user by name in the HashMap, and to find all the users with a certain interest (e.g. golf). If I delete a user, then all their interests should be deleted.
Anyone know a nice way to make this data structure?

I would suggest you create your own data structure for holding the information. Inside that class you could have two HashMaps storing the relevant information. Then write your own methods to insert and delete a user.
This way you have control over the insert/delete-operations while being able to query each attribute separately.

Do you know you really need to have a second index. You may find that a search of every user is fast enough, unless you have millions of users.
The following example takes 51 micro-second to scan 1,000 users. It takes 557 micro-seconds to scan 10,000 users.
I wouldn't suggest optimising the collection until your know whether it would make a difference.
import java.util.*;
import java.io.*;
public class TestExecutor {
public static void main(String[] args) throws IOException {
Map<String, User> users = new LinkedHashMap<String, User>();
generateUsers(users, 1000, 0.1);
// warmup.
int count = 10000;
for(int i=0;i< count;i++)
getAllUsersWithInterest(users, Interest.Golf);
long start = System.nanoTime();
for(int i=0;i< count;i++)
getAllUsersWithInterest(users, Interest.Golf);
long time = System.nanoTime() - start;
System.out.printf("Average search time %,d micro-seconds%n", time/ count/1000);
}
private static Set<User> getAllUsersWithInterest(Map<String, User> users, Interest golf) {
Set<User> ret = new LinkedHashSet<User>();
for (User user : users.values()) {
if (user.interests.contains(golf))
ret.add(user);
}
return ret;
}
private static void generateUsers(Map<String, User> users, int count, double interestedInGolf) {
Random rand = new Random();
while(users.size() < count) {
String name = Long.toString(rand.nextLong(), 36);
EnumSet<Interest> interests = rand.nextFloat() < interestedInGolf
? EnumSet.of(Interest.Golf) : EnumSet.noneOf(Interest.class);
users.put(name, new User(name, interests));
}
}
static class User {
private final String name;
private final Set<Interest> interests;
User(String name, Set<Interest> interests) {
this.name = name;
this.interests = interests;
}
}
enum Interest {
Golf
}
}

Simplest solution is to use a Commons Collection MultiKeyMap even if it is lacking generics.
...Check this thread too genericized-commons-collection

it seems like you could use something like a bi-directional map to implement something like this. check out http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/collect/BiMap.html for some doco.
although it doesnt give you exactly what you need in the question, its half the way there.

This may be overkill for your needs, but I don't know how complex and speed sensitive your needs are, so I'll throw it out there...
Have you considered looking at an in-memory (or even local disk based like SQLite) database to handle your data. Doing so would allow you to store your data in a way that allows much more power in how you search/index your data, without many of the costs of writing your own code.

Just put the users in an ArrayList, and walk over it till you found the one(s) you need. Give each user a set of interests. Once you get enough users that it takes too long, sort them.
Once that takes too long, take a look at the distribution of interests. If you have a low number of different ones, store them in a bitmap. If you have a limited set of combinations of interests, store them separately and give a user one of these.
Start simple, computers are fast. But hide the implementation, so you can change it.
[hmm, getting negative votes for this]. Look at the question: you'll need a lot of users before this code is as slow as a database. (on current hardware, at least a few hundred thousand)

I would implement the following
HashMap which include the user as key and the value could be any object which includs the userpreferences. The user preferences would include a list of interest for example.
And an additional HashMap with an interest as key and a list of users who are interested in this.
When you delet a user, you can get all the interest he has and delete the user name from the interest HashMap list.
When the interest HashMap list is empty you can delet the interest from the HashMap.
Take care, when 2 or more users have the same interest. You can not delet the interest when only one user is deleted.
The downside is, that you will have redundant informations.

You could use 2 HashMaps. But searching only trough preferences could be complicated.
HashMap <String,Hashmap> users;
//save data
//create new user
HashMap <String,String> prefs;
//save prefs
prefs.put(pref1,value1);
prefs.put(pref2,value2);
//save user
users.put(user1,prefs);
//get data
String x = users.get(user1).get(pref1);
Maybe you don't need this solution anymore, but many people still have same problems.

Related

How to get cumulative amount total from list of Map

In one of my case I get the input like below which has a list inside with list of Maps.
List<Map<String, String>> actAllSavAccDetLists = test1Page.getAllSavingsAccountsDetails();
// returns like this
[
{Savings=Account ****2623, Current Balance=$22000.00, Annual Rate=7.77%, Transfer=Make a Transfer, Ellipses=...},
{Savings=Account ****5678, Current Balance=$11000.00, Annual Rate=2.22%, Transfer=Make a Transfer, Ellipses=...}
]
Now I need to find the total balance for the user, i.e.; adding up all the current balance from the Map inside a list.
Say in this case, adding $22000.00 + $11000.00 to give the result as $33000.00 in a total_bal variable.
You can easily use java stream map->reduce to make that:
Double totalBalance = actAllSavAccDetLists.stream()
.map(e -> e.get("Current Balance").substring(1))
.map(e -> new BigDecimal(e))
.reduce(BigDecimal.ZERO, BigDecimal::add);
The java-compatible way to do it is not to have this map at all. Java is nominally typed, and really likes its types. a Map<String, String> is not an appropriate data type here.
First, make a class that represents a savings account.
Next, instead of having a List<Map<String, String>>, have a List<SavingsAccount>.
Finally, sum up the balances.
Making a class
Looks like it would be something along the lines of:
#lombok.Value
public class SavingsAccount {
String accountId;
int balance; // in cents
double rate; // might need to be BigDecimal
}
You'll need to festoon it up to become a proper java class (the fields need to be final and private, getters and setters nee dto be there, a constructor, equals, toString, etcetera). I'm using lombok here (disclaimer: I'm one of the developers), but you can also use a java16 record, or use your IDE to generate all this stuff.
Converting that mess into instances of SavingsAccount
Converting a map that contains for example a mapping Transfer = Make a Transfer into an instance of this rather strongly suggests your input is coming from some bizarre source. You'll know better than we do how to convert it. You can now localize all the various required conversions and open questions into a single place. For example, what should happen if, say, map.get("CurrentBalance") doesn't exist, or returns "€10000.00"?
This boils down to "How do I convert the string "$22000.00" into the integer 2200000", or "How do I convert "7.77%" into a double", which is not difficult, and an unrelated question; if you're having trouble with it, I'm sure it's been answered a million times on SO already so you'll find it swiftly with a web search.
Summing it up
That's trivial:
List<SavingsAccount> accounts = ...;
int sum = accounts.stream().mapToInt(SavingsAccount::getBalance).sum();
This streams all the accounts, extracts just the balance from each, and then sums the entire stream into a single number.
I don't want to make that class
Well, it's a bit silly to do things in ways no sane java programmer would ever do. If you're trying to learn, you'll be learning the wrong ways of work. If you're trying to deliver freelance work, you'll get negative reviews. If you're "in a hurry", taking shortcuts now will just cost you triple later on. You do want to make that class.
If you insist on being stubborn, the same techniques can be used, just, with the order all jumbled up. You can stick the code that extracts the balance in that mapToInt call:
.mapToInt(s -> extractBalanceFromThisBizarroMap(s))
and then just write static int extractBalanceFromThisBizarroMap(Map<String, String> s) yourself.
But don't do that.

What is the best way to have a 1 is to N bi-directional Hashmap like structure in Java

I have two entities, Person and Age. While the values of Person are unique, Age can be be same for multiple Person.
If I wanted to only lookup Age of a Person, I would have used a HashMap. But I also want to retrieve a list of Person of a particular Age. The solution I can think of is having another HashMap<String, List<Long>> for the reverse lookup. Is there a data structure or a map like interface with O(1) lookup in both the directions that does the job of two HashMap's in one?
Please note that I used Person and Age as a trivial example, and that the real examples are not something stored in a database, but fetched from a service, so I have to handle them as I get.
Update:
I think Guavas MultiMap can solve this issue. Because in my case both the key and value are String, so it will work. Just seems a little unclean though.
It depends how complex your requirement is - I have had a similar situation in the past but my situation was a bit more complex and I had 4 or 5 fields that I wanted to be able to look up on, so what I ended up doing was using an in-memory database (I used H2 but there are a few). I then indexed all the fields that I wanted to be able to look up by. Because it's in-memory, you get very fast speeds and if you use JPA etc the code is still pretty clean. Clearly this is more complicated that just using a HashMap but will scale better if your requirements get more complex.
if you use java 1.8+ you can use ".stream()" function, for example:
public static void main(String[] args ) {
List<Person> personList = new ArrayList<>();
//...
List<Person> listOfPersonsWith28 =
personList.stream()
.filter(person -> person.getMyAge().value == 28)
.collect(Collectors.toList());
}
class Age{
public int value;
//...
}
class Person{
private Age myAge;
public Age getMyAge() {
return myAge;
}
public void setMyAge(Age myAge) {
this.myAge = myAge;
}
}

Most efficient way to split one arraylist data into 4 seperate arraylists?

I am currently writing code which contains an arraylist. This arraylist includes data which is name, lastname, job and id. I need to seperate the data into different arraylists. Currently i am using the method which is shown below.
for (int i = 0; i < details.size(); i = i + 4) {
names.add(details.get(i));
lastname.add(details.get(i + 1));
job.add(details.get(i + 2));
id.add(details.get(i+3));
}
I want to know if there is a better way of doing this. The initial arraylist can be very long, and i dont know if there are any issues with this method.
You asked: "I want to know if there is a better way of doing this". There is a better way.
You should consider creating a class called Record that contains the data (name, last name, job, and ID), and create an ArrayList. Then, instead of using index locations (and potentially grab the wrong data item), you could use the Record getter methods to get the data item you need (and perhaps store it in a different list).
Step 1: Create a Record class:
public class Record
{
private String firstName;
private String lastName;
private String job;
private String id;
// TODO add constructor(s), getters and setters
}
Step 2: Create a list of Records (this is an better alternative that create a list having the information in different index locations. That way, each set of name, last name, job, and ID will be self-contained which is way better than disjointed in different index locations in a list.
ArrayList<Record> records = new ArrayList<Record>();
Step 3: Instead of using index locations (and potentially grab the wrong data item), you could use the Record getter methods to get the data item you need (and perhaps store it in a different list).
ArrayList<String> names = new ArrayList<String>();
ArrayList<String> jobs = new ArrayList<String>();
...
names.add(records.getLastName() + ", " + records.getFirstName());
jobs.add(records.getJob());
Alternatively, and maybe a better solution, you could use a Map to store this information. For example, a job ID could be the key in a Map that returns a job description and who has been assigned to perform it. Job IDs have to be unique. Adding IDs to a list can be duplicated, because the List interface doesn't restrict entering duplicate data. If you use a Map, they keys are guaranteed to be unique. The value being returned from the Map could be a Record object (or some other kind) that contains the name of the person and the job the person is responsible for. Since values can be duplicates, you can have a person performing multiple jobs, which is probably what you want to do. To use a Map:
Map<String, Record> jobs = new HashMap<String, Record>(); //This record class doesn't have ID in it.
jobs.put("ABC123", new Record("John", "Doe", "Fix Drywall");
jobs.put("321CBA", new Record("Bill", "Smith", "Install Light Fixtures");
A few things to consider if using a Map. If you try to make a new entry with an existing key, the old one will be overwritten.
jobs.put("ABC123", new Record("John", "Doe", "Fix Drywall");
jobs.put("ABC123", new Record("Bill", "Smith", "Install Light Fixtures"); //Overwrote the previous entry because key is the same
If you want to change the key for an existing value, you must obtain the value, store temporarily, remove the old record, and make a new entry with the old temp value:
jobs.put("ABC123", new Record("John", "Doe", "Fix Drywall");
Record rec = jobs.remove("ABC123"); // gets the record and removes old entry
jobs.put("321CBA", rec); // new job ID for old record
The main issue is that your details can have missing data. For example it has the size=5. Then your method will crush with IndexOutOfBounds. Your details list should contain a Person object which has all the details you want and then just use them to fill other lists.
The main performance kill will be the add operation since it will have to grow the data structure over time. Since you know details.size() you should initialize the other arraylists with details.size()/4.
You should also check that details.size() % 4 == 0 before the for loop. If not it means your data is somehow wrong and you will run for sure into an IndexOutOfBounds.
Just for correctness you should write i < details.size()+3 as your condition, since you will access element i+3 in the for body. You should always check for i < details.size()+x do it like this if you ever access i+x in the body. (for the largest x there will be in the body)

Organizing workout info with hashmaps and arrays

I'm looking to create a java program that will store a workout with each lift and reps performed. But I'm not exactly sure how to organize the data structure. I was thinking to use a hashmap to store everything with the hashcode being the date performed. So something performed december 24, 2013 would be stored at 122413. The element I would want to store there would be like an array of arrays, with each array displaying the lift and each following element would alternate between the reps performed and weight. Here's a visual example of that.
arr = [["bench press",10,185,9,195,8,205],["shoulder press",10,95,8,95,6,95],...]
So if I wanted the reps and weight performed for my second set of bench press it would output like:
You performed arr[0][1] reps of arr[0][2] for arr[0][0]
---
You performed 10 reps of 185 for bench press
-----
I know this can be done in python, but I'm not sure that it's available in java?
I'm having issues implementing as from what I've found the datatypes of an array all need to be the same type (no string AND int), which is actually okay because these numbers don't really have a numerical value and will just be displayed. But I'm also a little stuck on getting an array stored in a hashmap and building an array of arrays.
So, my question: is this a doable implementation for what I'm trying to accomplish? Or is there a better way to go about organizing this? I'd like to keep the data organized by date as I showed earlier if possible. Any help or suggestions would be wonderful. Thanks!
I'd probably go with creating custom data structures to model your scenario. For example.
Set class with a weight property and a reps property.
Exercise class with a name property and a collection of Sets.
Workout class with a collection of Exercises and a date property for when the workout was done.
That way, if you wanted to extend it to include food you eat that day, you could either add it to the workout, or probably a better option would be to compose another class of a Workout and food eaten.
That way you open the possibility to draw correlations between how well you trained on a certain day with what you eat.
-- I'm a C# developer and I haven't touched Java before so apologies if the Syntax or the types aren't correct --
public class Set {
public Set()
{
this._weight = 0;
this._reps = new ArrayList<int>();
}
private BigDecimal _weight;
private List<int> _reps;
// implement public properties / methods
}
public class Exercise {
public Exercise()
{
this._name = "";
this._sets = new ArrayList<Set>();
}
private String _name;
private List<Set> _sets;
// implement public properties / methods
}
public class Workout {
public Workout(Date workoutDate)
{
this._date = workoutDate;
this._exercises = new ArrayList<Exercise>
}
private Date _date;
private List<Exercise> _exercises;
// implement public properties / methods
}
Then you'd be able to store a List and order them by Date using a custom implementation of Comparator<Workout>

Converting an ArrayList<HashMap> into a multidimensional List

I have an ArrayList of HashMap elements that has a format of Category, Activity and Time. I.E.
{CATEGORY=Planning, ACTIVITY=Bills, TIME=5}
{CATEGORY=Planning, ACTIVITY=Bills, TIME=7}
{CATEGORY=Planning, ACTIVITY=Meetings, TIME=10}
{CATEGORY=Resources, ACTIVITY=Room1, TIME=15}
....
Take note of the CATEGORY/ACTIVITY pair that is repeated as that can happen in the List
I need to be able to convert this List into a multidimensional one. The best way I can think of for how this List needs to look is by writing some pseudocode...please see that at the bottom of the post.
I've thought of several different approaches on how to implement this but I'm quite frankly stuck and frustrated at how to do this. I've thought of taking the inefficient approach of looping through the ArrayList several times in outer and inner loops but I know that wouldn't be good coding practice.
Any suggestions on how I can implement this conversion so I can loop like in the pseudocode below?
For CATEGORY in CATEGORIES {
CategoryTime = 0
Display Category Header
For ACTIVITY in ACTIVITIES {
Activity Time = 0
For TIME_RECORD in ACTIVITY
Add time to activity total time, category total time & grand total
}
Display Activity Total
}
Display Category Total
}
Display Grand Total and rest of information...
Edit
I appreciate all the feedback given for this problem and it appears that really the best way to go is to enhance a class that the ArrayList of HashMap elements is a member of.
I've put in a vote to close this question as has another person as it's too localized. I would appreciate it if some of you other developers would follow suit to close the question. I would delete it but I can't at this point because there are answers to the question.
I would write a class that looks like so:
public class Planner
{
Map<Category, Collection<Planner> details;
String activity;
long time;
}
public enum Category
{
PLANNING,RESOURCES,ETC;
}
Then you should be able to do the following:
for(Category current: Planner.getDetails().keySet())
{
CategoryTime = 0
Display Category Header
Activity Time = 0
for(Planner currentPlanner : planner.getDetails().get(current))
{
currentPlanner.getActivity();
Activity Time += currentPlanner.getTime();
}
}
The problem you'll have with using the Collections API, besides a poor abstraction, is that you'll have to store many Activities for a given Category. If Category is the key, then you're forced to have a List<Activity> as the value in the Map. And if you query for a given Category, your work isn't done: you have to iterate over the List<Activity> to find the one you want. How will you know?
It's not a Map; it's a multi-map.
I agree with the folks who recommend a class. It's far better, and not that much more work. Better abstractions and more information hiding are usually better for you and your clients.
public class Activity {
private Category category;
private Duration duration; // You want to encapsulate value and units together, right?
// I can see sequencing information that could be useful. Your whole Planner seems to be in need of work.
}
I think your idea of time units is poorly done, too. I can't tell if TIME=10 means 10 hours, days, weeks, months, years, decades - you get the point. Units matter a lot, especially in this context. You would not want people to add times together that used different units.

Categories

Resources