package net.yadan.banana.experimental.set;
import net.yadan.banana.DebugLevel;
import net.yadan.banana.DefaultFormatter;
import net.yadan.banana.Formatter;
import net.yadan.banana.memory.Buffer;
import net.yadan.banana.memory.IBuffer;
import net.yadan.banana.memory.IMemAllocator;
import net.yadan.banana.memory.malloc.TreeAllocator;
public class HashSet implements ISet {
private static final double DEFAULT_GROWTH_FACTOR = 2.0;
private static final int NEXT_OFFSET = 0;
private static final int KEY_SIZE_OFFSET = 1;
private static final int KEY_DATA_OFFSET = 2;
IMemAllocator m_memory;
private final double m_loadFactor;
private double m_growthFactor;
private int m_size;
/**
* The table is rehashed when its size exceeds this threshold. (The value of
* this field is (int)(capacity * loadFactor).)
*/
private int m_threshold;
/**
* Holds an array of pointers into m_memory
*/
private int m_table[];
private Formatter m_formatter;
private DebugLevel m_debugLevel = DebugLevel.NONE;
public HashSet(int maxBlocks, int blockSize, double growthFactor, double loadFactor) {
this(new TreeAllocator(maxBlocks, blockSize, growthFactor), maxBlocks, loadFactor);
}
public HashSet(IMemAllocator memory, int initialCapacity, double loadFactor) {
m_size = 0;
m_loadFactor = loadFactor;
m_growthFactor = DEFAULT_GROWTH_FACTOR;
m_table = new int[initialCapacity];
m_threshold = (int) Math.min(getCapacity() * getLoadFactor(), Integer.MAX_VALUE);
for (int i = 0; i < m_table.length; i++) {
m_table[i] = -1;
}
m_formatter = new DefaultFormatter();
m_memory = memory;
}
@Override
public DebugLevel getDebug() {
return m_debugLevel;
}
@Override
public void setFormatter(Formatter formatter) {
m_formatter = formatter;
}
@Override
public Formatter getFormatter() {
return m_formatter;
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public int insert(IBuffer element) {
if (size() >= m_threshold && m_growthFactor > 0) {
increaseCapacity();
}
int listNum = hashCode(element, m_table.length);
int pointer = m_table[listNum];
// find if this key is already in the chain
while (pointer != -1) {
int keySize2 = m_memory.getInt(pointer, KEY_SIZE_OFFSET);
// list already contain this element, nothing to do
if (element.equals(m_memory, pointer, KEY_DATA_OFFSET, keySize2)) {
break;
}
pointer = m_memory.getInt(pointer, NEXT_OFFSET);
}
if (pointer == -1) {
int size = element.size();
int ptr = m_memory.malloc(size + KEY_DATA_OFFSET);
m_memory.setInt(ptr, KEY_SIZE_OFFSET, size);
m_memory.setInts(ptr, KEY_DATA_OFFSET, element.array(), 0, size);
m_table[listNum] = pointer;
m_size++;
}
return pointer;
}
private void increaseCapacity() {
// long t = Sysftem.currentTimeMillis();
IBuffer buffer = new Buffer(10, 2);
int capacity = getCapacity();
long newCapacity = Math.max(capacity + 1, (long) (capacity * m_growthFactor));
if (newCapacity > Integer.MAX_VALUE) {
throw new IllegalStateException("Attempted to resize table to " + newCapacity
+ " which is greated than Integer.MAX_VALUE");
}
int intCap = (int) newCapacity;
int newTable[] = new int[intCap];
for (int i = 0; i < newTable.length; i++) {
newTable[i] = -1;
}
for (int tableNum = 0; tableNum < m_table.length; tableNum++) {
int n = m_table[tableNum];
while (n != -1) {
int keySize = m_memory.getInt(n, KEY_SIZE_OFFSET);
buffer.ensureCapacity(keySize);
m_memory.getInts(n, KEY_DATA_OFFSET, buffer.array(), 0, keySize);
buffer.setUsed(keySize);
int newTableNum = hashCode(buffer, intCap);
buffer.reset();
int next = m_memory.getInt(n, NEXT_OFFSET);
m_memory.setInt(n, NEXT_OFFSET, newTable[newTableNum]);
newTable[newTableNum] = n;
n = next;
}
}
m_table = newTable;
m_threshold = (int) Math.min(getCapacity() * getLoadFactor(), Integer.MAX_VALUE);
// System.out.println(String.format("Increased map capacity from %d to %d took %d ms",
// capacity,
// intCap, (System.currentTimeMillis() - t)));
}
@Override
public boolean contains(IBuffer element) {
return findElement(element) != -1;
}
@Override
public int findElement(IBuffer element) {
int listNum = hashCode(element, m_table.length);
int n = m_table[listNum];
while (n != -1) {
int keySize = m_memory.getInt(n, KEY_SIZE_OFFSET);
if (element.equals(m_memory, n, KEY_DATA_OFFSET, keySize)) {
break;
}
n = m_memory.getInt(n, NEXT_OFFSET);
}
return n;
}
@Override
public boolean remove(IBuffer element) {
int listNum = hashCode(element, m_table.length);
int n = m_table[listNum];
int prev = -1;
boolean first = true;
while (n != -1) {
int keySize = m_memory.getInt(n, KEY_SIZE_OFFSET);
if (element.equals(m_memory, n, KEY_DATA_OFFSET, keySize)) {
int next = m_memory.getInt(n, NEXT_OFFSET);
if (first) {
m_table[listNum] = next;
} else {
m_memory.setInt(prev, NEXT_OFFSET, next);
}
m_size--;
m_memory.free(n);
return true;
}
prev = n;
first = false;
n = m_memory.getInt(n, NEXT_OFFSET);
}
return false;
}
@Override
public void clear() {
m_size = 0;
visitRecords(new DefaultSetVisitor() {
@Override
public void visit(ISet map, int keyPtr) {
m_memory.free(keyPtr);
}
});
for (int i = 0; i < m_table.length; i++) {
m_table[i] = -1;
}
}
@Override
public int getCapacity() {
return m_table.length;
}
@Override
public int size() {
return m_size;
}
@Override
public double getLoadFactor() {
return m_loadFactor;
}
@Override
public void setGrowthFactor(double d) {
m_growthFactor = d;
}
@Override
public long computeMemoryUsage() {
return 4 * m_table.length + m_memory.computeMemoryUsage();
}
@Override
public void setDebug(DebugLevel level) {
m_debugLevel = level;
}
@Override
public IMemAllocator getAllocator() {
return m_memory;
}
private int hashCode(IBuffer key, int listSize) {
int h = key.hashCode();
int r = h % listSize;
return r < 0 ? r + listSize : r;
}
@Override
public void visitRecords(ISetVisitor visitor) {
visitor.begin(this);
for (int i = 0; i < m_table.length; i++) {
int n = m_table[i];
while (n != -1) {
int next = m_memory.getInt(n, NEXT_OFFSET);
visitor.visit(this, n);
n = next;
}
}
visitor.end(this);
}
}