Selecting random key and value sets from a Map in Java - java

I want to get random keys and their respective values from a Map. The idea is that a random generator would pick a key and display that value. The tricky part is that both key and value will be strings, for example myMap.put("Geddy", "Lee").

HashMap<String, String> x;
Random random = new Random();
List<String> keys = new ArrayList<String>(x.keySet());
String randomKey = keys.get( random.nextInt(keys.size()) );
String value = x.get(randomKey);

This question should be of help to you Is there a way to get the value of a HashMap randomly in Java? and this one also Picking a random element from a set because HashMap is backed by a HashSet. It would be either O(n) time and constant space or it would be O(n) extra space and constant time.

If you don't mind the wasted space, one approach would be to separately keep a List of all keys that are in the Map. For best performance, you'll want a List that has good random-access performance (like an ArrayList). Then, just get a random number between 0 (inclusive) and list.size() (exclusive), pull out the key at that index, and look that key up.
Random rand = something
int randIndex = rand.nextInt(list.size());
K key = list.get(randIndex);
V value = map.get(key);
This approach also means that adding a key-value pair is a good deal cheaper than removing one. To add the key-value pair, you would test to see if the key is already in the map (if your values can be null, you'll have to separately call map.containsKey; if not, you can just add the key-value pair and see if the "old value" it returns is null). If the key is already in the map, the list is unchanged, but if not, you add the key to the list (an O(1) operation for most lists). Removing a key-value pair, though, involves an O(N) operation to remove the key from the list.
If space is a big concern, but performance is less so, you could also get an Iterator over the map's entry set (Map.entrySet()), and skip randIndex entries before returning the one you want. But that would be an O(N) operation, which kinda defeats the whole point of a map.
Finally, you can just get the entry set's toArray() and randomly index into that. That's simpler, though less efficient.

if your keys are integer, or something comparable, you can use TreeMap to do that.
TreeMap<Integer, Integer> treeMap = new TreeMap<>();
int key = RandomUtils.ranInt(treeMap.lastKey());
int value = treeMap.ceilingKey(key);

I would copy the Map into an array and select the entry you want at random. This avoid the need to also lookup the value from the key.
Map<String, String> x = new HashMap<String, String>();
Map.Entry<String,String>[] entries = x.entrySet().toArray(new Map.Entry[0]);
Random rand = new Random();
// call repeatedly
Map.Entry<String, String> keyValue = entries[rand.nextInt(entries.length)];
If you want to avoid duplication, you can randomize the order of the entries
Map<String, String> x = new HashMap<String, String>();
List<Map.Entry<String,String>> entries = new ArrayList<Map.Entry<String, String>> (x.entrySet());
Collections.shuffle(entries);
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry);
}

Use reservoir sampling to select a list of random keys, then insert them into a map (along with their corresponding values in the source map.)
This way you do not need to copy the whole keySet into an array, only the selected keys.
public static <K, V>Map<K, V> sampleFromMap(Map<? extends K, ? extends V> source, int n, Random rnd) {
List<K> chosenKeys = new ArrayList<K>();
int count = 0;
for (K k: source.keySet()) {
if (count++ < n) {
chosenKeys.add(k);
if (count == n) {
Collections.shuffle(chosenKeys, rnd);
}
} else {
int pos = rnd.nextInt(count);
if (pos < n) {
chosenKeys.set(pos, k);
}
}
}
Map<K, V> result = new HashMap<K, V>();
for (K k: chosenKeys) {
result.put(k, source.get(k));
}
return Collections.unmodifiableMap(result);
}

In some cases you might want to preserve an order you put the elements in the Set,
In such scenario you can use, This
Set<Integer> alldocsId = new HashSet<>();
for (int i=0;i<normalized.length;i++)
{
String sql = "SELECT DISTINCT movieID FROM postingtbl WHERE term=?";
PreparedStatement prepstm = conn.prepareStatement(sql);
prepstm.setString(1,normalized[i]);
ResultSet rs = prepstm.executeQuery();
while (rs.next())
{
alldocsId.add(rs.getInt("MovieID"));
}
prepstm.close();
}
List<Integer> alldocIDlst = new ArrayList<>();
Iterator it = alldocsId.iterator();
while (it.hasNext())
{
alldocIDlst.add(Integer.valueOf(it.next().toString()));
}

Been a while since a played with java, but doesn't keySet() give you a list that you can select from using a numerical index? I think you could pick a random number and select that from the keySet of myMap, then select the corresponding value from myMap. Can't test this right now, but it seems to strike me as possible!

Related

2 Maps with same key - get the values of each into a new map

How do i get both of the values from each map into another map? One map has the name of the ingredient as key. keywordsToIds has the ID as value and firstCounter has the occurance of the ingredient as value. I want to have a map of ID as key and occurance as value. The keys work but the values don't. I hope someone can help me out. I am very new to maps and arraylists.
Map<String, Long> keywordsToIds
Map<String, Integer> firstCounter
Map<Long, Integer> idAndCount = new HashMap<>();
for (Map.Entry<String, Integer> entry : firstCounter.entrySet())
if (keywordsToIds.containsKey(entry.getKey())){
idAndCount.put(keywordsToIds.get(entry.getKey()), firstCounter.get(entry.getValue()));
}
return idAndCount;
#Test
#DisplayName("can detect multiple occurrences of ingredients")
void testCounting() {
// Input-Daten:
String inputLine = "Ich hätte gerne einen Vollkorn Burger mit Cheddar-Käse Cheddar-Käse und noch mehr Cheddar-Käse";
Map<String, Long> keywordsToIds = Map.of(
"Vollkorn", 19L,
"Cheddar-Käse", 87L,
"Rindfleisch", 77L);
Map<Long, Integer> expected = Map.of(
19L, 1,
87L, 3);
Map<Long, Integer> actual = sut.idsAndCountFromInput(inputLine, keywordsToIds);
assertEquals(expected, actual);
}
expected: <{19=1, 87=3}> but was: <{19=null, 87=null}>
Expected :{19=1, 87=3}
Actual :{19=null, 87=null}
I have tried the loop above, where i say if the key of the one map contains the key of the other map, put the value of keywordsToIds as key and value of firstCounter as value.
I'm assuming you're trying to have 2 maps that hold the same strings as the keys, while having an additional map corresponding to an Integer occurrence and a Long ID.
To make a Map which holds an Integer occurrence as the key and a Long ID as the value, I believe you have two options, either manually going through each value set, or creating a custom class to hold your two properties.
Option One: Creating a custom class
You could create a class representing the thing your trying to store, and create a Map of your Object. For the sake of simplicity, lets say you are trying to have two maps represent IDs. Each ID Object represents the ID itself, and how frequently its been used (the occurrence).
You could create your class like this:
public class ID {
long id = 0L;
int occurrence = 0;
public ID(long id, int uses) {
this.id = id;
occurrence = uses;
}
public long getID() {
return(id);
}
public int getOccurs() {
return(occurrence);
}
}
Then, make all the ID objects you need and add them into either a Map, if you still want the string representation, as Map<String, ID>, or just create a list, because the ID object stores all the properties you were previously storing in the map.
Option Two: Manually Getting Each Value
You could get each of the value Collections and loop through them at the same time by iterating through each list to add each Long and Integer to the same map.
Map<String, Long> keywordsToIds = new HashMap<String, Long>();
Map<String, Integer> firstCounter = new HashMap<String, Integer>();
String[] list = new String[] {"wordA", "wordB", "wordC"};
long[] longs = new long[] {12L, 14L, 15L};
int[] ints = new int[] {123, 456, 789};
for (int i=0; i<list.length; i++) {
keywordsToIds.put(list[i], longs[i]); // Adds each word pointing to a Long
firstCounter.put(list[i], ints[i]); // Adds each word pointing to an Integer
}
Map<Long, Integer> idsToOccurrence = new HashMap<Long, Integer>();
ArrayList<Long> ids = new ArrayList<Long>(keywordsToIds.values()); // Retrieve the values from the ID map
ArrayList<Integer> occurrence = new ArrayList<Integer>(firstCounter.values()); // Retrieve the values from the Occurrence map
for (int i=0; i<ids.size(); i++) { // Loop through each, value set
idsToOccurrence.put(ids.get(i), occurrence.get(i)); // Add the values to the ID/Occurrence map
}
for (String key : keywordsToIds.keySet()) { // Loops through each item in the map(s)
Long longID = keywordsToIds.get(key);
System.out.println(key+" --> "+longID+" --> "+idsToOccurrence.get(longID)); // Displays the keys pointing to each other
}
Hope this helps clarify things for you. If you're still a bit confused about Maps, take a look here.
I got it. I actually just had to use "getKey" on both, instead of get Value for the second map. Actually sat down hours for this simple change. Pretty new to maps so got confused with getKey and getValue
idAndCount.put(keywordsToIds.get(entry.getKey()), firstCounter.get(entry.getKey()));

Java HashMap - How to simultaneously get and then remove a random entry from a HashMap?

I was wondering if it is possible to get a random value from a HashMap and then straight after remove that key/value from the HashMap? I can't seem to find any method that works, would a different data structure be more appropriate for this?
Edit:
I should've been more clear, I generate a random number and then retrieve the value that corresponds with that random number. I need to return the value and then remove the entry from the map.
Maybe Map#computeIfPresent would work in your case. From its documentation:
If the value for the specified key is present and non-null, attempts to compute a new mapping given the key and its current mapped value.
If the remapping function returns null, the mapping is removed.
var map = new HashMap<Integer, String>();
map.put(1, "One");
map.put(2, "Two");
map.put(3, "Three");
map.computeIfPresent(2, (k, v) -> {
// `v` is equal to "Two"
return null; // Returning `null` removes the entry from the map.
});
System.out.println(map);
The above code outputs the following:
{1=One, 3=Three}
If you were to use a ConcurrentHashMap, then this would be an atomic operation.
The best way to both return and remove the key-value pair from a HashMap is by using the remove(key) method. This method removes the entry associated with the key and returns its corresponding value.
Integer randomNumber = new Random().nextInt(10);
Map<Integer, String> map = new HashMap<>();
String valueOfRandomNumberKey = map.remove(randomNumber);
The problem, as I understand it, is this: given a HashMap you want to
Choose a key at random from among the the keys currently associated in the Map;
Remove that association of that randomly chosen key from the map; and
Return the value that had, until recently, been associated with that key
Here's an example of how to do this, along with some a little test/demonstration routine:
public class Main
{
private static <K, V> V removeRandomEntry(Map<K, V> map){
Set<K> keySet = map.keySet();
List<K> keyList = new ArrayList<>(keySet);
K keyToRemove = keyList.get((int)(Math.random()*keyList.size()));
return map.remove(keyToRemove);
}
public static void main(String[] args){
Map<String, String> map = new HashMap<>();
for(int i = 0; i < 100; ++i)
map.put("Key" + i, "Value"+i);
int pass = 0;
while (!map.isEmpty())
System.out.println("Pass " + (++pass) + ": Removed: " + removeRandomEntry(map));
}
}
I would do it like this:
Hashmap<Integer, Object> example;
int randomNum = ThreadLocalRandom.current().nextInt(0, example.size());
example.getValue() //do something
example.remove(new Integer(randomNum));

How does a hashmap work if all its keys have the same value?

If I have a hashmap say HashMap<Integer, String> map = new HashMap<>(); If I have all the values e.g 1 up to 100 all storing the same object. In memory, will this be 100 instances of that object or 100 pointers to one object.
Why?
Well if you have a map with HashMap<String, Integer> (notice the swap in generics) and the string is a word and the integer is the number of occurrences if I need to pick a word at random however such that it is proportional to the number of its occurences then a quick way would be just to fill an arraylist with the word "cat" 100 times and the rest accordingly (to "convert" the hashmap into an arraylist) and that way when a random number is picked using list.get(i) then its proportional to its occurences.
So this will take as n words * m occurences which means a huge list. So how efficient would it be to use a HashMap instead.
If indeed there will be pointers from the key to the value (when they repeat) then surely the map is a better approach.
After looking in the Map implemetation, Map#put() uses the static class Node<K,V> which is handling references
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
example:
final Map<Integer, Point> map = new HashMap<>();
final Point xPoint = new Point(0, 0);
map.put(1, xPoint);
map.put(2, xPoint);
map.put(3, xPoint);
System.out.println(map);
// modify the point
System.out.println(xPoint);
xPoint.setX(555);
System.out.println(xPoint);
System.out.println(map);
and I gave a try defininf a custom MAp Integer, Point, (custom point)
Map<Integer, Point> map = new HashMap<>();
Point xPoint = new Point(0, 0);
map.put(1, xPoint);
map.put(2, xPoint);
map.put(3, xPoint);
System.out.println(map);
// modify the point
System.out.println(xPoint);
xPoint.setX(555);
System.out.println(xPoint);
System.out.println(map);
as you can see, modifing the point will affect the hole map, since all nodes.V are pointing to the same ref.
It seems to me that the two options you are considering are either :
List<String> words = new ArrayList<>();
words.add("cat");
words.add("cat");
...
words.add("cat");
vs.
Map<Integer,String> words = new HashMap<>();
words.put(0,"cat");
words.put(1,"cat");
...
words.put(99,"cat");
Both the List and the Map would contain multiple reference to the same String object ("cat"). The Map would require more memory, though, since it also has to store the keys.
In addition, you have no easy way of obtaining the i'th String value of the Map for a given random i, since HashMap has no order.
Therefore your List solution is preferable to your suggested Map<Integer,String> alternative.
That said, you could build a more efficient TreeMap that would allow you to get a random String depending on its number of occurrences.
One way I can think of :
TreeMap<Integer,String> map = new TreeMap<>();
map.put(0,"cat");
map.put(100,"dog");
This TreeMap represents 100 occurrences of "cat" and 20 occurrences of "dog". Now, if you draw a random number from 0 to 119, you can easily check whether it lands in the range of "cat" or "dog".
For example, if you draw the number 105, you obtain the corresponding String with :
String randomStr = map.ceilingEntry(105).getValue();
All that remains is to convert the HashMap<String, Integer> containing the number of occurrences to the corresponding TreeMap<Integer, String> :
HashMap<String, Integer> occurrences = ...
TreeMap<Integer, String> map = new TreeMap<>();
int count = 0;
for (Map.Entry<String,Integer> entry : occurrences.entrySet()) {
map.put (count, entry.getKey());
count += entry.getValue();
}
Note that I'm using TreeMap instead of HashMap in order to be able to efficiently obtain the entry having the least key greater than or equal to the given key (without having to iterate over all the entries). This is only possible in NavigableMaps.

How do I sort the elements of an HashMap according to their values? [duplicate]

This question already has answers here:
Closed 10 years ago.
I have the following HashMap:
HashMap<String, Integer> counts = new HashMap<String, Integer>();
What is the simplest way to order it according to the values?
You can't sort a Map by the values, especially not a HashMap, which can't be sorted at all.
Instead, you can sort the entries:
List<Map.Entry<String, Integer>> entries = new ArrayList<Map.Entry<String, Integer>>(map.entrySet());
Collections.sort(entries, new Comparator<Map.Entry<String, Integer>>() {
public int compare(
Map.Entry<String, Integer> entry1, Map.Entry<String, Integer> entry2) {
return entry1.getValue().compareTo(entry2.getValue());
}
});
will sort the entries in ascending order of count.
You can get a set of entries (Set of Map.Entry) from a map, by using map.entrySet(). Just iterate over them, and check the values by getValue().
A work around, if you want to them print them in order(Not storing).
Create a new Map (tempMap) and put your value as key and key as value. To make the keys unique, please add some unique value in each of the keys e.g. key1 = value1+#0.
Get the list of values as map.values() as list myVlues
Sort the myVlues list as Collections.sort(myVlues)
Now iterate the myVlues, get the corresponding key from tempMap, restore the key e.g. key.substring(0, key.length-2) and print the key and value pair.
Hope this helps.
A TreeMap can keep its entries in an order defined by a Comparator.
We can create a comparator that will order the Map by putting the greatest value first.
Then, we will build a TreeMap that uses that Comparator.
We will then put all the entries in our counts map into the Comparator.
Finally, we will get the first key in the map, which should be the most common word (or at least one of them, if multiple words have equal counts).
public class Testing {
public static void main(String[] args) {
HashMap<String,Double> counts = new HashMap<String,Integer>();
// Sample word counts
counts.put("the", 100);
counts.put("pineapple",5);
counts.put("a", 50);
// Step 1: Create a Comparator that order by value with greatest value first
MostCommonValueFirst mostCommonValueFirst = new MostCommonValueFirst(counts);
// Step 2: Build a TreeMap that uses that Comparator
TreeMap<String,Double> sortedMap = new TreeMap<String,Integer (mostCommonValueFirst);
// Step 3: Populate TreeMap with values from the counts map
sortedMap.putAll(counts);
// Step 4: The first key in the map is the most commonly used word
System.out.println("Most common word: " + sortedMap.firstKey());
}
}
private class MostCommonValueFirst implements Comparator<String> {
Map<String, Integer> base;
public MostCommonValueFirst(Map<String, Integer> base) {
this.base = base;
}
// Note: this comparator imposes orderings that are inconsistent with equals.
public int compare(String a, String b) {
if (base.get(a) >= base.get(b)) {
return 1;
} else {
return -1;
} // returning 0 would merge keys
}
}
Source: https://stackoverflow.com/a/1283722/284685

how to shuffle key-value pairs?

I have a set of values which need to be shuffled when needed.
I don't know which variable type is best for me. Data is actually based on key-value structure.Like;
100 "white"
200 "black"
300 "red"
and like that. What I want to do is to change the key-value pairs according to I don't know yet, some algorithm.But they need to be shuffled like this, but shuffling need to be not random, so I can revert data when I need.
100 "red"
200 "white"
300 "black"
I don't really know how my approach should be to the solution. Should I use HashTable or something, and how can I shuffle them dynamically?
Any help is appreciated
Another way for shuffling the key-value mappings randomly:
public static <K,V> void shuffleMap(Map<K,V> map) {
List<V> valueList = new ArrayList<V>(map.values());
Collections.shuffle(valueList);
Iterator<V> valueIt = valueList.iterator();
for(Map.Entry<K,V> e : map.entrySet()) {
e.setValue(valueIt.next());
}
}
Edit:
If you don't want to change the original map (since you need it afterwards), you can create a new one instead:
public static <K,V> Map<K,V> shuffleMap(Map<K,V> map) {
List<V> valueList = new ArrayList<V>(map.values());
Collections.shuffle(valueList);
Iterator<V> valueIt = valueList.iterator();
Map<K,V> newMap = new HashMap<K,V>(map.size());
for(K key : map.keySet()) {
newMap.put(key, valueIt.next());
}
return newMap;
}
You do not really want a seemingly-randomly mixing which can be reverted (which quickly gets complicated), but simply retain your original map. If this does not fit, you need to describe your problem better.
Okay, you want to encrypt the mapping by using a secret key, giving another mapping, and then decrypt it again. Obviously random shuffling does not help here, and even pseudorandom is no good, since it gives no reliable way to reshuffle. In the basic case, your key would be a invertible map between the keys of our mapping.
public static <K,V> Map<K,V> encryptMap(Map<K,V> plainMap, Map<K,K> key) {
Map<K,V> cryptoMap = new HashMap<K,V>(plainMap.size());
for(Map.Entry<K,V> entry : plainMap.entrySet()) {
cryptoMap.put(key.get(entry.getKey()), entry.getValue());
}
return cryptoMap;
}
Decryption works the same, in fact, only using the reverse map of the key.
So, when you have your example keys of {100, 200, 300}, any permutation of these keys is a valid key for our "encryption scheme".
(There are only 6 possible ones, which is not very secure.)
Map sampleKey = new HashMap<Integer, Integer>();
sampleKey.put(100, 200);
sampleKey.put(200, 300);
sampleKey.put(300, 100);
Map sampleUnKey = new HashMap<Integer, Integer>();
for(Map.Entry<Integer, Integer> e : sampleKey) {
sampleUnKey.put(e.getValue(), e.getKey());
}
Map<Integer, String> data = new HashMap<Integer, String>();
data.put(100, "white");
data.put(200, "black");
data.put(300, "red");
System.out.println(data);
Map<Integer, String> encrypted = encryptMap(data, sampleKey);
System.out.println(encrypted);
Map<Integer, String> decrypted = encryptMap(data, sampleUnKey);
System.out.println(decrypted);
The map decrypted now should be the same as the original map.
For bigger keysets you would want to find a scheme to get a suitable
permutation of keys from some input-able key.
It looks like you need a list of tupples. A Map is exactly that. However, a standard like HashMap has no functionality for changing the relationship between key and value.
I think I would have implemented my own Map for this. Create a class that implements java.util.Map, implement the required methods and create some other methods for "mixing".
It all dependes on what functionality you really need on the list of tupples. Do you need to look up colors very fast? Can there be more than one tupple with the same numbers?
I am not sure how exactly you are going to shuffle the pairs, but if you need to shuffle them based on the key, you can use a Map:
Map<String, String> map = new HashMap<String, String>();
map.put("100", "white");
map.put("200", "black");
map.put("300", "red");
// swap 100 with 200
String temp = map.get("100");
map.put("100", map.get("200"));
map.put("200", temp);
Alternatively, if you need to shuffle the pair randomly, you can create a class Pair (which will basically store an int and a String), as suggested by larsmans, and store them in an array. Then, a slightly-modified version of Fisher-Yates shuffle can be used. Something along these lines:
// initialize list
List<Pair<Integer, String>> values = new ArrayList<Pair<Integer, String>>();
values.add(new Pair<Integer, String>(100, "white"));
values.add(new Pair<Integer, String>(200, "black"));
values.add(new Pair<Integer, String>(300, "red"));
// shuffle
System.out.println(values); // e.g., [100 white, 200 black, 300 red]
Random random = new Random();
for (int i = values.size() - 1; i > 1; i--) {
int j = random.nextInt(i + 1);
// swap values between i-th Pair and j-th Pair
Pair<Integer, String> iPair = values.get(i); // the iPair :-)
Pair<Integer, String> jPair = values.get(j);
String iString = iPair.getSecond();
iPair.setSecond(jPair.getSecond());
jPair.setSecond(iString);
}
System.out.println(values); // e.g., [100 red, 200 black, 300 white]

Categories

Resources