/*
* ARX: Powerful Data Anonymization
* Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.deidentifier.arx.framework.check.distribution;
import java.util.ArrayList;
import org.deidentifier.arx.framework.check.groupify.HashTableUtil;
/**
* A hash groupify operator.
*
* @author Fabian Prasser
* @author Florian Kohlmayer
*/
public class IntArrayDictionary {
/**
* Calculates the MURMUR v3 hashcode.
*
* @param key
* @return
*/
private static final int hashCodeMURMUR(final int[] key) {
int h1 = 0;
for (int i = 0; i < key.length; i++) {
int k1 = key[i];
k1 *= 0xcc9e2d51;
k1 = (k1 << 15) | (k1 >>> -15);
k1 *= 0x1b873593;
h1 ^= k1;
h1 = (h1 << 13) | (h1 >>> -13);
h1 = (h1 * 5) + 0xe6546b64;
}
h1 ^= (2 * key.length);
h1 ^= h1 >>> 16;
h1 *= 0x85ebca6b;
h1 ^= h1 >>> 13;
h1 *= 0xc2b2ae35;
h1 ^= h1 >>> 16;
return h1;
}
/** The entry array. */
private IntArrayDictionaryEntry[] buckets;
/** Current number of elements. */
private int elementCount;
/** The list. */
private final ArrayList<IntArrayDictionaryEntry> list;
/** Load factor. */
private final float loadFactor;
/**
* maximum number of elements that can be put in this map before having to
* rehash.
*/
private int threshold;
/**
* Constructs a new dictionary.
*
* @param capacity the capacity
*/
public IntArrayDictionary(int capacity) {
list = new ArrayList<IntArrayDictionaryEntry>();
if ((capacity >= 0) && (0.75f > 0)) {
capacity = HashTableUtil.calculateCapacity(capacity);
elementCount = 0;
buckets = new IntArrayDictionaryEntry[capacity];
loadFactor = 0.75f;
threshold = HashTableUtil.calculateThreshold(buckets.length,
loadFactor);
} else {
throw new IllegalArgumentException();
}
}
/**
* Clears the dictionary.
*/
public void clear() {
if (elementCount > 0) {
elementCount = 0;
HashTableUtil.nullifyArray(buckets);
list.clear();
}
}
/**
* Removes a element from the dictionary.
*
* @param index
*/
public void decrementRefCount(final int index) {
final IntArrayDictionaryEntry entry = list.get(index);
final int refCount = entry.decRefCount();
if (refCount == 0) { // entry no longer needed remove
list.set(index, null);
final int bucketIndex = entry.getHashcode() & (buckets.length - 1);
IntArrayDictionaryEntry prev = buckets[bucketIndex];
IntArrayDictionaryEntry e = prev;
while (e != null) {
final IntArrayDictionaryEntry next = e.getNext();
if (e == entry) { // found element
elementCount--;
if (prev == e) {
buckets[bucketIndex] = next;
} else {
prev.setNext(next);
}
}
prev = e;
e = next;
}
}
}
/**
* Returns the according entry.
*
* @param index
* @return
*/
public int[] get(final int index) {
return list.get(index).getKey();
}
/**
* Probes the dictionary and either inserts a new entry index or returns the
* corresponding entry index.
*
* @param key the key
* @return
*/
public int probe(final int[] key) {
final int hash = hashCodeMURMUR(key);
int index = hash & (buckets.length - 1);
IntArrayDictionaryEntry entry = findEntry(key, index, hash);
if (entry == null) {
if (++elementCount > threshold) {
rehash();
index = hash & (buckets.length - 1);
}
entry = createEntry(key, index, hash);
} else {
entry.incRefCount();
}
return entry.getValue();
}
/**
* Returns the element count of the dictionary.
*
* @return the int
*/
public int size() {
return elementCount;
}
/**
* Creates a new entry.
*
* @param key the key
* @param index the index
* @param hash the hash
* @return the hash groupify entry
*/
private IntArrayDictionaryEntry createEntry(final int[] key,
final int index,
final int hash) {
final IntArrayDictionaryEntry entry = new IntArrayDictionaryEntry(key,
hash,
list.size());
entry.setNext(buckets[index]);
buckets[index] = entry;
list.add(entry);
return entry;
}
/**
* Returns the according entry.
*
* @param key
* the key
* @param index
* the index
* @param keyHash
* the key hash
* @return the hash entry
*/
private final IntArrayDictionaryEntry findEntry(final int[] key,
final int index,
final int keyHash) {
IntArrayDictionaryEntry m = buckets[index];
while ((m != null) &&
((m.getHashcode() != keyHash) ||
(key.length != m.getKey().length) || !HashTableUtil.equals(key,
m.getKey()))) {
m = m.getNext();
}
return m;
}
/**
* Rehashes this operator.
*/
private void rehash() {
final int length = HashTableUtil.calculateCapacity((buckets.length == 0 ? 1
: buckets.length << 1));
final IntArrayDictionaryEntry[] newData = new IntArrayDictionaryEntry[length];
for (int i = 0; i < buckets.length; i++) {
IntArrayDictionaryEntry entry = buckets[i];
while (entry != null) {
final IntArrayDictionaryEntry next = entry.getNext();
final int index = entry.getHashcode() & (length - 1);
entry.setNext(newData[index]);
newData[index] = entry;
entry = next;
}
}
buckets = newData;
threshold = HashTableUtil.calculateThreshold(buckets.length, loadFactor);
}
}