/*
* BucketList.java
*
* Copyright (C) 2010 Leo Osvald <leo.osvald@gmail.com>
*
* This file is part of SGLJ.
*
* SGLJ is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SGLJ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
*/
package org.sglj.util;
import java.util.AbstractList;
import java.util.List;
import java.util.RandomAccess;
/**
*
* @author Leo Osvald
* @version 0.02
*
* @param <E> the type of elements held in this collection
*/
public class BucketList<E> extends AbstractList<E>
implements RandomAccess {
// private static final int INITIAL_BUCKET_SIZE_LOG = 1;
private int bucketSizeLog;
private int bucketCount;
private RandomAccessDeque<E>[] b;
public BucketList() {
this(16);
}
public BucketList(int capacity) {
allocateBuckets(capacity);
}
@SuppressWarnings("unchecked")
private void allocateBuckets(int numElements) {
if (numElements == 0)
throw new IllegalArgumentException();
// determine bucket size
// [1, 3) -> 1
// [3, 9) -> 2
// [9, 33) -> 3
// [2^(n-1)+1, 2^(n+1)+1] -> n
--numElements;
// set bucketSize to sqrt(floor(log2(numElements))
bucketSizeLog = ((31 - Integer.numberOfLeadingZeros(numElements)) >>> 1);
// determine the number of buckets
b = new RandomAccessDeque[2 << bucketSizeLog];
b[0] = new RandomAccessDeque<E>(1 << bucketSizeLog);
}
@SuppressWarnings("unchecked")
private void expand() {
RandomAccessDeque<E>[] newArr
= new RandomAccessDeque[bucketCount << 1];
bucketCount >>>= 1;
++bucketSizeLog;
b[0].addAll(b[1]);
b[1].clear();
newArr[0] = b[0];
for (int i = 1; i < bucketCount; ++i) {
RandomAccessDeque<E> from = b[2*i];
RandomAccessDeque<E> to = b[i];
// move left
to.addAll(from);
from.clear();
// move right
from = b[2*i + 1];
to.addAll(from);
from.clear();
newArr[i] = to;
}
b = newArr;
}
@SuppressWarnings("unchecked")
private void shrink() {
final int halfBucketSize = (1 << bucketSizeLog) >>> 1;
RandomAccessDeque<E>[] newArr
= new RandomAccessDeque[b.length >>> 1];
for (int i = 0; i < bucketCount; ++i) {
RandomAccessDeque<E> from = b[i];
// copy right half
newArr[2*i + 1] = new RandomAccessDeque<E>(
from.subList(halfBucketSize, from.size()));
// clear right half and assign as left side
while (from.size() > halfBucketSize)
from.removeLast();
newArr[2*i] = from;
}
b = newArr;
bucketCount <<= 1;
--bucketSizeLog;
}
public int size() {
return bucketCount == 0 ? 0 : ((bucketCount - 1) << bucketSizeLog)
+ b[bucketCount - 1].size();
}
public E get(int index) {
return b[index >>> bucketSizeLog]
.get(index & ((1 << bucketSizeLog) - 1));
}
@Override
public void add(int index, E element) {
int bucket = (index >>> bucketSizeLog);
int bucketSize = (1 << bucketSizeLog);
if (bucketCount == 0 || b[bucketCount - 1].size() == bucketSize) {
if ((bucketSize << 1) == bucketCount) {
expand();
bucket = (index >>> bucketSizeLog);
bucketSize = (1 << bucketSizeLog);
}
b[bucketCount++] = new RandomAccessDeque<E>(bucketSize);
}
for (int i = bucketCount - 1; i > bucket; --i)
b[i].addFirst(b[i - 1].pollLast());
b[bucket].add(index & (bucketSize - 1), element);
}
@Override
public E remove(int index) {
final int bucket = (index >>> bucketSizeLog);
final int bucketSize = (1 << bucketSizeLog);
E oldValue = b[bucket].remove(index & (bucketSize - 1));
for (int i = bucket + 1; i < bucketCount; ++i)
b[i - 1].addLast(b[i].pollFirst());
if (b[bucketCount - 1].isEmpty()) { // XXX
b[--bucketCount] = null;
if (bucketCount == (bucketSize >>> 2))
shrink();
}
return oldValue;
}
protected int getBufferCount() {
return bucketCount;
}
protected int getBufferSize() {
return 1 << bucketSizeLog;
}
@Override
public int hashCode() {
int ret = 1;
for (int i = size() - 1; i >= 0; --i) {
E e = get(i);
ret = ret * 31 + (e != null ? e.hashCode() : 0);
}
return ret;
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (!(obj instanceof List))
return false;
@SuppressWarnings("unchecked")
BucketList<E> other = (BucketList<E>) obj;
for (int i = size() - 1; i >= 0; --i) {
E e1 = get(i);
E e2 = other.get(i);
if ((e1 == null ^ e2 == null) || e1 != null && !e1.equals(e2))
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(
"[bucketSize = " + (1<<bucketSizeLog)
+ " , bucketCount = " + bucketCount + "]: ");
sb.append('[');
for (int i = 0; i < bucketCount; ++i) {
if (i > 0) sb.append(" | ");
int bucketSize = (i + 1 != bucketCount ? (1 << bucketSizeLog)
: b[i].size());
for (int j = 0; j < bucketSize; ++j) {
if (j > 0) sb.append(' ');
sb.append(b[i].get(j));
}
}
sb.append(']');
return sb.toString();
}
}