package org.cdlib.xtf.util; /* * Copyright (c) 2006, Regents of the University of California * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - Neither the name of the University of California nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * Acknowledgements: * * A significant amount of new and/or modified code in this module * was made possible by a grant from the Andrew W. Mellon Foundation, * as part of the Melvyl Recommender Project. */ import java.util.Arrays; import org.apache.lucene.util.IntList; /** * This class efficiently implements a "one to many" relationship between * integer keys and multiple integer values. The maximum key ID * is fixed at construction time, but the number of values can grow * insanely large, without large penalties for resizing arrays, etc. * * @author Martin Haye */ @SuppressWarnings("cast") public class IntMultiMap { private int[] keyLinks; private static final int BLOCK_SIZE = 32760; private Block[] blocks = { new Block() }; private short curBlockNum = 0; private Block curBlock = blocks[0]; private short curBlockTop = 0; /** * Calculate the size in bytes of the major structures of the map. * * @return Approximate size in bytes */ public long byteSize() { return (blocks.length * 8) + ((curBlockNum + 1) * (long)BLOCK_SIZE * 8); } /** * Initialize the mapping table. * * @param maxKey One larger than the largest key value that will ever be * passed to {@link #add(int, int)}. Note that the mapping * cannot be expanded. */ public IntMultiMap(int maxKey) { keyLinks = new int[maxKey]; Arrays.fill(keyLinks, -1); } // constructor /** * Add a new association between a key and a value. Note that each key is * allowed to have multiple values associated with it. */ public void add(int key, int value) { // Is there room in the current block? If not, make a new one. if (curBlockTop == BLOCK_SIZE) { ++curBlockNum; if ((curBlockNum & 0x8000) != 0) throw new RuntimeException("Too many blocks - increase block size"); // Resize the array of blocks if necessary if (curBlockNum == blocks.length) { Block[] oldBlocks = blocks; blocks = new Block[blocks.length * 2]; System.arraycopy(oldBlocks, 0, blocks, 0, oldBlocks.length); } // Allocate the new block. curBlock = blocks[curBlockNum] = new Block(); curBlockTop = 0; } // Link in the new entry. curBlock.links[curBlockTop] = keyLinks[key]; curBlock.values[curBlockTop] = value; keyLinks[key] = (((int)curBlockNum) << 16) | curBlockTop; ++curBlockTop; } // add() /** * Reverse the order of all links. This can be helpful, since for speed reasons * the normal order of iteration is reversed. */ public void reverseOrder() { IntList links = new IntList(); int pos; // Process each key. for (int key = 0; key < keyLinks.length; key++) { // Gather all the links for this key links.clear(); pos = keyLinks[key]; if (pos < 0) continue; while (pos >= 0) { links.add(pos); pos = blocks[(int)pos >> 16].links[(int)pos & 0x7FFF]; } // And reverse their order. pos = links.get(links.size() - 1); keyLinks[key] = pos; for (int i = links.size() - 2; i >= 0; i--) { int nextPos = links.get(i); blocks[(int)pos >> 16].links[(int)pos & 0x7FFF] = nextPos; pos = nextPos; } // for // Null out the last link. blocks[(int)pos >> 16].links[(int)pos & 0x7FFF] = -1; } // for } // reverseOrder() /** * For iteration: get the first position for the given key, or -1 if it has none. */ public final int firstPos(int key) { return keyLinks[key]; } /** * For iteration: get the next position after the given pos, or -1 if we're at * the end of the chain. */ public final int nextPos(int prevPos) { return blocks[(int)prevPos >> 16].links[(int)prevPos & 0x7FFF]; } /** * Retrieve the value for a given link. */ public final int getValue(int pos) { return blocks[(int)pos >> 16].values[(int)pos & 0x7FFF]; } /** * Keeps track of a block of values, with links to the following values. */ private class Block { int[] values = new int[BLOCK_SIZE]; int[] links = new int[BLOCK_SIZE]; Block() { Arrays.fill(values, Integer.MIN_VALUE); Arrays.fill(links, -1); } } // class Block /** * Basic regression test */ public static final Tester tester = new Tester("IntMultiMap") { protected void testImpl() { IntMultiMap map = new IntMultiMap(10); map.add(1, 10); map.add(2, 20); map.add(2, 21); map.add(3, 30); map.add(3, 31); map.add(3, 32); int pos; pos = map.firstPos(0); assert pos < 0; pos = map.firstPos(1); assert pos >= 0; assert map.getValue(pos) == 10; pos = map.nextPos(pos); assert pos < 0; pos = map.firstPos(2); assert pos >= 0; assert map.getValue(pos) == 21; pos = map.nextPos(pos); assert pos >= 0; assert map.getValue(pos) == 20; pos = map.nextPos(pos); assert pos < 0; pos = map.firstPos(3); assert pos >= 0; assert map.getValue(pos) == 32; pos = map.nextPos(pos); assert pos >= 0; assert map.getValue(pos) == 31; pos = map.nextPos(pos); assert pos >= 0; assert map.getValue(pos) == 30; pos = map.nextPos(pos); assert pos < 0; map.reverseOrder(); pos = map.firstPos(0); assert pos < 0; pos = map.firstPos(1); assert pos >= 0; assert map.getValue(pos) == 10; pos = map.nextPos(pos); assert pos < 0; pos = map.firstPos(2); assert pos >= 0; assert map.getValue(pos) == 20; pos = map.nextPos(pos); assert pos >= 0; assert map.getValue(pos) == 21; pos = map.nextPos(pos); assert pos < 0; pos = map.firstPos(3); assert pos >= 0; assert map.getValue(pos) == 30; pos = map.nextPos(pos); assert pos >= 0; assert map.getValue(pos) == 31; pos = map.nextPos(pos); assert pos >= 0; assert map.getValue(pos) == 32; pos = map.nextPos(pos); assert pos < 0; } // testImpl() }; } // class OneToManyInt