So I'm trying to create a smart data structure based off AVL tree and Hash Table.
I'm making sure I need to check first which implementation the data type will have depending on the size the list given to it.
For example, if I have a list n of size 1000, it'll be implemented using a Hash table. For anything more than 1000, using an AVL tree.
Code for this:
public class SmartULS<K,V> {
protected TreeMap<K,V> tree = new TreeMap<>();
protected AbstractHashMap<K,V> hashMap = new AbstractHashMap<K,V>();
public void setSmartThresholdULS(size){
int threshold = 1000;
if (size >= threshold) {
map = new AbtractMap<K,V>();
}
else
map = new TreeMap<K,V>();
}
}
Now after this, I should be writing the standard methods such as
get(SmartULS, Key), add(SmartULS, Key, Value), remove(SmartULS,Key), nextKey(Key), previousKey(Key), etc.
I'm really lost as to how to start this? I've thought about creating these methods like this(written in pseudo):
Algorithm add(SmartULS, Key, Value):
i<- 0
If SmartULS instanceof AbstractHashMap then
For i to SmartULS.size do
If Key equals to SmartULS[i] then
SmartULS.get(Key).setValue(Value)
Else
SmartULS.add(Key, Value)
Else if SmartULS instanceof TreeMap then
Entry newAdd equals new MapEntry(Key, Value)
Position<Entry> p = treeSearch(root( ), Key)
You're on the correct track, this is how I understood your question and implemented it:
public class SmartULS<K, V> {
Map<K,V> map;
public static final int THRESHOLD = 1000;
public SmartULS(int size) {
if(size < THRESHOLD) {
map = new HashMap();
} else {
map = new TreeMap();
}
}
public V get(K key) {
return map.get(key);
}
public V put(K key, V value) {
return map.put(key, value);
}
public V remove(K key) {
return map.remove(key);
}
}
Based on the initial size given, the constructor decides if to initialize a hash table or a tree. I also added a the get, put and remove functions and used the Map's interface functions.
I didn't understand what the nextKey and previousKey functions are suppose to do or return, so couldn't help there.
A way of using the class would be as follows:
public static void main(String[] args) {
SmartULS<String, String> smartULS = new SmartULS(952);
smartULS.put("firstKey", "firstValue");
smartULS.put("secondKey", "secondsValue");
String value = smartULS.get("firstKey");
smartULS.remove("secondKey");
}
Hope this helps:)
Related
I am creating a function that loops through a string, separates it by comma and then takes the key from the second item in the array and the value from the 1st after splitting the string.
I then want to place these values in a map. This works perfectly, however if i have two strings with the same key it doesn't add the value up it just replaces it.
For example if my string was
123,totti 100,roma, 100,totti
I would want
totti 223
roma 100
Here is my code
private void processCallLogs(String[] splitCalls) {
for (String individualCall : splitCalls) {
int duration = 0;
String[] singleCall = individualCall.split(",");
duration += DurationParser.returnDuration(singleCall[0]);
this.cost += CalculateCost.calculateCostPerCall(singleDuration);
if (totalCallDurations.containsKey(singleCall[1])) {
totalCallDurations.put(singleCall[1], singleDuration);
} else {
totalCallDurations.put(singleCall[1], duration);
}
}
}
You can replace the if with something like this:
if (totalCallDurations.containsKey(singleCall[1])) {
duration += totalCallDurations.get(singleCall[1]);
}
totalCallDurations.put(singleCall[1], duration);
Create a map and update the value if the key is present
public static void main(String[] args) {
myMap = new HashMap<>();
// 123,totti 100,roma, 100,totti
addToMap("totti", 123);
addToMap("roma", 100);
addToMap("totti", 100);
System.out.println(myMap);
}
private static void addToMap(String string, int i) {
int t = i;
if (myMap.get(string) != null) {
t += myMap.get(string);
}
myMap.put(string, t);
}
If you're using Java 8, you can do this easily with the Map.merge() method:
totalCallDurations.merge(singleCall[1], duration, Integer::sum);
If you want to make a map that will add the values together instead of replacing, I would recommend extending the Map type to make your own map. Since Map is very abstract. I would extend HashMap. (I suggest this both for code style and because it will make your code more extendable).
public class AdderMap extends HashMap<String, Integer> { // This extends the HashMap class
public Integer get(String key) { // This overrides the Map::get method
if(super.containsKey(key)) return super.get(key); // If the key-value pairing exists, return the value
else return 0; // If it doesn't exist, return 0
}
public Integer put(String key, Integer value) { // This overrides the Map::put method
Integer old_value = this.get(key); // Get the former value of the key-value pairing (which is 0 if it doesn't exist)
super.put(key, old_value + value); // Add the new value to the former value and replace the key-value pairing (this behaves normally when the former value didn't exist)
return old_value; // As per the documentation, Map::put will return the old value of the key-value pairing
}
}
Now, when you initialize your map, make it an AdderMap. Then, you can just use put(String, Integer) and it will add it together.
The advantage of this solution is that it helps with keeping your code clean and it allows you to use this type of map again in the future without needing separate code in your main code. The disadvantage is that it requires another class, and having too many classes can become cluttered.
I want to periodically iterate over a ConcurrentHashMap while removing entries, like this:
for (Iterator<Entry<Integer, Integer>> iter = map.entrySet().iterator(); iter.hasNext(); ) {
Entry<Integer, Integer> entry = iter.next();
// do something
iter.remove();
}
The problem is that another thread may be updating or modifying values while I'm iterating. If that happens, those updates can be lost forever, because my thread only sees stale values while iterating, but the remove() will delete the live entry.
After some consideration, I came up with this workaround:
map.forEach((key, value) -> {
// delete if value is up to date, otherwise leave for next round
if (map.remove(key, value)) {
// do something
}
});
One problem with this is that it won't catch modifications to mutable values that don't implement equals() (such as AtomicInteger). Is there a better way to safely remove with concurrent modifications?
Your workaround works but there is one potential scenario. If certain entries have constant updates map.remove(key,value) may never return true until updates are over.
If you use JDK8 here is my solution
for (Iterator<Entry<Integer, Integer>> iter = map.entrySet().iterator(); iter.hasNext(); ) {
Entry<Integer, Integer> entry = iter.next();
Map.compute(entry.getKey(), (k, v) -> f(v));
//do something for prevValue
}
....
private Integer prevValue;
private Integer f(Integer v){
prevValue = v;
return null;
}
compute() will apply f(v) to the value and in our case assign the value to the global variable and remove the entry.
According to Javadoc it is atomic.
Attempts to compute a mapping for the specified key and its current mapped value (or null if there is no current mapping). The entire method invocation is performed atomically. Some attempted update operations on this map by other threads may be blocked while computation is in progress, so the computation should be short and simple, and must not attempt to update any other mappings of this Map.
Your workaround is actually pretty good. There are other facilities on top of which you can build a somewhat similar solution (e.g. using computeIfPresent() and tombstone values), but they have their own caveats and I have used them in slightly different use-cases.
As for using a type that doesn't implement equals() for the map values, you can use your own wrapper on top of the corresponding type. That's the most straightforward way to inject custom semantics for object equality into the atomic replace/remove operations provided by ConcurrentMap.
Update
Here's a sketch that shows how you can build on top of the ConcurrentMap.remove(Object key, Object value) API:
Define a wrapper type on top of the mutable type you use for the values, also defining your custom equals() method building on top of the current mutable value.
In your BiConsumer (the lambda you're passing to forEach), create a deep copy of the value (which is of type your new wrapper type) and perform your logic determining whether the value needs to be removed on the copy.
If the value needs to be removed, call remove(myKey, myValueCopy).
If there have been some concurrent changes while you were calculating whether the value needs to be removed, remove(myKey, myValueCopy) will return false (barring ABA problems, which are a separate topic).
Here's some code illustrating this:
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
public class Playground {
private static class AtomicIntegerWrapper {
private final AtomicInteger value;
AtomicIntegerWrapper(int value) {
this.value = new AtomicInteger(value);
}
public void set(int value) {
this.value.set(value);
}
public int get() {
return this.value.get();
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof AtomicIntegerWrapper)) {
return false;
}
AtomicIntegerWrapper other = (AtomicIntegerWrapper) obj;
if (other.value.get() == this.value.get()) {
return true;
}
return false;
}
public static AtomicIntegerWrapper deepCopy(AtomicIntegerWrapper wrapper) {
int wrapped = wrapper.get();
return new AtomicIntegerWrapper(wrapped);
}
}
private static final ConcurrentMap<Integer, AtomicIntegerWrapper> MAP
= new ConcurrentHashMap<>();
private static final int NUM_THREADS = 3;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; ++i) {
MAP.put(i, new AtomicIntegerWrapper(1));
}
Thread.sleep(1);
for (int i = 0; i < NUM_THREADS; ++i) {
new Thread(() -> {
Random rnd = new Random();
while (!MAP.isEmpty()) {
MAP.forEach((key, value) -> {
AtomicIntegerWrapper elem = MAP.get(key);
if (elem == null) {
System.out.println("Oops...");
} else if (elem.get() == 1986) {
elem.set(1);
} else if ((rnd.nextInt() & 128) == 0) {
elem.set(1986);
}
});
}
}).start();
}
Thread.sleep(1);
new Thread(() -> {
Random rnd = new Random();
while (!MAP.isEmpty()) {
MAP.forEach((key, value) -> {
AtomicIntegerWrapper elem =
AtomicIntegerWrapper.deepCopy(MAP.get(key));
if (elem.get() == 1986) {
try {
Thread.sleep(10);
} catch (Exception e) {}
boolean replaced = MAP.remove(key, elem);
if (!replaced) {
System.out.println("Bailed out!");
} else {
System.out.println("Replaced!");
}
}
});
}
}).start();
}
}
You'll see printouts of "Bailed out!", intermixed with "Replaced!" (removal was successful, as there were no concurrent updates that you care about) and the calculation will stop at some point.
If you remove the custom equals() method and continue to use a copy, you'll see an endless stream of "Bailed out!", because the copy is never considered equal to the value in the map.
If you don't use a copy, you won't see "Bailed out!" printed out, and you'll hit the problem you're explaining - values are removed regardless of concurrent changes.
Let us consider what options you have.
Create your own Container-class with isUpdated() operation and use your own workaround.
If your map contains just a few elements and you are iterating over the map very frequently compared against put/delete operation. It could be a good choice to use CopyOnWriteArrayList
CopyOnWriteArrayList<Entry<Integer, Integer>> lookupArray = ...;
The other option is to implement your own CopyOnWriteMap
public class CopyOnWriteMap<K, V> implements Map<K, V>{
private volatile Map<K, V> currentMap;
public V put(K key, V value) {
synchronized (this) {
Map<K, V> newOne = new HashMap<K, V>(this.currentMap);
V val = newOne.put(key, value);
this.currentMap = newOne; // atomic operation
return val;
}
}
public V remove(Object key) {
synchronized (this) {
Map<K, V> newOne = new HashMap<K, V>(this.currentMap);
V val = newOne.remove(key);
this.currentMap = newOne; // atomic operation
return val;
}
}
[...]
}
There is a negative side effect. If you are using copy-on-write Collections your updates will be never lost, but you can see some former deleted entry again.
Worst case: deleted entry will be restored every time if map get copied.
I have defined an HashMap with the following code:
final Map<OrderItemEntity, OrderItemEntity> savedOrderItems = new HashMap<OrderItemEntity, OrderItemEntity>();
final ListIterator<DiscreteOrderItemEntity> li = ((BundleOrderItemEntity) oi).getDiscreteOrderItems().listIterator();
while (li.hasNext()) {
final DiscreteOrderItemEntity doi = li.next();
final DiscreteOrderItemEntity savedDoi = (DiscreteOrderItemEntity) orderItemService.saveOrderItem(doi);
savedOrderItems.put(doi, savedDoi);
li.remove();
}
((BundleOrderItemEntity) oi).getDiscreteOrderItems().addAll(doisToAdd);
final BundleOrderItemEntity savedBoi = (BundleOrderItemEntity) orderItemService.saveOrderItem(oi);
savedOrderItems.put(oi, savedBoi);
I put 4 items into the HashMap. When I debug, even if the size is 4, it only shows 3 elements:
This is the list of the elements it contains.
{DiscreteOrderItemEntity#1c29ef3c=DiscreteOrderItemEntity#41949d95, DiscreteOrderItemEntity#2288b93c=DiscreteOrderItemEntity#2288b93c, BundleOrderItemEntity#1b500292=BundleOrderItemEntity#d0f29ce5, DiscreteOrderItemEntity#9203174a=DiscreteOrderItemEntity#9203174a}
What can be the problem?
Hashmaps handle collisions.
Since your HashMap is composed by only 16 buckets, the hash of the element must be reduced to a number that spans between 0 and 15 (e.g. hash % 16). So two elements may be in the same bucket (the same HashMapNode).
You can inspect each HashMapNode to find out which one contains two elements.
The mechanism is explained as enrico.bacis, There is an example to reproduce it:
public class TestJava {
static class TT {
private String field;
#Override
public int hashCode() {
return 1;
}
}
public static void main(String[] args) {
Map<TT, String> test = new HashMap<>();
TT t1 = new TT();
TT t2 = new TT();
test.put(t1, "test2");
test.put(t2, "test2");
test.put(null, "test2");
test.put(null, "test2");
System.out.println(test.toString());
System.out.println(test.size());
}
}
In there we override hashCode and hard code return 1 that all objects of TT will return same hashCode 1.
and we can dig into HashMap.java:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
we can found when we put key/value pair into HashMap, it will calculate hash number by object's hashcode to locate the element's location in hash table.
so if the objects hash code are same, they will be stored in the same bucket in hash table. but these confilct elements still will be stored, because their key are not same.
For example... Adjacency list realiszation
public class Vertex {
String name;
boolean visited;
public Vertex(String name) {
this.name=name;
visited=false;
}
public int hashCode() {
return name.hashCode();
}
public boolean equals(Object ob) {
return hashCode()==ob.hashCode();
}
public String toString() {
return name;
}
}
The main class
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) {
PrintWriter pw=new PrintWriter(System.out);
Map<Vertex,Vertex> m=new HashMap();
m.put(new Vertex("a"), new Vertex("b"));// a ---> b
m.put(new Vertex("a"), new Vertex("c"));// a ---> c
m.put(new Vertex("a"), new Vertex("d"));// a ---> d
pw.println("All vertex from: ");
for (Vertex vert_from:m.keySet()) {
pw.print(vert_from+" ");
}
pw.println();
pw.println("All vertices to: ");
for (Vertex vert_to:m.values()) {
pw.print(vert_to+" ");
}
pw.close();
}
}
It outputs:
All vertex from:
a
All vertices to:
d
But i need that "All vertices to: b c d"
How can I fix that?
A Map indeed stores a single value per key. You could, however, store a collection in value, say a Set:
Map<Vertex, Set<Vertex>> m = new HashMap<>();
Set<Vertex> set = new HashSet<>();
set.add(new Vertex("b"));
set.add(new Vertex("c"));
set.add(new Vertex("d"));
m.add (new Vertex("a"), set);
Alternatively, you can use one of the common implementations of this concept, such as Apache Commons Collections' MultiValueMap or Guava's HashMultiMap.
What you are asking for is called a "Multi Map".
If you are using Java 8 then this is quite neat, first you need a Map<Vertex, Collection<Vertex>>. I don't know what properties you need from the Collection, that you will have to investigate yourself.
As you have overridden equals and hashCode (incorrectly, but a valiant attempt), I will assume that you want to have the items unique by name. I will also assume that order matters, so LinkedHashSet seems a good choice.
final Map<Vertex, Collection<Vertex>> graph = new HashMap<>();
Now, to add an item to the Map we need to first ensure that the Collection for that key is not null. This is exactly what the new Map.computeIfAbsent comes in.
final Vertex a = new Vertex("a");
graph.computeIfAbsent(a, v -> new LinkedHashSet<>()).add(new Vertex("b"));
graph.computeIfAbsent(a, v -> new LinkedHashSet<>()).add(new Vertex("c"));
graph.computeIfAbsent(a, v -> new LinkedHashSet<>()).add(new Vertex("d"));
So what this does is, when inserting a into the Map, if the Collection for that key is null, computes a new value for it.
Now to get all values for a key:
Collection<Vertex> values = graph.get(a);
You could wrap the Map<Vertex, Collection<Vertex>> in some sort of Graph class to hide the implementation details and to have neater code:
class Graph {
final Map<Vertex, Collection<Vertex>> graph = new HashMap<>();
public void put(final Vertex key, final Vertex value) {
graph.computeIfAbsent(key, k -> new LinkedHashSet<>()).add(value);
}
public Collection<Vertex> get(final Vertex key) {
return Optional.ofNullable(graph.get(key)).orElse(Collections.EMPTY_SET);
}
}
This also deals with returning an empty collection instead of null if a key is not present in the Map. Depending on your use case you might also want to wrap the returned Collection with Collections.unmodifiableCollection to prevent unwanted modifications:
public Collection<Vertex> get(final Vertex key) {
return Optional.ofNullable(graph.get(key))
.map(Collections::unmodifiableCollection)
.orElse(Collections.EMPTY_SET);
}
You could also use a Guava Multimap if you aren't averse to external libraries.
Using a Multimap for your problem, it could be written like that:
public static void main(String[] args) {
PrintWriter pw=new PrintWriter(System.out);
ListMultimap<Vertex,Vertex> m= ArrayListMultimap.create();
Vertex a = new Vertex("a"); // it's better to create each object once
Vertex b = new Vertex("b");
Vertex c = new Vertex("c");
Vertex d = new Vertex("d");
m.put(a,b);// a ---> b
m.put(a,c);// a ---> c
m.put(a,d);// a ---> d
pw.println("All vertex from: ");
for (Vertex vert_from:m.keySet()) { //exactly the same as in your code
pw.print(vert_from+" ");
}
pw.println();
pw.println("All vertices to: ");
for (Vertex vert_to:m.values()) { //exactly the same as in your code
pw.print(vert_to+" ");
}
pw.close();
}
To use Guava, just download the latest jar from here and add it to your libraries.
Explanation:
By definition, each java Map has a single key and a single value.
However, you can use a Collection (like a List), or an Array for value. This way, your Map will be defined like that:
Map<Vertex, List<Vertex>> m = new HashMap<>();
Each time you want to add an element value to the list of vertex key, you can do it that way:
List<Vertex> list = m.get(key);
if (list == null) {
list = new ArrayList<>();
}
list.add(value);
An easier way, is to use Guava's Multimaps. It is the same as a Map, but the value is a Collection. So, an ArrayListMultimap is quite what I described above. The way to use it, though is much simpler:
ListMultimap<Vertex, Vertex> m = ArrayListMultimap.create();
m.put(key, value1);
m.put(key, value2); //adds value2 to the key, which also contains value1
....
I am trying to make kind of highscores in Java.
Basically I want a hashmap to hold the double value (so index starts from the highest double, so it's easier for me to sort highscores) and then the second value will be the client object, like this:
private HashMap<Double, TempClient> players = new HashMap<Double, TempClient>();
And to insert a new value:
TempClient client = new TempClient(kills, rank, deaths, name);
this.players.put(client.getKdr(), client);
Now, of course I can't iterate through the hashmap because it gets the list item by key, not index.
How can I iterate through a hashmap? or any good ideas for my case?
I tried it in a Foo class:
Output:
0.5
0.6
0.9
0.1
2.5
Code:
public class Foo {
public static void main(String[] args) {
HashMap<Double, String> map = new LinkedHashMap<Double, String>();
map.put(0.5, "hey");
map.put(0.6, "hey1");
map.put(0.9, "hey2");
map.put(0.1, "hey425");
map.put(2.5, "hey36");
for (Double lol : map.keySet()) {
System.out.println(lol);
}
}
}
You can iterate like this.
for (Double k : players.keySet())
{
TempClient p = players.get(k);
// do work with k and p
}
If you want to keep keys sorted, use e.g. a TreeMap.
If you want to keep the keys in the order you inserted
them in there, use e.g. a LinkedHashMap.
The best way is to iterate through hashmap is using EntrySet.
for (Map.Entry<Double, TempClient> entry : map.entrySet()) {
Double key= entry.getKey();
TempClient value= entry.getValue();
// ...
}
You'd be better off making your TempClient objects implement Comparable, adding them to a list, and then just using Collections.sort().
Since you can't sort items in a HashMap, nor you can sort them by value in a TreeMap you could use a TreeSet with a custom class:
class Score implements Comparable<Score>
{
final Player player;
final int score;
Score(Player player, int score) {
this.player = player;
this.score = score;
}
public int compareTo(Score other) {
return Integer.compare(this.score, other.score);
}
public int hashCode() { return player.hashCode(); }
public boolean equals(Object o) { return this.player.equals(...); }
}
TreeSet<Score> scores = new TreeSet<Score>();
score.add(new Score(player, 500));
for (Score s : scores) {
..
}
This will have both the advantages:
it will be iterable
it will keep scores automatically sorted
It should work easily with consistente between equals, hashCode and compareTo but maybe you should tweak something (since it's untested code).