From 7ea4acb504e9e96adaa3398af29281807794fd0c Mon Sep 17 00:00:00 2001 From: Vlad Ilyushchenko Date: Thu, 28 Mar 2019 13:43:25 +0000 Subject: [PATCH] #64: IntLong- and LongLong hash maps. --- .../java/com/questdb/std/IntLongHashMap.java | 105 +++++++++++++++++ .../java/com/questdb/std/LongLongHashMap.java | 105 +++++++++++++++++ .../com/questdb/std/IntLongHashMapTest.java | 106 ++++++++++++++++++ .../com/questdb/std/LongLongHashMapTest.java | 106 ++++++++++++++++++ 4 files changed, 422 insertions(+) create mode 100644 core/src/main/java/com/questdb/std/IntLongHashMap.java create mode 100644 core/src/main/java/com/questdb/std/LongLongHashMap.java create mode 100644 core/src/test/java/com/questdb/std/IntLongHashMapTest.java create mode 100644 core/src/test/java/com/questdb/std/LongLongHashMapTest.java diff --git a/core/src/main/java/com/questdb/std/IntLongHashMap.java b/core/src/main/java/com/questdb/std/IntLongHashMap.java new file mode 100644 index 000000000..e49220ae9 --- /dev/null +++ b/core/src/main/java/com/questdb/std/IntLongHashMap.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (C) 2014-2019 Appsicle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +package com.questdb.std; + +import java.util.Arrays; + +public class IntLongHashMap extends AbstractIntHashSet { + private static final int noEntryValue = -1; + private long[] values; + + public IntLongHashMap() { + this(8); + } + + public IntLongHashMap(int initialCapacity) { + this(initialCapacity, 0.5f); + } + + private IntLongHashMap(int initialCapacity, double loadFactor) { + super(initialCapacity, loadFactor); + values = new long[keys.length]; + clear(); + } + + public long get(int key) { + return valueAt(keyIndex(key)); + } + + public void put(int key, long value) { + putAt(keyIndex(key), key, value); + } + + public void putAt(int index, int key, long value) { + if (index < 0) { + Unsafe.arrayPut(values, -index - 1, value); + } else { + Unsafe.arrayPut(keys, index, key); + Unsafe.arrayPut(values, index, value); + if (--free == 0) { + rehash(); + } + } + } + + public long valueAt(int index) { + return index < 0 ? Unsafe.arrayGet(values, -index - 1) : noEntryValue; + } + + @Override + protected void erase(int index) { + Unsafe.arrayPut(keys, index, this.noEntryKeyValue); + } + + @Override + protected void move(int from, int to) { + Unsafe.arrayPut(keys, to, Unsafe.arrayGet(keys, from)); + Unsafe.arrayPut(values, to, Unsafe.arrayGet(values, from)); + erase(from); + } + + private void rehash() { + int size = size(); + int newCapacity = capacity * 2; + mask = newCapacity - 1; + free = capacity = newCapacity; + int arrayCapacity = (int) (newCapacity / loadFactor); + + long[] oldValues = values; + int[] oldKeys = keys; + this.keys = new int[arrayCapacity]; + this.values = new long[arrayCapacity]; + Arrays.fill(keys, noEntryKeyValue); + + free -= size; + for (int i = oldKeys.length; i-- > 0; ) { + int key = Unsafe.arrayGet(oldKeys, i); + if (key != noEntryKeyValue) { + final int index = keyIndex(key); + Unsafe.arrayPut(keys, index, key); + Unsafe.arrayPut(values, index, Unsafe.arrayGet(oldValues, i)); + } + } + } +} diff --git a/core/src/main/java/com/questdb/std/LongLongHashMap.java b/core/src/main/java/com/questdb/std/LongLongHashMap.java new file mode 100644 index 000000000..19ac90f00 --- /dev/null +++ b/core/src/main/java/com/questdb/std/LongLongHashMap.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (C) 2014-2019 Appsicle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +package com.questdb.std; + +import java.util.Arrays; + +public class LongLongHashMap extends AbstractLongHashSet { + private static final int noEntryValue = -1; + private long[] values; + + public LongLongHashMap() { + this(8); + } + + public LongLongHashMap(int initialCapacity) { + this(initialCapacity, 0.5f); + } + + private LongLongHashMap(int initialCapacity, double loadFactor) { + super(initialCapacity, loadFactor); + values = new long[keys.length]; + clear(); + } + + public long get(long key) { + return valueAt(keyIndex(key)); + } + + public void put(long key, long value) { + putAt(keyIndex(key), key, value); + } + + public void putAt(int index, long key, long value) { + if (index < 0) { + Unsafe.arrayPut(values, -index - 1, value); + } else { + Unsafe.arrayPut(keys, index, key); + Unsafe.arrayPut(values, index, value); + if (--free == 0) { + rehash(); + } + } + } + + public long valueAt(int index) { + return index < 0 ? Unsafe.arrayGet(values, -index - 1) : noEntryValue; + } + + @Override + protected void erase(int index) { + Unsafe.arrayPut(keys, index, this.noEntryKeyValue); + } + + @Override + protected void move(int from, int to) { + Unsafe.arrayPut(keys, to, Unsafe.arrayGet(keys, from)); + Unsafe.arrayPut(values, to, Unsafe.arrayGet(values, from)); + erase(from); + } + + private void rehash() { + int size = size(); + int newCapacity = capacity * 2; + mask = newCapacity - 1; + free = capacity = newCapacity; + int arrayCapacity = (int) (newCapacity / loadFactor); + + long[] oldValues = values; + long[] oldKeys = keys; + this.keys = new long[arrayCapacity]; + this.values = new long[arrayCapacity]; + Arrays.fill(keys, noEntryKeyValue); + + free -= size; + for (int i = oldKeys.length; i-- > 0; ) { + long key = Unsafe.arrayGet(oldKeys, i); + if (key != noEntryKeyValue) { + final int index = keyIndex(key); + Unsafe.arrayPut(keys, index, key); + Unsafe.arrayPut(values, index, Unsafe.arrayGet(oldValues, i)); + } + } + } +} \ No newline at end of file diff --git a/core/src/test/java/com/questdb/std/IntLongHashMapTest.java b/core/src/test/java/com/questdb/std/IntLongHashMapTest.java new file mode 100644 index 000000000..e5158b683 --- /dev/null +++ b/core/src/test/java/com/questdb/std/IntLongHashMapTest.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (C) 2014-2019 Appsicle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +package com.questdb.std; + +import org.junit.Assert; +import org.junit.Test; + +public class IntLongHashMapTest { + @Test + public void testAll() { + + Rnd rnd = new Rnd(); + // populate map + IntLongHashMap map = new IntLongHashMap(); + final int N = 1000; + for (int i = 0; i < N; i++) { + long value = i + 1; + map.put(i, value); + } + Assert.assertEquals(N, map.size()); + + rnd.reset(); + + // assert that map contains the values we just added + for (int i = 0; i < N; i++) { + Assert.assertFalse(map.excludes(i)); + Assert.assertEquals(i + 1, map.get(i)); + } + + Rnd rnd2 = new Rnd(); + + rnd.reset(); + + // remove some keys and assert that the size() complies + int removed = 0; + for (int i = 0; i < N; i++) { + if (rnd2.nextPositiveInt() % 16 == 0) { + Assert.assertTrue(map.remove(i) > -1); + removed++; + Assert.assertEquals(N - removed, map.size()); + } + } + + // if we didn't remove anything test has no value + Assert.assertTrue(removed > 0); + + rnd2.reset(); + rnd.reset(); + + Rnd rnd3 = new Rnd(); + + // assert that keys we didn't remove are still there and + // keys we removed are not + for (int i = 0; i < N; i++) { + int value = rnd.nextInt(); + if (rnd2.nextPositiveInt() % 16 == 0) { + Assert.assertTrue(map.excludes(i)); + } else { + Assert.assertFalse(map.excludes(i)); + + int index = map.keyIndex(i); + Assert.assertEquals(i + 1, map.valueAt(index)); + + // update value + map.putAt(index, value, rnd3.nextLong()); + } + } + + // assert that update is visible correctly + rnd3.reset(); + rnd2.reset(); + rnd.reset(); + + // assert that keys we didn't remove are still there and + // keys we removed are not + for (int i = 0; i < N; i++) { + if (rnd2.nextPositiveInt() % 16 == 0) { + Assert.assertTrue(map.excludes(i)); + } else { + Assert.assertFalse(map.excludes(i)); + Assert.assertEquals(rnd3.nextLong(), map.get(i)); + } + } + } +} diff --git a/core/src/test/java/com/questdb/std/LongLongHashMapTest.java b/core/src/test/java/com/questdb/std/LongLongHashMapTest.java new file mode 100644 index 000000000..b024bfa0b --- /dev/null +++ b/core/src/test/java/com/questdb/std/LongLongHashMapTest.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (C) 2014-2019 Appsicle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +package com.questdb.std; + +import org.junit.Assert; +import org.junit.Test; + +public class LongLongHashMapTest { + @Test + public void testAll() { + + Rnd rnd = new Rnd(); + // populate map + LongLongHashMap map = new LongLongHashMap(); + final int N = 1000; + for (int i = 0; i < N; i++) { + long value = i + 1; + map.put(i, value); + } + Assert.assertEquals(N, map.size()); + + rnd.reset(); + + // assert that map contains the values we just added + for (int i = 0; i < N; i++) { + Assert.assertFalse(map.excludes(i)); + Assert.assertEquals(i + 1, map.get(i)); + } + + Rnd rnd2 = new Rnd(); + + rnd.reset(); + + // remove some keys and assert that the size() complies + int removed = 0; + for (int i = 0; i < N; i++) { + if (rnd2.nextPositiveInt() % 16 == 0) { + Assert.assertTrue(map.remove(i) > -1); + removed++; + Assert.assertEquals(N - removed, map.size()); + } + } + + // if we didn't remove anything test has no value + Assert.assertTrue(removed > 0); + + rnd2.reset(); + rnd.reset(); + + Rnd rnd3 = new Rnd(); + + // assert that keys we didn't remove are still there and + // keys we removed are not + for (int i = 0; i < N; i++) { + int value = rnd.nextInt(); + if (rnd2.nextPositiveInt() % 16 == 0) { + Assert.assertTrue(map.excludes(i)); + } else { + Assert.assertFalse(map.excludes(i)); + + int index = map.keyIndex(i); + Assert.assertEquals(i + 1, map.valueAt(index)); + + // update value + map.putAt(index, value, rnd3.nextLong()); + } + } + + // assert that update is visible correctly + rnd3.reset(); + rnd2.reset(); + rnd.reset(); + + // assert that keys we didn't remove are still there and + // keys we removed are not + for (int i = 0; i < N; i++) { + if (rnd2.nextPositiveInt() % 16 == 0) { + Assert.assertTrue(map.excludes(i)); + } else { + Assert.assertFalse(map.excludes(i)); + Assert.assertEquals(rnd3.nextLong(), map.get(i)); + } + } + } +} -- GitLab