MarginaliaSearch/code/libraries/coded-sequence/java/nu/marginalia/sequence/EliasGammaCodec.java

92 lines
2.6 KiB
Java
Raw Normal View History

package nu.marginalia.sequence;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntList;
import nu.marginalia.sequence.io.BitReader;
import nu.marginalia.sequence.io.BitWriter;
import java.nio.ByteBuffer;
/** Implement coding and decoding of sequences of integers using the Elias Gamma code
*
* https://en.wikipedia.org/wiki/Elias_gamma_coding
* */
public class EliasGammaCodec implements IntIterator {
private final BitReader reader;
private int last = 0;
private int next = 0;
private EliasGammaCodec(ByteBuffer buffer) {
reader = new BitReader(buffer);
}
/** Decode a sequence of integers from a ByteBuffer using the Elias Gamma code */
public static IntIterator decode(ByteBuffer buffer) {
return new EliasGammaCodec(buffer);
}
/** Encode a sequence of integers into a ByteBuffer using the Elias Gamma code.
* The sequence must be strictly increasing and may not contain values less than
* or equal to zero.
*/
public static ByteBuffer encode(ByteBuffer workArea, IntList sequence) {
var writer = new BitWriter(workArea);
int last = 0;
for (var iter = sequence.iterator(); iter.hasNext(); ) {
int i = iter.nextInt();
int delta = i - last;
last = i;
// can't encode zeroes
assert delta > 0 : "Sequence must be strictly increasing and may not contain zeroes or negative values";
int bits = Integer.numberOfTrailingZeros(Integer.highestOneBit(delta));
writer.put(0, bits + 1);
writer.put(delta, bits + 1);
}
return writer.finish();
}
/** Encode a sequence of integers into a ByteBuffer using the Elias Gamma code.
* The sequence must be strictly increasing and may not contain values less than
* or equal to zero.
*/
public static ByteBuffer encode(ByteBuffer workArea, int[] sequence) {
return encode(workArea, IntList.of(sequence));
}
@Override
public boolean hasNext() {
if (next > 0)
return true;
if (!reader.hasMore())
return false;
int bits = reader.takeWhileZero();
if (!reader.hasMore()) {
return false;
}
int delta = reader.get(bits);
last += delta;
next = last;
return true;
}
@Override
public int nextInt() {
if (hasNext()) {
int ret = next;
next = -1;
return ret;
}
throw new ArrayIndexOutOfBoundsException("No more data to read");
}
}