I am designing a Hashtable with linear probing collision resolution. I have designed a capacity which defines the number of buckets, and a load factor. When the number of elements in the hashtable exceeds the product of the capacity and the load factor, the array should be rehashed. For some reason, the method is not even being called in my code. Here is my main method:
public class MyJHUHashMap<K extends Comparable<? super K>, V> implements JHUHashMap<K, V>{
private int cap;
private int numElements;
private double loadFactor;
private Node<K,V>[] hashtable;
public static void main(String[] args) {
MyJHUHashMap<Integer, Integer> m = new MyJHUHashMap<>();
m.put(1, 1);
m.put(2, 2);
m.put(3, 3);
m.put(4, 4);
m.put(5, 5);
m.put(6, 6);
m.put(7, 7);
System.out.println(m.cap);
System.out.println(m.size());
for(int i=0; i<m.hashtable.length; i++) {
if(m.hashtable[i] != null)
System.out.println(m.hashtable[i].key);
}
}
and here is my code:
private final class Node<K extends Comparable<? super K>, V> {
public K key;
public V val;
public Node() {
this.key = null;
this.val = null;
}
public Node(K theKey, V theValue) {
this.key = theKey;
this.val = theValue;
}
}
public MyJHUHashMap() {
this.cap = 11;
this.numElements = 0;
this.loadFactor = 0.5;
this.hashtable = new Node[cap];
}
public MyJHUHashMap(int pInitCap, double pLoadFactor) {
if(pInitCap > 0 && pLoadFactor > 0) {
this.cap = pInitCap;
this.loadFactor = pLoadFactor;
} else {
throw new IllegalArgumentException();
}
this.numElements = 0;
this.hashtable = new Node[cap];
}
#Override
public V put(K key, V value) {
if(containsKey(key)) {
int foundindex = find(key);
V prevObject = this.hashtable[foundindex].val;
//hashtable[foundindex] = new Node(key, value);
this.hashtable[foundindex].val = value;
return prevObject;
}
int index = key.hashCode() % cap;
if(this.hashtable[index] == null) {
this.hashtable[index] = new Node<K,V>(key, value);
this.numElements++;
return null;
}
boolean found = false;
//index += 1;
while(found) {
index += 1;
if(hashtable[index] == null) {
hashtable[index] = new Node<K,V>(key, value);
this.numElements++;
found = true;
//return null;
}
//index += 1;
}
if(this.numElements > this.cap * this.loadFactor) {
System.out.println("Rehashing...");
rehashAndResize();
}
return null;
}
public void rehashAndResize() {
//Node<K,V>[] temp = new Node[this.hashtable.length*2];
//Node<K,V>[] temp = new Node[this.hashtable.length*2];
System.out.println("Calling rehash method");
MyJHUHashMap<K,V> temp = new MyJHUHashMap<K, V>(this.hashtable.length * 2, this.loadFactor);
for(int i=0; i<this.hashtable.length; i++) {
temp.put(this.hashtable[i].key, this.hashtable[i].val);
}
this.hashtable = temp.hashtable;
}
"Rehashing" and "Calling rehash method" are not being printed, so the method is not being called, which I can't fathom why. I input 7 elements, and the hashtable prints out 7 correctly when I ask it the number of elements, so why isn't the condition being evaluated?
EDIT: Ok, after closer inspection I found that the rehash method was never being reached because the method was returning early. Thank you for your help!
Related
I'm creating a tree map class with the following method for obtaining an array of values:
#SuppressWarnings("unchecked")
public V[] values() {
V[] values = (V[]) new Object[size()];
//System.out.println(values.getClass().getName());
return values(root, 0, values);
}
Now, when I try to access an element in this array I get a Class Cast Exception.
private AVLTreeMap<Integer, Integer> rankingMap;
...
System.out.println(rankingMap.values()[0]); //Exception on this line
yeilds
Exception in thread "main" java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.Integer;
Could anyone explain why this is happening and how to fix it?
Thanks.
Edit: here is the entire tree map class
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
public class AVLTreeMap<K extends Comparable<K> , V> implements /*Map<K, V>,*/ Serializable{
private AVLTreeNode<K, V> root;
private int size;
public AVLTreeMap(){
root = null;
size = 0;
}
public int size(){
return size;
}
public void add(K key, V val){
root = insert(key, val, root);
size++;
}
private AVLTreeNode<K, V> insert(K key, V val, AVLTreeNode<K, V> currNode){
//AVLTreeNode<K, V> node = new AVLTreeNode<>(key, val);
if(currNode == null) {
return new AVLTreeNode<K, V>(key, val);
}else if(currNode.key.compareTo(key) > 0){
currNode.left = insert(key, val, currNode.left);
}else if(currNode.key.compareTo(key) < 0) {
currNode.right = insert(key, val, currNode.right);
}else if(currNode.key.compareTo(key) == 0) {
currNode.addDup(val);
}
updateNode(currNode);
return rebalance(currNode);
}
private int balanceFactor(AVLTreeNode<K, V> node) {
if(node.left == null && node.right == null){
return 0;
}else if(node.left == null){
return -node.right.height;
}else if(node.right == null){
return node.left.height;
}else {
return node.left.height - node.right.height;
}
}
private AVLTreeNode<K, V> rotateRight(AVLTreeNode<K, V> node){
AVLTreeNode<K, V> swapNode = node.left;
node.left = swapNode.right;
swapNode.right = node;
updateNode(node);
updateNode(swapNode);
return swapNode;
}
private AVLTreeNode<K, V> rotateLeft(AVLTreeNode<K, V> node){
AVLTreeNode<K, V> swapNode = node.right;
node.right = swapNode.left;
swapNode.left = node;
updateNode(node);
updateNode(swapNode);
return swapNode;
}
private void updateNode(AVLTreeNode<K, V> node){
if(node.left == null && node.right == null){
node.height = 1;
node.size = 1;
}else if(node.left == null){
node.height = node.right.height + 1;
node.size = node.right.size + 1;
}else if(node.right == null){
node.height = node.left.height + 1;
node.size = node.left.size + 1;
}else {
node.height = Math.max(node.left.height, node.right.height) + 1;
node.size = node.left.size + node.right.height + 1;
}
}
private AVLTreeNode<K, V> rebalance(AVLTreeNode<K, V> node) {
if (balanceFactor(node) < -1) {
if (balanceFactor(node.right) > 0) {
node.right = rotateRight(node.right);
}
node = rotateLeft(node);
}
else if (balanceFactor(node) > 1) {
if (balanceFactor(node.left) < 0) {
node.left = rotateLeft(node.left);
}
node = rotateRight(node);
}
return node;
}
public String tree(){
return tree(root, 0, false);
}
private String tree(AVLTreeNode<K, V> node, int tabs, boolean left){
String s = "\n";
for (int i = 0; i < tabs - 1; i++) {
s+=" ";
}
s += "----";
if(node.left == null && node.right == null){
return s + "|" + node.toString();
}else if(node.left == null){
return s + " |null" + s + "|" + node.toString() + "|" + tree(node.right, tabs + 1, false);
}else if(node.right == null){
return tree(node.left, tabs + 1, true) + s + "|"+ node.toString() + "|" + s+ " |null";
}
return tree(node.left, tabs + 1, true) + s + "|"+ node.toString() +"|" + tree(node.right, tabs + 1, false);
}
#SuppressWarnings("unchecked")
public K[] keys() {
K[] keys = (K[]) new Comparable[size()]; //https://stackoverflow.com/questions/34827626/cannot-be-cast-to-ljava-lang-comparable
return keys(root, 0, keys);
}
private K[] keys(AVLTreeNode<K, V> node, int idx, K[] keys){
if (node != null) {
keys = keys(node.left, idx, keys);
idx += (node.left != null) ? node.left.size : 0;
keys[idx++] = node.key;
for (int i = 0; i < node.dups; i++) {
keys[idx++] = node.key;
}
keys = keys(node.right, idx, keys);
}
return keys;
}
#SuppressWarnings("unchecked")
public V[] values() {
V[] values = (V[]) new Object[size()];
//System.out.println(values.getClass().getName());
return values(root, 0, values);
}
private V[] values(AVLTreeNode<K, V> node, int idx, V[] values){
if (node != null) {
values = values(node.left, idx, values);
idx += (node.left != null) ? node.left.size : 0;
values[idx++] = node.val;
addDuplicates(node, values, idx);
idx += node.dups;
values = values(node.right, idx, values);
}
return values;
}
private void addDuplicates(AVLTreeNode<K, V> node, V[] arr, int idx){
DuplicateNode<V> dup = node.nextDup;
for (int i = 0; i < node.dups; i++) {
arr[idx++] = dup.val;
dup = dup.next;
}
}
private static class AVLTreeNode<K, V>{
public int height;
public AVLTreeNode<K, V> left;
public AVLTreeNode<K, V> right;
public K key;
public V val;
public int dups;
public int size;
public DuplicateNode<V> nextDup;
public AVLTreeNode(K key, V val){
this.key = key;
this.val =val;
left = null;
right = null;
size = 1;
height = 1;
dups = 0;
nextDup = null;
}
public void addDup(V val){
DuplicateNode<V> dup = new DuplicateNode<>(val);
dup.next = nextDup;
nextDup = dup;
dups++;
size++;
}
public String toString(){
return key.toString();
}
}
private static class DuplicateNode<V>{
public V val;
public DuplicateNode<V> next;
public DuplicateNode(V val){
this.val = val;
next = null;
}
}
}
I get an array of the correct values but incorrect types.
You might value to consider why java.util.Collection.toArray has two overloads:
One taking no arguments, returns Object[]
One taking an array argument T[], and returns an array of type T[].
The reason is that it's not possible to construct a generic array. If it were possible, the second method wouldn't be necessary.
You need to take the same (or similar) approach: pass a V[] as a parameter that you can use to construct the array instance:
public V[] values(V[] array) {
// Basically, this: http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/classes/java/util/AbstractCollection.java#l176
V[] values = (V[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size());
//System.out.println(values.getClass().getName());
return values(root, 0, values);
}
There are alternatives. For example, you could pass a IntFunction<V[]> (or a V[], or a V, or a Class<V>):
public V[] values(IntFunction<V[]> arraySupplier) {
V[] values = arraySupplier.apply(size());
//System.out.println(values.getClass().getName());
return values(root, 0, values);
}
If you don't want to pass it to the method, you could pass it to the constructor, and use that in values():
public AVLTreeMap(IntFunction<V[]> arraySupplier){
this.arraySupplier = arraySupplier; // Assign to a field
}
public V[] values() {
V[] values = arraySupplier.apply(size());
// ...
}
You should choose your approach based on how burdensome it is to construct and use the class.
The first time I get the collision in put method ie when hasKey returns -1 the rehashing method starts and value that triggered collision goes to doubled array to likely empty slot. But System.out.println(m.get("1000")); gives me null for some keys which means they were lost. I don't understand how they could be lost because there is nothing to override them in keyArray.
import java.util.*;
public class StringMapParallel implements Iterable<String>{
private int nButckets = 2000;
private String[] keyArray = new String[nButckets];
private String[] valueArray = new String[nButckets];
private int numberOfEntries = 0;
public static void main(String[] args) {
StringMapParallel m = new StringMapParallel();
;
for (int i = 0; i < 8000; i++) {
m.put(String.valueOf(i), String.valueOf(i));
}
System.out.println(m.get("1000"));
}
private void rehashing() {
if (numberOfEntries > (int) (0.3 * nButckets)) {
int newBucketsNumber = nButckets * 2;
String[] newKeyArray = new String[newBucketsNumber];
String[] newValueArray = new String[newBucketsNumber];
for (int i = 0; i < keyArray.length; i++) {
if (keyArray[i] != null) {
int index = keyArray[i].hashCode() % newBucketsNumber;
newKeyArray[index] = keyArray[i];
newValueArray[index] = valueArray[keyArray[i].hashCode() % nButckets];
}
}
/*
for (String key: this) {
int index = key.hashCode() % newBucketsNumber;
newKeyArray[index] = key;
if (key == null) System.out.println(key);
newValueArray[index] = valueArray[key.hashCode() % nButckets];
}
*/
keyArray = newKeyArray;
valueArray = newValueArray;
nButckets = newBucketsNumber;
}
}
public void put(String key, String value) {
int index = key.hashCode() % nButckets;
int hasKey = hasKey(index, key);
if (hasKey == 1) {
valueArray[index] = value;
} else if (hasKey == 0){
keyArray[index] = key;
valueArray[index] = value;
numberOfEntries++;
} else {
rehashing();
index = key.hashCode() % nButckets;
keyArray[index] = key;
valueArray[index] = value;
numberOfEntries++;
}
}
public String get(String key) {
int index = key.hashCode() % nButckets;
if (hasKey(index, key) == 1) return valueArray[index];
return null;
}
private int hasKey(int index, String key) {
if (keyArray[index] == null) {
return 0;
} else if (keyArray[index].equals(key)) {
return 1;
} else {
return -1;
}
}
public Iterator<String> iterator(){
Iterator<String> iter = new Iterator<String>() {
private int currentIndex = 0;
private int nEntries = 0;
#Override
public boolean hasNext() {
return nEntries < numberOfEntries && numberOfEntries != 0;
}
#Override
public String next() {
for (int i = currentIndex; i < keyArray.length; i++) {
if (keyArray[i] != null) {
currentIndex = i + 1;
nEntries++;
return keyArray[i];
}
}
return null;
}
};
return iter;
}
}
The value of int index = key.hashCode() % nButckets; in your algorithm for key 1000 and 6357 is same, which is 1423. Hence, the your algorithm overwrites keyArray[1423]=1000 with keyArray[1423]=6357
When you are printing m.get(String.valueOf(1000)), following get() method checks for index as well as key, hence it would return null. Read comment in the code for further explanation.
public String get(String key) {
//System.out.println(key+" "+ key.hashCode());
int index = key.hashCode() % nButckets;
System.out.println(index+"bfb"+hasKey(index, key));
//hasKey(index, key) would return -1, because key[1423] is 6357, and not 1000 as you expected.
if (hasKey(index, key) == 1) return valueArray[index];
return null;
}
private int hasKey(int index, String key) {
//System.out.println(keyArray[index]);
if (keyArray[index] == null) {
return 0;
} else if (keyArray[index].equals(key)) {
return 1;
} else {
return -1;
}
}
I am trying to create a class called HybridTST, where I can add multiple different TST's to a bigger data structure.
I have tried creating an array of Ternary Search Trees to a letter in the alphabet and then when I want to go to a specific TST I can iterate through the alphabet to find the correct TST. I am not sure if that the outcome is what I want. Am I creating a this Hybrid TST incorrectly?
public class HybridTST<E> implements TrieInterface
{
private TST[] myHybridTST = new TST[54];
private String alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.'";
private int position = 0;
private int size;
HybridTST()
{
char[] alphabetArray = alphabet.toCharArray();
for (int i = 0; i<54; i++)
{
myHybridTST[i]= new TST(alphabetArray[i]);
}
}
#Override
public Object get(String key) {
if (key == null) {
throw new InvalidKeyException();
}
for (int i = 0; i < key.length(); i++) {
if (!Character.isLetter(key.charAt(i))) {
throw new InvalidKeyException();
}
}
position = 0;
for(char c: alphabet.toCharArray())
{
if(key.charAt(0)==c)
{
break;
}
position++;
}
return myHybridTST[position].get(key);
}
Here is the TST get portion:
public Object get(String key) {
if (key == null) {
throw new InvalidKeyException();
}
if (key == null)
throw new NullPointerException();
if(key!="aren't")
{
for (int i = 0; i < key.length(); i++) {
if (!Character.isLetter(key.charAt(i))) {
throw new InvalidKeyException();
}
}
}
Node x = get(root, key, 0);
if (x == null) return null;
return x.val;
}
private Node get(Node x, String key, int d) {
if (key == null) {
throw new InvalidKeyException();
}
if (key.length() == 0)
throw new IllegalArgumentException("key must have length >= 1");
if (x == null)
return null;
char c = key.charAt(d);
if (c<x.c)
{
return get(x.left, key, d);
}
else if(c>x.c)
{
return get(x.right, key, d);
}
else if(d < key.length()-1)
{
return get(x.mid, key, d+1);
}
else
{
return x;
}
}
Here is the test:
public void test10()
{
HybridTST<Integer> t = new HybridTST<Integer>();
t.put("A",new Integer(0));
t.put("AB",new Integer(1));
t.put("ABC",new Integer(2));
assertEquals( new Integer(0), t.get("A") );
assertEquals( new Integer(1), t.get("AB") );
assertEquals( new Integer(2), t.get("ABC") );
}
It its failing to get any items that I have put into the data structure.
My Hybrid TST PUT method:
public void put(String key, Object val) {
if (key == null) {
throw new InvalidKeyException();
}
for (int i = 0; i <1; i++) {
if (!Character.isLetter(key.charAt(i))) {
throw new InvalidKeyException();
}
}
int count = 0;
int position = 0;
position = 0;
for(char c: alphabet.toCharArray())
{
if(key.charAt(0)==c)
{
break;
}
position++;
}
size++;
myHybridTST[position].put(key, val);
}
In my TST class,
private static class Node {
private char c; // character
private Node left, mid, right; // left, middle, and right
// subtries
private Object val; // value associated with string
public Node[] next;
Node(char c) {
this.c = c;
}
}
public TST(char c) {
this.root = new Node(c);
}
I have no problem running your code, inserting and fetching values from the tree. However, it can be improved in some way:
Remove unused attributes
Use generics so you force the type of the elements you insert and fetch from the HybridTST and TST
Indentation is sometimes off
The code doesn't work as is (Node doesn't have method visit)
Instead of getting the position of the first letter on the alphabet, create a helper method for that.
Instead of calling new Integer(i), use Integer.valueOf(i)
From what I can see, your code contains the attribute of a 256-way trie plus the one for a ternary search tree, you should clean it
I'm new to Java and I'm having some problems shuffling a list.
I have to write the Iterable class so that when you iterate over the queue it returns the items in a randomly order. That's why I wrote that shuffle method and then I'm calling it inside RandomizedQueueIterator class
import java.util.Iterator;
import java.util.NoSuchElementException;
public class RandomizedQueue<Item> implements Iterable<Item>
{
// instance variables
private Item[] queue;
private int N = 0;
private int R = 0;
// constructor
public RandomizedQueue()
{
queue = (Item[]) new Object[1];
}
// helper functions
private void resize(int capacity)
{
Item[] copy = (Item[]) new Object[capacity];
for (int i = 0; i < N; i++)
copy[i] = queue[i];
queue = copy;
}
private void shrink(int capacity)
{
Item[] copy = (Item[]) new Object[capacity];
int M = 0;
for (int i = 0; i < N; i++)
{
if (queue[i] != null)
copy[M++] = queue[i];
}
queue = copy;
}
// shuffle queue
private void shuffle(Item[] a )
{
int N = a.length;
for (int i = 0; i < N; i++)
{
int r = StdRandom.uniform(i + 1);
Item temp = a[r];
a[r] = a[i];
a[i] = temp;
}
}
// return wether queue is empty
public boolean isEmpty() { return queue.length == 0; }
// return size of queue
public int size() { return queue.length; }
// add item to the queue
public void enqueue(Item item)
{
if (N == queue.length) { resize(N*2); }
queue[N++] = item;
}
// remove and return a random item from the queue
public Item deque()
{
// generate a random index and store item
int random_index;
do {
random_index = StdRandom.uniform(0, N);
} while (queue[random_index] == null);
Item item = queue[random_index];
// delete item from array and increment counter of null items
queue[random_index] = null;
R += 1;
// shrink array if corresponds
if (R == (3/4 * N)) { shrink(N/2); }
// return item
return item;
}
// return a random item from the queue
public Item sample()
{
// generate a random index and store item
int random_index;
do {
random_index = StdRandom.uniform(0, N);
} while (queue[random_index] == null);
Item item = queue[random_index];
// return item
return item;
}
// iteration
public Iterator<Item> iterator()
{
return new RandomizedQueueIterator();
}
private class RandomizedQueueIterator implements Iterator<Item>
{
private int i = 0;
shuffle(queue);
public boolean hasNext()
{
return i < N;
}
public Item next()
{
if (i >= N)
{
throw java.util.NoSuchElementException;
}
return queue[i++];
}
public void remove()
{
throw new UnsupportedOperationException("Invalid operation for array");
}
}
// main method
public static void main(String[] args)
{
RandomizedQueue example = new RandomizedQueue();
example.enqueue(0);
example.enqueue(1);
example.enqueue(2);
example.enqueue(3);
example.enqueue(3);
example.enqueue(3);
example.enqueue(3);
}
}
The output when trying to compile:
2 errors found:
[line: 114] Error: invalid method declaration; return type required
[line: 114] Error: expected
line:114 > shuffle(queue);
Any help would be really appreciated.
Thanks
If you want to run the class you need to put the method call in a main method
public static void main(String...args) {
shuffle(list_test);
}
If you do this you will either define list_test as a static variable or define it inside the main method.
There are a couple of problems. One of the problems is that you are calling the function method "inside" your class not in a function. You should be calling the function outside after you have create the test object.
So In your main. Call:
list_test = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
test testObj = new test();
newList = test.shuffle(list_test); // in the case it returns a new list
where
private void shuffle(int[] a )
// and returns a new list
return shuffled_list
If you only care about shuffling items in your list, use the JDK built-in shuffle() method instead.
There are quite a few things that can be improved. Your class might look like this:
import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Random;
// it is standard to use E or T for collections
public class RandomizedQueue<E> implements Iterable<E> {
// most java collections have a default size of 16, set at 2 for testing
private static final int DEFAULT_INITIAL_CAPACITY = 2;
private static final Random GENERATOR = new Random();
// have good variable names
private boolean iterating = false; // to avoid concurrent modification
private E[] queue;
private int size = 0;
#SuppressWarnings("unchecked")
public RandomizedQueue() {
queue = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
// remove and return a random item from the queue
public E dequeue() {
if (iterating) {
throw new ConcurrentModificationException();
}
return dequeue(GENERATOR.nextInt(size));
}
// add item to queue
public void enqueue(E item) {
if (iterating) {
throw new ConcurrentModificationException();
}
if (size == queue.length) {
resize(size * 2);
}
queue[size++] = item;
}
// return whether queue is empty
public boolean isEmpty() {
return size == 0;
}
// iteration
public Iterator<E> iterator() {
return new RandomizedQueueIterator();
}
// return a random item from the queue
public E sample() {
if (isEmpty()) {
throw new NoSuchElementException("trying to sample from empty queue");
}
return queue[GENERATOR.nextInt(size)];
}
// shuffle queue
public void shuffle() {
for (int i = size - 1; i >= 0; i--) {
swap(i, GENERATOR.nextInt(i + 1));
}
}
// return size of queue
public int size() {
return size;
}
// helper functions
private E dequeue(int randomIndex) {
if (isEmpty()) {
throw new NoSuchElementException("trying to remove from empty queue");
}
size--;
// resize if only 1/4 of the queue is full
if (size < queue.length / 4) {
resize(queue.length / 2);
}
// since all get data methods are random, only need to swap on remove
swap(randomIndex, size);
E randomItem = queue[size];
queue[size] = null;
return randomItem;
}
private void resize(int capacity) {
// copyOf is substantially faster since it uses arraycopy which is in native code
queue = Arrays.copyOf(queue, capacity);
}
// swap data at indices i and j
private void swap(int i, int j) {
E temp = queue[j];
queue[j] = queue[i];
queue[i] = temp;
}
private class RandomizedQueueIterator implements Iterator<E> {
private int currentIndex = 0;
private int indexToRemove = -1;
private RandomizedQueueIterator() {
iterating = true;
shuffle();
}
public boolean hasNext() {
return currentIndex < size;
}
public E next() {
if (currentIndex >= size) {
throw new NoSuchElementException();
}
indexToRemove = currentIndex;
E nextElement = queue[currentIndex++];
if (currentIndex == size) {
iterating = false;
}
return nextElement;
}
public void remove() {
if (indexToRemove == -1) {
throw new IllegalStateException();
}
// print for testing
System.out.println("r: " + dequeue(indexToRemove));
currentIndex--;
indexToRemove = -1;
}
}
// main method
public static void main(String[] args) {
int exampleSize = 7;
System.out.println("example 1:");
RandomizedQueue<Integer> example1 = new RandomizedQueue<>();
for (int i = 0; i < exampleSize; i++) {
example1.enqueue(i);
}
System.out.println("size: " + example1.size());
for (int i = 0; i < exampleSize; i++) {
System.out.println("d: " + example1.dequeue());
}
System.out.println("size: " + example1.size());
System.out.println("\nexample 2:");
RandomizedQueue<Integer> example2 = new RandomizedQueue<>();
for (int i = 0; i < exampleSize; i++) {
example2.enqueue(i);
}
System.out.println("size: " + example2.size());
Iterator<Integer> iterator = example2.iterator();
// iterator.remove(); throws IllegalStateException
while (iterator.hasNext()) {
System.out.println("i: " + iterator.next());
iterator.remove();
System.out.println("size: " + example2.size());
// iterator.remove(); should throws IllegalStateException
}
}
}
Use this to give you ideas on how to write your class.
So I need a 2-dimensional ConcurrentHashMap.
It has to be as blazing fast as possible, as I'm going to be adding to and updating its values extremely frequently. It's in a multithreaded application, hence the choice to use ConcurrentHashMap instead of just HashMap.
Both the "x" and "y" indices are integers with a known range (0 through 40,000,000).
What I need to know is: What's the most efficient way to implement this so it'll be as speedy as possible? The most obvious route is to do a literal 2-D hashmap:
ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, ValueObj>> foo;
Or I could make a private class "IntPair" with two properties x and y, and use that as a key... though if I do that, what's the most efficient way to do equals() and hashcode()? and will I wind up allocating too many new IntPairs? Could I keep a set of IntPairs for each x/y I've assigned, and then use a purely reflexive equals() such that I'm just checking for the exact same object instance?
Update:
Now that I've taken a closer look at Integer.valueOf(int), the specific caching model it uses wouldn't make sense here, since I'm dealing with a very sparse matrix with unpredictable entries. I really need to be caching all those IntPairs which are used, not a prespecified subset.
Intuitively, it seems to me that looking up an IntPair in a big map to see if I've already created it would, in fact, be more-or-less the same as just looking it up in the big "2-D" ConcurrentHashMap anyway, wouldn't it? So it seems the solution here is really to just use new IntPair(x,y) each time I look up a key. Yes?
It depends on how sparse your (x,y) points are, in the 40,000,000 x 40,000,000 matrix. My guess is that the matrix is going to be quite sparse anyway, so creating a lot of ConcurrentHashMaps is going to be expensive.
Your (immutable) IntPair suggestion seems more attractive in comparison. As you've suggested, you can even cache some of these pairs to improve performance (see Integer.valueOf(int) to see how this can be implemented using a static nested class and a static factory method). Since the hashcode will always be required, you can pre-compute it in the constructor and save it as a final field. To compute equals, you could use the identity equality for objects in the cache, otherwise you'll need to compare x and y individually.
EDIT: Here's the source code (OpenJDK) for Integer.valueOf(int).
ConcurrentHashMap is quite large, so you probably don't want a collection of them.
Short lived objects are actually very fast to allocate. Are you going to have to create the Integers anyway?
You could intern the coordinate objects, but the cost for just a lookup would probably be comparable to creating them anyway. The real win with Integer is that the same instances are shared when you keep around lots of them for some time.
If performance is really a huge issue, you could write (or use) a map-type object that maps longs to references. I wouldn't be surprised to see custom maps out there which also have functionality associated with coordinate systems (like finding nearest or within a range).
In response to Zach, Yes, the matrix will be very sparse.
I looked at the page you linked, and without a doubt the functionality of Integer.valueOf(int) would be ideal. If I developed a similar static method within my IntPair class, can I assume that I could define equals() to only check for strict reflexive equality?
That said, I don't see in that page where it explains how to implement that functionality using a static nested class and static factory method.... am I just missing it somehow? How do I do that?
Thanks!
I've made a Int2DMap implementation based on the standard Java HashMap. I find it is faster than using an IntPair as key. However it will need to be synchronized.
import java.io.*;
import java.util.*;
public class Int2DMap implements Map, Serializable {
private static final int DEFAULT_INITIAL_CAPACITY = 16;
private static final int MAXIMUM_CAPACITY = 1 << 30;
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
protected Entry[] table;
protected int size;
protected int threshold;
protected float loadFactor;
protected transient volatile int modCount;
public Int2DMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity) {
capacity <<= 1;
}
this.loadFactor = loadFactor;
this.threshold = (int) (capacity * loadFactor);
this.table = new Entry[capacity];
}
public Int2DMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public Int2DMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public boolean containsKey(Object key) {
int[] xy = (int[]) key;
return containsKey(xy[0], xy[1]);
}
public Object get(Object key) {
int[] xy = (int[]) key;
return get(xy[0], xy[1]);
}
public Object put(Object key, Object value) {
int[] xy = (int[]) key;
return put(xy[0], xy[1], value);
}
public Object remove(Object key) {
int[] xy = (int[]) key;
return remove(xy[0], xy[1]);
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
protected static final int indexFor(int x, int y, int length) {
return (x * 31 + y) & (length - 1);
}
public Object get(int x, int y) {
for (Entry e = table[indexFor(x, y, table.length)]; e != null; e = e.next) {
if (e.x == x && e.y == y) {
return e.value;
}
}
return null;
}
public boolean containsKey(int x, int y) {
return getEntry(x, y) != null;
}
protected Entry getEntry(int x, int y) {
for (Entry e = table[indexFor(x, y, table.length)]; e != null; e = e.next) {
if (e.x == x && e.y == y) {
return e;
}
}
return null;
}
public Object put(int x, int y, Object value) {
int i = indexFor(x, y, table.length);
for (Entry e = table[i]; e != null; e = e.next) {
if (e.x == x && e.y == y) {
Object oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(x, y, value, i);
return null;
}
protected void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int) (newCapacity * loadFactor);
}
protected void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry e = src[j];
if (e != null) {
src[j] = null;
do {
Entry next = e.next;
int i = indexFor(e.x, e.y, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
public void putAll(Map m) {
int numKeysToBeAdded = m.size();
if (numKeysToBeAdded == 0) {
return;
}
if (numKeysToBeAdded > threshold) {
int targetCapacity = (int) (numKeysToBeAdded / loadFactor + 1);
if (targetCapacity > MAXIMUM_CAPACITY)
targetCapacity = MAXIMUM_CAPACITY;
int newCapacity = table.length;
while (newCapacity < targetCapacity)
newCapacity <<= 1;
if (newCapacity > table.length)
resize(newCapacity);
}
for (Iterator i = m.entrySet().iterator(); i.hasNext();) {
Map.Entry e = (Map.Entry) i.next();
put(e.getKey(), e.getValue());
}
}
public Object remove(int x, int y) {
Entry e = removeEntryForKey(x, y);
return (e == null ? null : e.value);
}
protected Entry removeEntryForKey(int x, int y) {
int i = indexFor(x, y, table.length);
Entry prev = table[i];
Entry e = prev;
while (e != null) {
Entry next = e.next;
Object k;
if (e.x == x && e.y == y) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
protected Entry removeMapping(Object o) {
if (!(o instanceof Entry))
return null;
Entry entry = (Entry) o;
int x = entry.x;
int y = entry.y;
int i = indexFor(x, y, table.length);
Entry prev = table[i];
Entry e = prev;
while (e != null) {
Entry next = e.next;
if (e.x == x && e.y == y) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
public void clear() {
modCount++;
Entry[] tab = table;
for (int i = 0; i < tab.length; i++)
tab[i] = null;
size = 0;
}
public boolean containsValue(Object value) {
Entry[] tab = table;
for (int i = 0; i < tab.length; i++)
for (Entry e = tab[i]; e != null; e = e.next)
if (value.equals(e.value))
return true;
return false;
}
static class Entry implements Map.Entry {
final int x;
final int y;
Object value;
Entry next;
Entry(int x, int y, Object value, Entry next) {
this.x = x;
this.y = y;
this.value = value;
this.next = next;
}
public final Object getKey() {
return new int[] { x, y };
}
public final Object getValue() {
return value;
}
public final Object setValue(Object newValue) {
Object oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry) o;
int[] xy = (int[])e.getKey();
if (x == xy[0] && y == xy[1]) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return ((31 + x) * 31 + y);
}
public final String toString() {
return "[" + x + ", " + y + "]=" + value;
}
/**
* This method is invoked whenever the value in an entry is overwritten by
* an invocation of put(k,v) for a key k that's already in the HashMap.
*/
void recordAccess(Int2DMap m) {
}
/**
* This method is invoked whenever the entry is removed from the table.
*/
void recordRemoval(Int2DMap m) {
}
}
void addEntry(int x, int y, Object value, int bucketIndex) {
Entry e = table[bucketIndex];
table[bucketIndex] = new Entry(x, y, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
private abstract class HashIterator implements Iterator {
Entry next; // next entry to return
int expectedModCount; // For fast-fail
int index; // current slot
Entry current; // current entry
HashIterator() {
expectedModCount = modCount;
if (size > 0) { // advance to first entry
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
}
public final boolean hasNext() {
return next != null;
}
final Entry nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry e = current = next;
if (e == null)
throw new NoSuchElementException();
if ((next = e.next) == null) {
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
return e;
}
public void remove() {
if (current == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
int x = current.x;
int y = current.y;
current = null;
Int2DMap.this.removeEntryForKey(x, y);
expectedModCount = modCount;
}
}
private final class ValueIterator extends HashIterator {
public Object next() {
return nextEntry().value;
}
}
private final class KeyIterator extends HashIterator {
public Object next() {
return nextEntry().getKey();
}
}
private final class EntryIterator extends HashIterator {
public Map.Entry next() {
return nextEntry();
}
}
// Subclass overrides these to alter behavior of views' iterator() method
Iterator newKeyIterator() {
return new KeyIterator();
}
Iterator newValueIterator() {
return new ValueIterator();
}
Iterator newEntryIterator() {
return new EntryIterator();
}
public Set keySet() {
return new KeySet();
}
private final class KeySet extends AbstractSet {
public Iterator iterator() {
return newKeyIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
int[] xy = (int[]) o;
return Int2DMap.this.removeEntryForKey(xy[0], xy[1]) != null;
}
public void clear() {
Int2DMap.this.clear();
}
}
public Collection values() {
return new Values();
}
private final class Values extends AbstractCollection {
public Iterator iterator() {
return newValueIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsValue(o);
}
public void clear() {
Int2DMap.this.clear();
}
}
public Set entrySet() {
return new EntrySet();
}
private final class EntrySet extends AbstractSet {
public Iterator iterator() {
return newEntryIterator();
}
public boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Entry e = (Entry) o;
Entry candidate = getEntry(e.x, e.y);
return candidate != null && candidate.equals(e);
}
public boolean remove(Object o) {
return removeMapping(o) != null;
}
public int size() {
return size;
}
public void clear() {
Int2DMap.this.clear();
}
}
public static void main(String[] args) {
try {
Int2DMap map = new Int2DMap();
map.put(20, 6000, "Test");
System.out.println(map.size() == 1);
System.out.println(map.get(20, 6000) != null);
System.out.println("Test".equals(map.get(20, 6000)));
for (Iterator iter = map.values().iterator(); iter.hasNext();) {
System.out.println("Test".equals(iter.next()));
}
for (Iterator iter = map.keySet().iterator(); iter.hasNext();) {
int[] key = (int[])iter.next();
System.out.println(key[0] == 20 && key[1] == 6000);
}
for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
Map.Entry e = (Map.Entry)iter.next();
System.out.println(e.toString().equals("[20, 6000]=Test"));
}
map.remove(20, 6000);
System.out.println(map.size() == 0 && map.get(20, 6000) == null);
long start = System.nanoTime();
int max = 40000000;
for (int i = 0; i < 500000; i++) {
int x = (int)(Math.random() * max);
int y = (int)(Math.random() * max);
map.put(x, y, "");
int x2 = (int)(Math.random() * max);
int y2 = (int)(Math.random() * max);
Object o = map.get(x2, y2);
}
System.out.println(map.size());
System.out.println((System.nanoTime() - start) / 1000000);
Map map2 = new HashMap();
start = System.nanoTime();
for (int i = 0; i < 500000; i++) {
String key = "" + (int)(Math.random() * max) + "," + (int)(Math.random() * max);
map2.put(key, "");
String key2 = "" + (int)(Math.random() * max) + "," + (int)(Math.random() * max);
Object o = map2.get(key2);
}
System.out.println(map2.size());
System.out.println((System.nanoTime() - start) / 1000000);
} catch (Throwable t) {
t.printStackTrace();
}
}
}