How can I properly relate multiple BiMap collections using shared keys? - java

I have the following BiMap collections:
BiMap<String,String> accessIds = HashBiMap.create();
accessIds.put("FOO","accessId 1"); //This access Id is common to both FOO and BAR
BiMap<String,String> merchants = HashBiMap.create();
merchants.put("FOO", "merchant 1"); //Both FOO and BAR each have unique merchants
merchants.put("BAR", "merchant 2");
These are 2 of the 4 total collections I currently have. All 4 collections share the same keys, but different values.
The question I have is: How can I ensure that I can get merchant 2 when I have an accessIds key of FOO?
Before someone points out that these two collections do not, in fact, share the same keys, please remember that a BiMap enforces unique values so I am unable to list "BAR","accessId 1" in the collection.
I'm not convinced that BiMap is the right collection, but I do make use of its inverse() method. If there is a collection better suited ( or some other method that I am overlooking ) please let me know.
FYI: I use Guava-14.0-rc1 for the BiMap collection.

Based on your comment, in your workflow, the Access ID is a key, not a value, that in at least one case has several associated values instead of one.
You could use a Multimap for your Access IDS, assuming you can then select which value to retain as the key for accessing the other Maps (or BiMaps, though it's unclear through your example why they are BiMaps, but I guess that's unrelated).
ImmutableMultimap.Builder<String, String> builder = ImmutableMultimap.builder();
builder.put("FOO", "accessId 1");
builder.put("BAR", "accessId 1");
ImmutableMultimap<String, String> accessIds = builder.build();
ImmutableMultimap<String, String> byAccessIds = accessIds.inverse();
Collection<String> keys = byAccessIds.get("accessId 1"); // ["FOO", "BAR"]
String key = doSomething(keys); // "BAR" is chosen
String merchant = merchants.get(key); // "merchant 2"
If you cannot use immutable structures, you can also build a regular Multimap for accessIds (for example using a HashMultimap) and inverse it using Multimaps.invertFrom().

Related

Create a collection by associating two id's of two different object

I have two list of objects.
members: List,
membersHistory: List
Both of these objects have an "id" field. I want to create a list by joining both of these lists by making sure the "id" field of one is associated to the other object that have the same id. Both, object have different data but they are for specific member. Just need to pair them somehow to create a collection.
I started out something like below. But, I think I need to map them by "id" first before zipping them. Thank you!
members.zip(membersHistory).mapIndexed {_, pair ->
val (member, memberHistory) = pair
}
Here are a couple of ways to create a list of pairs of the items.
For each item in the first list, find a corresponding item in the second list with the same idea and pair them if found. mapNotNull will cause it to skip items that have no match in the second list.
val combination: List<Pair<Member, MemberHistory>> = members
.mapNotNull { member -> memberHistories.firstOrNull { it.id == member.id }?.let { member to it } }
To do this in O(n), you can create a map with the IDs as keys from one of the sources lists using associateBy.
val memberHistoryById = memberHistories.associateBy { it.id }
val combination = members.mapNotNull { member -> memberHistoryById[member.id]?.let { member to it } }
Presumably, though it hasn't been stated explicitly in the question, the id is the ID of a Member, so there will be no two objects in the members list with the same id value.
For quick lookup of Member by ID, I'd recommend creating 2 maps:
// Examples in Java
Map<Integer, Member> memberById = members.stream()
.collect(Collectors.toMap(Member::getId, Function.identity()));
Map<Integer, List<History>> memberHistoryById = membersHistory.stream()
.collect(Collectors.groupingBy(History::getId));
Those are both good to keep around, but if you want the Member and the History together, you can then create a combined map, keyed by the Member object. Assuming the natural order of Member is not the ID, we need a custom key, which we can do with TreeMap.
Map<Member, List<History>> historyByMember = memberHistoryById.entrySet().stream()
.collect(Collectors.toMap(e -> memberById.get(e.getKey()),
Map.Entry::getValue,
(a,b) -> a/*this is never called*/,
() -> new TreeMap(Comparator.comparingInt(Member::getId)));
The Answer by Andreas is a good one, using a map to associate a member object with its matching history object.
Write a class
Alternatively, you could create a class to bind the two objects together.
In Java 16 and later, a record might do. A record is a brief way to write a class whose main purpose is to communicate data transparently and immutably. The compiler implicitly creates the constructor, getters, equals & hashCode, and toString. A record can be declared locally or separately.
record MemberWithHistory ( Member member , History history ) {}
Loop your list of members.
For each member, find a matching history. We can do this easily with streams. Make a stream of your history objects, filtering for one whose member identifier matches the identifier of the nth member. An Optional is returned carrying the history object if found. Otherwise, if no matching history is found, the optional carries nothing.
If the optional does indeed have a found history object, instantiate a new MemberWithHistory record object. Collect that new record by adding to our results list named mwhs.
Here is some untested code to get your started.
List< MemberWithHistory > mwhs = new ArrayList<>( members.size() ) ;
for( Member member : members )
{
Optional< History > historyOptional = histories.stream().filter( history -> history.memberId.equals( member.id ) ).findAny() ;
if( historyOptional.isPresent() )
{
MemberWithHistory mwh = new MemberWithHistory( member , historyOptional.get() ) ;
mwhs.add( mwh ) ;
}
}
If you had a large number of items, searching by way of a stream repeatedly might become inefficient. Sorting and possibly deleting from a copy of the histories might be more efficient. But I would not bother for small data size or occasional use.

Check if Set of Object contain an Object with this attribute

Consider i have a List Of Objects, and i convert it to a Set to make some actions like this :
List<User> listUser = new ArrayList<>();
listUser.add(new User(1, "user1"));
listUser.add(new User(2, "user2"));
listUser.add(new User(3, "user3"));
Set<User> myset = new HashSet<>(listUser);
I know there are a contains(Object o) but i want to check with the attributes of my Object.
My Question is What is the best way to check if the Set contain an Object with ID = 1 or user = "user1" or any attribute
Thank you.
Does your User class override equalsusing just the ID? If so, you could use:
if (mySet.contains(new User(1, "irrelevant"));
Note that it's quite odd to have something called mySet which is actually a List rather than a Set... I'd consider either changing the name or the type. If you use HashSet you'll need to override hashCode as well as equals, but you should do that anyway to obey the normal contracts from Object.
If you want to be able to check by multiple different attributes - or if you don't want to override equality in User - then you could consider creating maps instead, e.g.
Map<Integer, User> usersById = new HashMap<>();
Map<String, User> usersByName = new HashMap<>();
...
Then you need to keep all the maps in sync, of course - and you need to consider what happens if you've got multiple users with the same name, etc.
You are correct that you cannot use Java streams with Java 7 (they were introduced in Java 8). For Java 7 I would suggest an enhanced for loop in which you check your search condition (if (u.getId() == 1)) and act appropriately.
If you are ever getting a problem with the linear search that the for loop would do, you may build maps: HashMap<Integer, User> for allowing lookup by ID and HashMap<String, User> for lookup by user name. Map.containsKey() will tell you whether the map contains a specific user. You will need to take care that the maps contain all your users, and also that users are removed from the maps when deleted from your set/list, of course (oops, only saw now that Jon Skeet has already said all of this, sorry).
For anyone using Java 8 and reading along here, use of streams needs not be very fancy. A couple of examples:
boolean containsUser1 = myset.stream().anyMatch(u -> u.getId() == 1);
This yieds true.
User[] u1Array = myset.stream().filter(u -> u.getId() == 1).toArray(User[]::new);
Set<User> u1Set = myset.stream().filter(u -> u.getUserName().equals("user1")).collect(Collectors.toSet());
Each of these two yield [1 user1] (the first one as an array, the second as s Set, obviously). Each use of a stream does a linear search behind the scenes.
That said, you can do fancy things with streams. I’ve found it a pleasure learning them and using them, look forward to that.
Simpliest and fanciest way i think is to override equals method in your User class by just considering the id.
Then you can use the contains method from the List interface

Get element without iterating

I have an ArrayList of HashMap key-value pairs which looks like
ArrayList<HashMap<String, String>> myList =
new ArrayList<HashMap<String, String>>();
I understand that I can iterate through these items and find a match, but this seems to be an expensive task. Is there any other way to get an element without iterating?
My ArrayList has values like
[{Father Name=a, Mother Name=b, Child Name=c, Reg No=1, Tag ID=1},
{Father Name=p, Mother Name=q, Child Name=r, Reg No=2, Tag ID=2},
{Father Name=x, Mother Name=y, Child Name=z, Reg No=3, Tag ID=3}]
Based on RegNo, I wish to get Father Name, Mother Name and Child Name without iterating individual items.
Without iterating you will need to store your HashMap in another HashMap with key Reg No. Though I'd recommend using a Family object or something similar: HashMap<Integer, Family> registration (that's the beauty of OO-languages :) )
class Family {
String father;
String mother;
String child;
// constructor getters setters
}
Map<Integer, Family> registration = new HashMap(); // note this is a JDK7 future
//Map<Integer, Family> registration = new HashMap<Integer, Family>(); // the 'old' way
registration.put(regNo, new Family("Jack", "Mary", "Bastard"));
Family family = registration.get(regNo);
String father = family.getFather();
since you are storing hashes in list, that means order remain constant. So that mean you can create another array to store the Reg No in same order, and then search reg no in that array and based on searched value index you can get the other values.
Iterating is O(n), but you want the access to your structure to be faster... This means storing objects in a ordered manner ( -> O(log(n)) usually) or using another hash ( -> O(1)).
Or this, or you "hide" the iteration, but this would solve the problem only esthetically (something like getElementsByTagName in xml).
In any case you'll probably have to alter your structures, especially if you want to be able to have faster access for every field (father/mother/child/tag) and not just 'reg no'.
Maybe another solution could be storing plain data in a hash with a keypair like (primary key, data), duplicating the PK for every field in your HashMap, but this not only implies searching a valid primary key, there could be the problem of the size of the hash.

Whats the benefit of allowing multiple null keys in hashmap?

Just for experimenting, I added multiple null keys in a Hashmap instance. And it didn't complain. What's the benefit of doing that?
The code is,
Map hmap = new HashMap();
hmap.put("sushil","sushil11" );
hmap.put(null,null);
hmap.put(null,"king");
hmap.put(null,"nagasaki");
hmap.put(null,null);
How many keys are there in the map?
I would guess you haven't added multiple null-keys. You just overwrote the same nullkey multiple times.
A normal hashmap will have unique keys, so you're overwriting the entry for the null key repeatedly. You won't have multiple identical keys (for this you need a MultiMap or similar)
It is used to get switch:case:default behavior.
Example:
Problem Definition: Coffee shop in CS Department building. They provide coffee to CS Student for $1.00, to IT department students $1.25 and others for $1.50.
Then Map will be:
Key -> Value
IT -> 1.25
CS -> 1.00
null -> 1.50
if(map.containsKey(dept))
price = map.get(dept);
else
price = map.get(null);
P.S. - I am not "Department-ist" if that's a word. :)
There's an API call for this:
size: Returns the number of key-value mappings in this map.
hmap.size();
As noted you're just overwriting the key/value pair with a new value.
The question has asked about having multiple keys in HashMap which is not possible.
If you pass null again and again the old value is replaced only.
Refer:
http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/HashMap.java#HashMap.putForNullKey%28java.lang.Object%29
HashMap allows multiple null values but only one null key.
If you write multiple null keys like below, then null will be overrides and you'll get the final overridden result. i.e "world"
Map<String, String> valueMap = new HashMap<>();
valueMap.put(null, "hello");
valueMap.put(null, "world");
System.out.println(valueMap.get(null));
Output:
"world"

Is it possible to have a hashmap with 4 objects?

Can I have an hashMap with say ID as my key and info, name, quantity as my values?
ok, say I have a class (Products) already that sets my variables, getters and setters. In my Invoice class, which is where the hashMap would be. Would I put like:
private HashMap<String, Products> keys = new HashMap<String, Products>
I'm not quite sure how to access the HashMap though. Say I implement a class that allows me to add and remove invoices from the HashMap, I do not know what the values would be:
keys.put(??value of id??,??not sure what goes here??);
Sure. Make another class that contains your info, name and quantity and put that as the value of your HashMap.
No, but the best way is to wrap the information you want to keep in the map in a class:
public class Info {
private String info;
private String name;
private int quantity;
...
public Info(String info, String name, int quantity) {
...
}
}
Then do this to put something in the map:
Info info = new Info("info", "name", 2);
Map map = new HashMap<Integer, Info>();
map.put(22, info);
And do this to get something out:
Info info = map.get(22)
How about HashMap<Integer, ArrayList<String>> ?
UPDATE: Please try to avoid this, this is a better approach.
Not exactly.
A Map defines a strictly 1 to 1 relationship between keys and values. One key in the map has one value.
If you want to associate multiple values with one key you need to do one of the following:
Define a Values class to represent the values as a single object; e.g. as per #Starkey's and #Javed's answers. Then the map becomes a Map<String, Values> (assuming that the key type is String).
Define the map as a Map<String,List<Object>> or Map<String,Object[]> and represent the values as an untyped list / array
Define the map as a Map<String,Properties> or Map<String,Map<String,Object>> and represent the values as the Java equivalent of an associative array.
Of these, the first option is both the safest (smallest chance of runtime errors), the most efficient and the best style.
(Aside: an Apache commons MultiMap might be considered as another possibility, but the conceptual model and APIs don't really match this use-case.)
Sure. Depending on how flexible your datastructe is you can use a Hashmap a la:
HashMap<IdType, List<String>>, with IdType String or Integer, depending on the Keys you like to use.
HashMap<IdType, String[]>
HashMap<IdType, YourObjectType>, with YourObjectType beeing a Object you defined yourself, holding the values you like
YourObjectType can of course be anything you can define as an Object. Also another HashMap if you like.
One of the concerns while using a Map would be use of hardcoded keys. If the key is a string, and the key changes. Can consider using a constant instead of a hardcoded string.
Having a dedicated class has the benefit of compiler to check for name changes. However, as mentioned in the earlier comments.. It can become a concern...
In my opinion both are feasible. We need to weigh which option is better depending on the situation
Create an object that encapsulates the four together. Something like:
public class Foo {
private String s1;
private String s2;
private int v3;
private MyObject obj1
// constructors, getters, helper functions.
}
I think MultiMap from google library could serve the purpose
https://google.github.io/guava/releases/19.0/api/docs/com/google/common/collect/Multimap.html
Multimap<String, String> map = ArrayListMultimap.create();
String key = "uniqueKey";
map.put(key, "value1");
map.put(key, "value2");
map.put(key, "value3");
System.out.println(map);//{uniqueKey=[value1, value2, value3]}
Of course, you could for example declare it like this: HashMap<Integer, HashMap<String,Object>> You use the outer hashmap to link your id with your inner HashMap, and in the inner one, you create keys "info", "name", "quantity" and associate values with them.
Of course, you could also use an ArrayList as the outer collection (it could be a better match for your ID: ArrayList<HashMap<String,Object>> that way you have indexed (id based) access to each of your "info", "name", "quantity" hashmap "records"
You could have ID as key and a List or Set (Collection in general) of objects as value.

Categories

Resources