Is it possible to create a map where the key is a Collection (any sort of collection)?
If I try it on most common collections I'm told the collection can't be cast to comparable.
I've been trying to write a compareTo function for a custom collection, but I am struggling.
Either I need to write the compareTo, or I need to find a premade Map that accepts collections/Collection accepted by Maps.
How can I use a collection as a key on a map? I've looked over Stack overflow and I've googled this problem several times, but I've never found a solid solution!
The reason I want to do this is that I've written a 'shuffle' simulation in Java that mimic card shuffling. I want to be able to count up the number of times a specific hand (modeled as a collection) comes up. It would look something like this:
H4,C3,D2: 8
H9,D6,S11: 10
......
Is it possible to create a map where the key is a Collection (any sort of collection)?
Yes it is possible, but definitely not recommended. If your collection changes, it is likely that its hashcode will also change and that can lead to surprising behaviour.
See Map's javadoc:
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map.
If I try it on most common collections I'm told the collection can't be cast to comparable.
The key does not need to be comparable, unless you use a sorted map, i.e. TreeMap. Use a simple HashMap and you won't have the issue.
Following your edit, I would create a new immutable Hand class:
class Hand implements Comparable<Hand> {
private final List<Card> cards;
Hand(Card c1, Card c2, Card c3) {
cards = Collections.unmodifiableList(Arrays.asList(c1, c2, c3));
}
//getters, no setters
//implement compareTo
}
and implement compareTo if you want to use it in a TreeSet<Hand, Integer> and sort by hand strength for example.
Yes, you can use any collection as a key. If you want a SortedMap like TreeMap you have to provide a Comparator to determine the order. However if you use any kind of HashMap you don't need.
Map<List<Integer>, String> map = new HashMap<>();
map.put(Arrays.asList(1,2,3), "one to three");
map.put(Arrays.asList(7,8,9), "seven eat nine");
System.out.println(map);
prints
{[1, 2, 3]=one to three, [7, 8, 9]=seven eat nine}
Related
There is a Team object , that contains list of players List<Players>. All teams need to be stored in a Teams collection.
Conditions:
If a new player need to be added to a particular team , that particular team is retrieved from Teams and Player need to be added to Players list of that team
Each Team object in the collection Teams need to be unique based on the team name
Team objects in the collection need to be sorted based on team name.
Considerations:
In this scenario when I use List<Team> , I can achieve 1, 3 . But uniqueness cannot be satisfied.
If I use TreeSet<Team> 2,3 can be achieved. But as there is no get method on TreeSet , a particular team cannot be selected
So I ended up using TreeMap<teamName,Team>. This makes all 1,2,3 possible. But I think it's not the good way to do it
Which Data Structure is ideal for this use case? Preferably form Java collections.
You can utilize your TreeSet if you wish. However, if you're going to utilize the Set interface you can use remove(object o) instead of get. You'll remove the object, make your modifications, then add it back into the set.
I think extending (i.e. creating a subclass from) ArrayList or LinkedList and overriding the set(), add(), addAll(), remove(), and removeRange() methods in such way that they ensure the uniqueness and sortedness conditions (invariants) would be a very clean design. You can also implement a binary search method in your class to quickly find a team with a given name.
ArrayList is a better choice to base your class on, if you aren't going to add or remove teams too frequently. ArrayList would give you O(n) insertion and removal, but O(log n) cost for element access and ensuring uniqueness if you use binary search (where n is the number of elements in the array).
See the generics tutorial for subclassing generics.
How about using a Guava's MultiMap? More precisely, a SetMultimap. Specifically, a SortedSetMultimap. Even more specifically, its TreeMultimap implementation (1).
Explanations:
In a MultiMap, a Key points not to a single value, but rather to a Collection of values.
This means you can bind to a single Team key a collection of several Player values, so that's Req1 solved.
In a SetMultiMap, the Keys are unique.
This gets your Req2 solved.
In a SortedSetMultimap, the Valuess are also sorted.
While you don't specifically care for this, it's nice to have.
In a TreeMultimap, The Keyset and each of their Values collections are Sorted.
This gets your Req3 sorted (See what I did there?)
Usage:
TreeMultimap<Team, Player> ownership = new TreeMultimap<Team, Player>();
ownership.put(team1, playerA);
ownership.put(team1, playerB);
ownership.put(team2, playerC);
Collection<Player> playersOfTeamA = ownership.get(team1); // contains playerA, playerB
SortedSet<Team> allTeams = ownership.keySet(); // contains team1, team2
Gothas:
Remember to set equals and hashCode correctly on your Team object to use its name.
Alternatively, you could use the static create(Comparator<? super K> keyComparator, Comparator<? super V> valueComparator) which provides a purpose-built comparison if you do not wish to change the natural ordering of Team. (use Ordering.natural() for the Player comparator to keep its natural ordering - another nice Guava thing). In any case, make sure it is compatible with equals!
MultiMaps are not Maps because puting a new value to a key does not remove the previously held value (that's the whole point), so make sure you understand it. (for instance it still hold that you cannot put a key-value pair twice...)
(1): I am unsure wether SortedSetMultimap is sufficient. In its Javadoc, it states the Values are sorted, but nothing is said of the keys. Does anyone know any better?
(2) I assure you, I'm not affiliated to Guava in any way. I just find it awesome!
I need to do a look-up table based on two keys. I am building a mileage look-up chart similar to what is seen in the back of road maps. A sample of a chart can be found here. If you know the starting city is x and the ending city is y you look to find the intersection to find out the total miles.
When I first started attacking this problem I though of doing Two maps. City being an ENUM of my city of interest.
Map<City, Map<City, Integer>> map;
But, as I researched I am seeing warnings about Map's that have values of type Map. Is there an easier solution to my problem that I might be overlooking? With this being 66x66 col*row I want to make sure I do it right the first time and dont have to redo the data entry.
As a note I will be saving all my values into a database for easy update and retrieval so the solution would need to be easy to map with JPA or Hibernate etc.
Thanks in advanced.
It'd be easier if you do this:
Map<Pair<City, City>, Integer> map;
That is: create a new generic class, let's call it Pair that represents a pair of cities, and use it as key to your Map. Of course, don't forget to override hashCode() and equals() in Pair. And take a look at #increment1's answer, he's right: if the distance from city A to B is the same as the distance from B to A, then there's no need to store two pairs of cities, a single pair will do, no matter the order used to add the cities to the Map.
Notice that this is the strategy used by ORMs (for instance, JPA) when mapping composite keys in a database: create a new class (Pair in the example) that encapsulates all the objects used as keys, it'll be much easier to manage this way: conceptually, there's only one key - even if internally that key is composed of several elements.
Make a map of Path's, where Path is a custom class that holds two cities. Remember to override equals and hashcode.
Edit: Why is there 66x66 paths? Is the mileage different regarding which way you go (probably is a bit difference, but do you have that data)? If not, you can discard more than half that number of entries (the half is obvious, the 'more' part is from New York to New York entry no longer needs to be saved with 0).
You should create a simple class that contains two City references, from and to, and which overrides equals and hashCode appropriately. Then use that as your key.
Similar to other answers, I suggest creating a city pair class to be your map key (thus avoid a map of maps). One difference I would make, however, would be to make the city pair class order agnostic in regards to the cities in its hashCode and equals methods.
I.e. Make CityPair(Seattle,LA) equal to CityPair(LA,Seattle).
The advantage of this is that you would then not duplicate any unnecessary entries in your map automatically.
I would achieve this by having hashCode and equals always consider city1 to be the city with the lower ordinal value (via Enum.ordinal()) in your enum.
Alternatively, try this simple unordered pair implementation given in another question and answer.
If you're using Eclipse Collections, you can use MutableObjectIntMap and Pair.
MutableObjectIntMap<Pair<City, City>> map = ObjectIntHashMap.newMap();
map.put(Tuples.pair(newYorkCity, newark), 10);
map.put(Tuples.pair(newYorkCity, orlando), 1075);
Assert.assertEquals(10, map.get(Tuples.pair(newYorkCity, newark)));
Assert.assertEquals(1075, map.get(Tuples.pair(newYorkCity, orlando)));
Pair is built into the framework so you don't have to write your own. MutableObjectIntMap is similar to a Map<Object, Integer> but optimized for memory. It's backed by an Object array and an int array and thus avoids storing Integer wrapper objects.
Note: I am a committer for Eclipse collections.
To do the same as the graphic, i would use a 2d- array.
// index is the city code:
int[][] distances;
store the city code in a
Map<String, Integer> cityNameToCodeMap
Use it as follows;
Integer posA = cityNameTCodeMap.get("New York");
// TODO check posA and posB for null, if city does not exits
Integer posB = cityNameTCodeMap.get("Los Angeles");
int distance = distances[posA][posB];
reason for this design:
The matrix is in the graphic is not a sparse matrix, it is full.
For that case an 2d-array uses least memory.
There is another way to do this, that may work for you. Basically, you want to create a class called something like CityPair. It would take 2 arguments to its constructor, the start and end cities, and would override the hashcode function to generate a unique hash based on the two inputs. These two inputs could then be used in a HashMap<CityPair,Integer> type.
if there are only 66 cities, then your hashing function could look something like this:
//first assign each city an id, 0-65 and call it city.getID()
#Override public int hashCode()
{
return ((city1.getID() << 16) | (city2.getID()))
}
of course as noted in the comments, and in other answers, you will want to override the function prototyped by:
public boolean equals(Object)
from object so that the map can recover from a hash collision
I've got Treasure objects in a TreasureCollectionDB class.
The TreasureCollectionDB class has a Map<Long, Treasure> (long being an id generated by the TreasureCollectionDB) called treasures
and a second data collection/list (available treasures).
The thing I need the other Collection or List to do is hold Treasures which I will add/remove through JSP pages. The Treasures in this list should be unique, but sorted alphabetically (if there's no data holder that does this by it self, I will write a sort method).
Anyone know what data holder I should use? Answers on the internet are confusing me as to which is most suitable.
You may use TreeSet, that should give you the desired results.
Set doesn't allow duplicates and Tree maintains sorted order.
The Treasures may implement Comparable interface so that you can sort on the desired field(s).
You would need to create equals and hash code methods and also write a comparator. That comparator you may pass to a TreeSet and use SortedSet interface.
I am looking to implement a data structure that would add / render an ordered pair of values. eg.
orderedPair.add(value1, text1)
orderedPair.add(value1, text2)
orderedPair.add(value2, text3, data1)
orderedPair.add(value2, data2)
orderedPair.add(value1, text5)
When I get, I want it to return iteratively as
value1, text1
value1, text2
value2, text3, data1 and so on.
LinkedHashMaps or any variants of HashMaps do not work since they return only the value based on key and what I am trying to get is value, value pairs. Note that neither value / text or data are unique and I may not be able to fetch it based on any keys. Also, i do NOT want a sorted list, I do need an ORDERED list only.
Question is: Is there any data structure in Java that can be used to accomplish this?
I did not come across any that serves this purpose. In which case I am pondering about writing a custom collection that would accomplish this. Any suggestions / help is welcome.
Wrapping the discussion in the comments into an answer, since it seems to be useful to the OP:
Create a class Tuple, which will be your pairs/triples.
Note that this class can be implemented with a fixed number of parameters or as a container that holds a list of objects.
Hold these Tuple objects in a List<Tuple>, and you are done.
You can also implement hashCode(), equals() and make it implement Comparable to this class - and you will be able to use it with other collections such as TreeSet and HashSet.
Just use a Map of Lists, like Treemap
Map<Integer, List<Integer>> content = new Treemap<Integer, List<Integer>>();
if (not content.containsKey(value1)) {
content.put(value1, new LinkedList<Integer>());
}
content.get(value1).add(text1)
This would be the function orderedPair.add
Then for output, traverse the Map and for each entry, write out each item of the corresponding List
Since you want to have it ordered, pass a Comparator to the Treemap constructor.
ArrayList<HashMap<String, String>>
arrayListRowsFirst = new ArrayList<HashMap<String, String>>();
Today when i was going through a code, this piece of code struck me for a while. Here are some of my questions over this declaration.
What could be the requirement if one has to append an HashMap into an ArrayList.
What will happen during sorting of arraylist, how it will go, will it take long time.
First off, "generic chaining" in my opinion is a poor practice. I would encourage wrapping the HashMap in a class that encapsulates the data inside, allowing the logic for manipulation to be inside the class, not just strewn about everywhere.
To answer #1, I could think of a number of scenarios. You might have languages for instance, mapping certain constants to other translations. The fact that it says rows first in the identifier makes me think perhaps it's some kind of matrix of data, and that the first String parameter will exist in all the entries of the list (a poor practice indeed.)
Edit: I misunderstood your question, it appears. You would add it like any other entry. See the others' answers for example code. :-)
To answer #2, you won't be able to sort the ArrayList unless you are able to provide a comparator, at which point it's up to you how it's sorted (could be size, could be the value of a particular key, could be Math.random(), it's up to whoever writes the comparator).
There is no "special requirement" to append an HashMap to an ArrayList.
And as neither Map nor HashMap implements Comparable, so if you want to sort the ArrayList, you would have to create your own Comparator.
A sort on a List which contains Map would be exactly the same as a sort on a List wich contains anything else.
What do you mean about "append a HashMap into an ArrayList"? You add HashMaps to the ArrayList the way you add any object to a List
HashMap<String,String> hm = new HashMap<String,String>();
arrayListRowsFirst.add(hm);
You sort the array list like you sort any other - you would need to define a Comparator that compared two HashMaps, and use Collections.sort with that Comparator. How long it takes will depend a lot on how you're comparing the HashMaps.
You would add HashMaps to the ArrayList like you would any other object, using the add() method. Obviously it would need to be of the correct Type, in this case a HashMap of Strings.
You would need to create a comparator so that your HashMaps would be sortable.
The declaration should be
List<Map<String, String>>
1 to append a map into the list, you just do
Map<String, String> map = new HashMap<String, String>();
list.add(map);
2 To sort the list, you would need a way to tell if one Map is "greater than", "less than", or "equal" to another Map. The could or might not take a long time depending on your needs. It doesn't have to take a long time.