All Downloads are FREE. Search and download functionalities are using the official Maven repository.

apoc.algo.algorithms.Chunks Maven / Gradle / Ivy

package apoc.algo.algorithms;

import java.util.Arrays;
import java.util.function.IntConsumer;

/*
Ideas for improvements

* use a pool for chunks that are returned on clear
* only use one BASE_CHUNK per default value globally
* increase chunk-size when merging chunks
*
*/

/**
 * a "chunked" growable int-array that can have gaps and grows dynamically,
 * meant to be used from a single thread, so no guards or volatiles
 * @author mh
 * @since 17.10.16
 */
public class Chunks {
    private int[] baseChunk;
    public static final int CHUNK_BITS = 16;
    private final int chunkSize;
    private final int chunkBits;
    private final int mask;
    int[][] chunks;
    int numChunks = 0;
    int maxIndex = 0;
    private int defaultValue = 0;

    public Chunks() {
        this(1, CHUNK_BITS);
    }

    public Chunks(int capacity) {
        this((capacity >> CHUNK_BITS ) +1,CHUNK_BITS);
    }
    Chunks(int numChunks, int chunkBits) {
        this.chunkSize = 1 << chunkBits;
        this.chunkBits = chunkBits;
        this.mask = chunkSize - 1;
        this.chunks = new int[numChunks][chunkSize];
        this.numChunks = numChunks;
    }

    // call directly after construction, todo move to constructor or static factory
    public Chunks withDefault(int defaultValue) {
        this.defaultValue = defaultValue;
        if (defaultValue != 0) {
            for (int[] chunk : chunks) {
                Arrays.fill(chunk, defaultValue);
            }
        }
        return this;
    }

    /**
     * Sets a value an grows chunks dynamically if exceeding size or missing chunk encountered
     */
    void set(int index, int value) {
        int chunk = assertSpace(index);
        chunks[chunk][index & mask] = value;
    }

    public void increment(int index) {
        int chunk = assertSpace(index);
        chunks[chunk][index & mask]++;
    }

    private int assertSpace(int index) {
        int chunk = index >> chunkBits;
        if (index > maxIndex) {
            maxIndex = index;
            if (chunk >= numChunks) growTo(chunk);
        }
        if (chunks[chunk]==null) growTo(chunk);
        return chunk;
    }

    int get(int index) {
        if (index>maxIndex) return defaultValue;
        int chunk = index >> chunkBits;
        if (chunks[chunk]==null) return defaultValue;
        return chunks[chunk][index & mask];
    }

    public int getNumChunks() {
        return numChunks;
    }

    public int size() {
        return maxIndex+1;
    }

    public int getFilledChunks() {
        return size() / chunkSize;
    }

    public int getChunkSize() {
        return chunkSize;
    }

    int[][] getChunks() {
        return chunks;
    }

    private void growTo(int chunk) {
        int newNumChunks = Math.max(chunk + 1,numChunks);
        if (newNumChunks!=numChunks) {
            int[][] newChunks = new int[newNumChunks][];
            System.arraycopy(chunks, 0, newChunks, 0, numChunks);
            chunks = newChunks;
            numChunks = newNumChunks;
        }
        if (chunks[chunk]==null) {
            chunks[chunk] = newChunk();
        }
    }

    // todo use pool
    private int[] newChunk() {
        if (defaultValue==0) return new int[chunkSize];

        if (baseChunk==null) {
            baseChunk = new int[chunkSize];
            Arrays.fill(baseChunk,defaultValue);
        }
        return baseChunk.clone();
    }

    /**
     * special operation implemented inline to compute and store sum up until here
     */
    public void sumUp() {
        int offset=0;
        int tmp=0;
        for (int i = 0; i < numChunks; i++) {
            int[] chunk = chunks[i];
            if (chunk==null) throw new IllegalStateException("Chunks are not continous, null fragement at offset "+i);
            for (int j = 0; j < chunkSize; j++) {
                tmp = chunk[j];
                chunk[j] = offset;
                offset += tmp;
            }
        }
    }

    /**
     * adds values from another Chunks to the current one
     * missing chunks are cloned over
     * @param c other Chunks
     */
    public void add(Chunks c) {
        assert c.chunkBits == chunkBits;
        assert c.defaultValue == defaultValue;

        int[][] oChunks = c.chunks;
        if (c.numChunks > numChunks) growTo(c.numChunks-1);
        for (int i = 0; i < oChunks.length; i++) {
            int[] oChunk = oChunks[i];
            if (oChunk!=null) {
                if (chunks[i]==null) {
                    chunks[i]=oChunk.clone();
                } else {
                    int[] chunk = chunks[i];
                    for (int j = 0; j < oChunk.length; j++) {
                        chunk[j] += oChunk[j];
                    }
                }
            }
        }
        this.maxIndex = Math.max(this.maxIndex,c.maxIndex);
    }

    /**
     * Clones this chunk by copying over all internal arrays
     */
    public Chunks clone() {
        Chunks result = new Chunks(0, chunkBits);
        int[][] newChunks = new int[numChunks][];
        for (int i = 0; i < numChunks; i++) {
            if (this.chunks[i]!=null) {
                newChunks[i]=this.chunks[i].clone();
            }
        }
        result.defaultValue = defaultValue;
        result.chunks = newChunks;
        result.maxIndex = maxIndex;
        result.numChunks = numChunks;
        return result;
    }

    /**
     * turn this chunked array into a regular int-array, mostly for compatibility
     * also copying unused space at the end filled with default value
     */
    public int[] mergeAllChunks() {
        int[] merged = new int[chunkSize*numChunks];
        int filledChunks = Math.min(numChunks,getFilledChunks()+1);
        for (int i = 0; i < filledChunks ; i++) {
            if (chunks[i]!=null) {
                System.arraycopy(chunks[i],0,merged,i*chunkSize,chunkSize);
            }
        }
        if (defaultValue!=0) Arrays.fill(merged, size(),merged.length,defaultValue);
        return merged;
    }

    /**
     * turn this chunked array into a regular int-array, mostly for compatibility
     * cuts off unused space at the end
     */
    public int[] mergeChunks() {
        int filledChunks = getFilledChunks();
        int[] merged = new int[size()];
        for (int i = 0; i < filledChunks ; i++) {
            if (chunks[i]!=null) {
                System.arraycopy(chunks[i], 0, merged, i * chunkSize, chunkSize);
            }
        }
        int remainder = size() % chunkSize;
        if (remainder != 0 && chunks[filledChunks]!=null) {
            System.arraycopy(chunks[filledChunks], 0, merged, (filledChunks) * chunkSize, remainder);
        }
        return merged;
    }

    /**
     * free resources (arrays) associated with this chunk
     */
    // todo return chunks to pool
    public void clear() {
        this.numChunks=0;
        this.maxIndex=-1;
        this.chunks=new int[0][];
    }

    interface IndexValueConsumer {
        void accept(int index, int value);
    }

    /**
     * internal iteration over data, avoids checks in {@link #get(int)}
     * @return total size of this Chunks
     */
    public int consume(IndexValueConsumer consumer) {
        int filledChunks = getFilledChunks();
        int offset;
        for (int i = 0; i < filledChunks ; i++) {
            offset = i * chunkSize;
            int[] chunk = chunks[i];
            if (chunk!=null) {
                for (int j = 0; j < chunkSize; j++) {
                    consumer.accept(offset+j, chunk[i]);
                }
            }
        }
        int remainder = size() % chunkSize;
        if (remainder != 0) {
            offset = filledChunks * chunkSize;
            int[] chunk = chunks[filledChunks];
            for (int j = 0; j < remainder; j++) {
                consumer.accept(offset+j, chunk[j]);
            }
        }
        return size();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy