From 626e96f0f3fc19d381744a412767341a95295b81 Mon Sep 17 00:00:00 2001 From: godotg Date: Sun, 26 Mar 2023 11:53:30 +0800 Subject: [PATCH] feat[map]: add primitive int-long tree map --- .../zfoo/net/packet/common/PairIntLong.java | 63 ++ .../com/zfoo/net/util/TreeMapIntLong.java | 788 ++++++++++++++++++ .../java/com/zfoo/net/util/TreeMapTest.java | 423 ++++++++++ net/src/test/resources/protocol.xml | 1 + 4 files changed, 1275 insertions(+) create mode 100644 net/src/main/java/com/zfoo/net/packet/common/PairIntLong.java create mode 100644 net/src/main/java/com/zfoo/net/util/TreeMapIntLong.java create mode 100644 net/src/test/java/com/zfoo/net/util/TreeMapTest.java diff --git a/net/src/main/java/com/zfoo/net/packet/common/PairIntLong.java b/net/src/main/java/com/zfoo/net/packet/common/PairIntLong.java new file mode 100644 index 00000000..7ab576af --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/common/PairIntLong.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ + +package com.zfoo.net.packet.common; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.zfoo.protocol.IPacket; + +import java.util.Comparator; + +/** + * @author godotg + * @version 3.0 + */ +public class PairIntLong implements IPacket { + + public static final short PROTOCOL_ID = 110; + + @JsonSerialize(using = ToStringSerializer.class) + private int key; + + @JsonSerialize(using = ToStringSerializer.class) + private long value; + + public static PairIntLong valueOf(int key, long value) { + var pair = new PairIntLong(); + pair.key = key; + pair.value = value; + return pair; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public int getKey() { + return key; + } + + public void setKey(int key) { + this.key = key; + } + + public long getValue() { + return value; + } + + public void setValue(long value) { + this.value = value; + } +} diff --git a/net/src/main/java/com/zfoo/net/util/TreeMapIntLong.java b/net/src/main/java/com/zfoo/net/util/TreeMapIntLong.java new file mode 100644 index 00000000..98e2c790 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/util/TreeMapIntLong.java @@ -0,0 +1,788 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package com.zfoo.net.util; + + +import com.zfoo.net.packet.common.PairIntLong; + +import java.util.Arrays; + +/** + * @author godotg + * @version 3.0 + */ +public class TreeMapIntLong { + private static final int DEFAULT_CAPACITY = 10; + private static final double ENLARGE_SCALE = 2.0; + + private static final boolean BLACK = false; + private static final boolean RED = true; + private static final int NULL = 0; + private static final int DEFAULT_NULL_KEY = (new int[1])[0]; + private static final long DEFAULT_NULL_VALUE = (new long[1])[0]; + + // Arrays are 1-indexed. Index 0 is a null node. + private int[] keys; + private long[] values; + private int[] left; + private int[] right; + private int[] p; + private boolean[] color; + + private int size; + private int root; + private boolean returnedNull; + + public TreeMapIntLong() { + this(DEFAULT_CAPACITY); + } + + public TreeMapIntLong(int capacity) { + if (capacity < 0) { + throw new IllegalArgumentException("Capacity must be non-negative"); + } + capacity++; + keys = new int[capacity]; + values = new long[capacity]; + left = new int[capacity]; + right = new int[capacity]; + p = new int[capacity]; + color = new boolean[capacity]; + color[NULL] = BLACK; + size = 0; + root = NULL; + returnedNull = false; + } + + public int size() { + return size; + } + + + public boolean isEmpty() { + return size == 0; + } + + + public boolean containsKey(int key) { + int x = root; + while (x != NULL) { + if (key < keys[x]) { + x = left[x]; + } else if (key > keys[x]) { + x = right[x]; + } else { + return true; + } + } + return false; + } + + + public long get(int key) { + int x = root; + while (x != NULL) { + if (key < keys[x]) { + x = left[x]; + } else if (key > keys[x]) { + x = right[x]; + } else { + returnedNull = false; + return values[x]; + } + } + returnedNull = true; + return DEFAULT_NULL_VALUE; + } + + + public long put(int key, long value) { + int y = NULL; + int x = root; + while (x != NULL) { + //noinspection SuspiciousNameCombination + y = x; + if (key < keys[x]) { + x = left[x]; + } else if (key > keys[x]) { + x = right[x]; + } else { + final long oldValue = values[x]; + values[x] = value; + returnedNull = false; + return oldValue; + } + } + if (size == color.length - 1) { + enlarge(); + } + int z = ++size; + keys[z] = key; + values[z] = value; + p[z] = y; + if (y == NULL) { + root = z; + } else { + if (key < keys[y]) { + left[y] = z; + } else { + right[y] = z; + } + } + left[z] = NULL; + right[z] = NULL; + color[z] = RED; + fixAfterAdd(z); + returnedNull = true; + return DEFAULT_NULL_VALUE; + } + + + public long remove(int key) { + int z = root; + while (z != NULL) { + if (key < keys[z]) { + z = left[z]; + } else if (key > keys[z]) { + z = right[z]; + } else { + final long removedValue = values[z]; + removeNode(z); + returnedNull = false; + return removedValue; + } + } + returnedNull = true; + return DEFAULT_NULL_VALUE; + } + + private void removeNode(int z) { + int y = (left[z] == NULL || right[z] == NULL) ? z : successorNode(z); + int x = (left[y] != NULL) ? left[y] : right[y]; + p[x] = p[y]; + if (p[y] == NULL) { + root = x; + } else { + if (y == left[p[y]]) { + left[p[y]] = x; + } else { + right[p[y]] = x; + } + } + if (y != z) { + keys[z] = keys[y]; + values[z] = values[y]; + } + //noinspection PointlessBooleanExpression + if (color[y] == BLACK) { + fixAfterRemove(x); + } + // Swap with last + if (y != size) { + // copy fields + keys[y] = keys[size]; + values[y] = values[size]; + left[y] = left[size]; + right[y] = right[size]; + p[y] = p[size]; + color[y] = color[size]; + // fix the children's parents + p[left[size]] = y; + p[right[size]] = y; + // fix one of the parent's children + if (left[p[size]] == size) { + left[p[size]] = y; + } else { + right[p[size]] = y; + } + // fix root + if (root == size) { + root = y; + } + } + size--; + } + + private int successorNode(int x) { + if (right[x] != NULL) { + x = right[x]; + while (left[x] != NULL) { + x = left[x]; + } + return x; + } else { + int y = p[x]; + while (y != NULL && x == right[y]) { + //noinspection SuspiciousNameCombination + x = y; + y = p[y]; + } + return y; + } + } + + private void fixAfterAdd(int z) { + while (color[p[z]] == RED) { + if (p[z] == left[p[p[z]]]) { + int y = right[p[p[z]]]; + if (color[y] == RED) { + color[p[z]] = BLACK; + color[y] = BLACK; + color[p[p[z]]] = RED; + z = p[p[z]]; + } else { + if (z == right[p[z]]) { + z = p[z]; + rotateLeft(z); + } + color[p[z]] = BLACK; + color[p[p[z]]] = RED; + rotateRight(p[p[z]]); + } + } else { + int y = left[p[p[z]]]; + if (color[y] == RED) { + color[p[z]] = BLACK; + color[y] = BLACK; + color[p[p[z]]] = RED; + z = p[p[z]]; + } else { + if (z == left[p[z]]) { + z = p[z]; + rotateRight(z); + } + color[p[z]] = BLACK; + color[p[p[z]]] = RED; + rotateLeft(p[p[z]]); + } + } + } + color[root] = BLACK; + } + + private void fixAfterRemove(int x) { + while (x != root && color[x] == BLACK) { + if (x == left[p[x]]) { + int w = right[p[x]]; + if (color[w] == RED) { + color[w] = BLACK; + color[p[x]] = RED; + rotateLeft(p[x]); + w = right[p[x]]; + } + if (color[left[w]] == BLACK && color[right[w]] == BLACK) { + color[w] = RED; + x = p[x]; + } else { + if (color[right[w]] == BLACK) { + color[left[w]] = BLACK; + color[w] = RED; + rotateRight(w); + w = right[p[x]]; + } + color[w] = color[p[x]]; + color[p[x]] = BLACK; + color[right[w]] = BLACK; + rotateLeft(p[x]); + x = root; + } + } else { + int w = left[p[x]]; + if (color[w] == RED) { + color[w] = BLACK; + color[p[x]] = RED; + rotateRight(p[x]); + w = left[p[x]]; + } + if (color[left[w]] == BLACK && color[right[w]] == BLACK) { + color[w] = RED; + x = p[x]; + } else { + if (color[left[w]] == BLACK) { + color[right[w]] = BLACK; + color[w] = RED; + rotateLeft(w); + w = left[p[x]]; + } + color[w] = color[p[x]]; + color[p[x]] = BLACK; + color[left[w]] = BLACK; + rotateRight(p[x]); + x = root; + } + } + } + color[x] = BLACK; + } + + private void rotateLeft(int x) { + int y = right[x]; + right[x] = left[y]; + if (left[y] != NULL) { + p[left[y]] = x; + } + p[y] = p[x]; + if (p[x] == NULL) { + root = y; + } else { + if (x == left[p[x]]) { + left[p[x]] = y; + } else { + right[p[x]] = y; + } + } + left[y] = x; + p[x] = y; + } + + private void rotateRight(int x) { + int y = left[x]; + left[x] = right[y]; + if (right[y] != NULL) { + p[right[y]] = x; + } + p[y] = p[x]; + if (p[x] == NULL) { + root = y; + } else { + if (x == right[p[x]]) { + right[p[x]] = y; + } else { + left[p[x]] = y; + } + } + right[y] = x; + p[x] = y; + } + + + public boolean returnedNull() { + return returnedNull; + } + + + public void clear() { + color[NULL] = BLACK; + size = 0; + root = NULL; + } + + + public int[] keys() { + int[] result = new int[size]; + for (int i = 0, x = firstNode(); x != NULL; x = successorNode(x), i++) { + result[i] = keys[x]; + } + return result; + } + + + public long[] values() { + long[] result = new long[size]; + for (int i = 0, x = firstNode(); x != NULL; x = successorNode(x), i++) { + result[i] = values[x]; + } + return result; + } + + private void enlarge() { + int newLength = Math.max(color.length + 1, (int) (color.length * ENLARGE_SCALE)); + keys = Arrays.copyOf(keys, newLength); + values = Arrays.copyOf(values, newLength); + left = Arrays.copyOf(left, newLength); + right = Arrays.copyOf(right, newLength); + p = Arrays.copyOf(p, newLength); + color = Arrays.copyOf(color, newLength); + } + + private int firstNode() { + int x = root; + while (left[x] != NULL) { + x = left[x]; + } + return x; + } + + private int lastNode() { + int x = root; + while (right[x] != NULL) { + x = right[x]; + } + return x; + } + + + public int getFirstKey() { + if (root == NULL) { + returnedNull = true; + return DEFAULT_NULL_KEY; + } + final int x = firstNode(); + returnedNull = false; + return keys[x]; + } + + + public int getLastKey() { + if (root == NULL) { + returnedNull = true; + return DEFAULT_NULL_KEY; + } + final int x = lastNode(); + returnedNull = false; + return keys[x]; + } + + + public PairIntLong getFirstEntry() { + if (root == NULL) { + returnedNull = true; + return null; + } + final int x = firstNode(); + returnedNull = false; + return PairIntLong.valueOf(keys[x], values[x]); + } + + + public PairIntLong getLastEntry() { + if (root == NULL) { + returnedNull = true; + return null; + } + final int x = lastNode(); + returnedNull = false; + return PairIntLong.valueOf(keys[x], values[x]); + } + + + public int floorKey(int key) { + int x = root; + while (x != NULL) { + if (key > keys[x]) { + if (right[x] != NULL) { + x = right[x]; + } else { + returnedNull = false; + return keys[x]; + } + } else if (key < keys[x]) { + if (left[x] != NULL) { + x = left[x]; + } else { + int y = p[x]; + while (y != NULL && x == left[y]) { + //noinspection SuspiciousNameCombination + x = y; + y = p[y]; + } + if (y == NULL) { + returnedNull = true; + return DEFAULT_NULL_KEY; + } else { + returnedNull = false; + return keys[y]; + } + } + } else { + returnedNull = false; + return keys[x]; + } + } + returnedNull = true; + return DEFAULT_NULL_KEY; + } + + + public int ceilingKey(int key) { + int x = root; + while (x != NULL) { + if (key < keys[x]) { + if (left[x] != NULL) { + x = left[x]; + } else { + returnedNull = false; + return keys[x]; + } + } else if (key > keys[x]) { + if (right[x] != NULL) { + x = right[x]; + } else { + int y = p[x]; + while (y != NULL && x == right[y]) { + //noinspection SuspiciousNameCombination + x = y; + y = p[y]; + } + if (y == NULL) { + returnedNull = true; + return DEFAULT_NULL_KEY; + } else { + returnedNull = false; + return keys[y]; + } + } + } else { + returnedNull = false; + return keys[x]; + } + } + returnedNull = true; + return DEFAULT_NULL_KEY; + } + + + public int lowerKey(int key) { + int x = root; + while (x != NULL) { + if (key > keys[x]) { + if (right[x] != NULL) { + x = right[x]; + } else { + returnedNull = false; + return keys[x]; + } + } else { + if (left[x] != NULL) { + x = left[x]; + } else { + int y = p[x]; + while (y != NULL && x == left[y]) { + //noinspection SuspiciousNameCombination + x = y; + y = p[y]; + } + if (y == NULL) { + returnedNull = true; + return DEFAULT_NULL_KEY; + } else { + returnedNull = false; + return keys[y]; + } + } + } + } + returnedNull = true; + return DEFAULT_NULL_KEY; + } + + + public int higherKey(int key) { + int x = root; + while (x != NULL) { + if (key < keys[x]) { + if (left[x] != NULL) { + x = left[x]; + } else { + returnedNull = false; + return keys[x]; + } + } else { + if (right[x] != NULL) { + x = right[x]; + } else { + int y = p[x]; + while (y != NULL && x == right[y]) { + //noinspection SuspiciousNameCombination + x = y; + y = p[y]; + } + if (y == NULL) { + returnedNull = true; + return DEFAULT_NULL_KEY; + } else { + returnedNull = false; + return keys[y]; + } + } + } + } + returnedNull = true; + return DEFAULT_NULL_KEY; + } + + + public PairIntLong floorEntry(int key) { + int x = root; + while (x != NULL) { + if (key > keys[x]) { + if (right[x] != NULL) { + x = right[x]; + } else { + returnedNull = false; + return PairIntLong.valueOf(keys[x], values[x]); + } + } else if (key < keys[x]) { + if (left[x] != NULL) { + x = left[x]; + } else { + int y = p[x]; + while (y != NULL && x == left[y]) { + //noinspection SuspiciousNameCombination + x = y; + y = p[y]; + } + if (y == NULL) { + returnedNull = true; + return null; + } else { + returnedNull = false; + return PairIntLong.valueOf(keys[y], values[y]); + } + } + } else { + returnedNull = false; + return PairIntLong.valueOf(keys[x], values[x]); + } + } + returnedNull = true; + return null; + } + + + public PairIntLong ceilingEntry(int key) { + int x = root; + while (x != NULL) { + if (key < keys[x]) { + if (left[x] != NULL) { + x = left[x]; + } else { + returnedNull = false; + return PairIntLong.valueOf(keys[x], values[x]); + } + } else if (key > keys[x]) { + if (right[x] != NULL) { + x = right[x]; + } else { + int y = p[x]; + while (y != NULL && x == right[y]) { + //noinspection SuspiciousNameCombination + x = y; + y = p[y]; + } + if (y == NULL) { + returnedNull = true; + return null; + } else { + returnedNull = false; + return PairIntLong.valueOf(keys[y], values[y]); + } + } + } else { + returnedNull = false; + return PairIntLong.valueOf(keys[x], values[x]); + } + } + returnedNull = true; + return null; + } + + + public PairIntLong lowerEntry(int key) { + int x = root; + while (x != NULL) { + if (key > keys[x]) { + if (right[x] != NULL) { + x = right[x]; + } else { + returnedNull = false; + return PairIntLong.valueOf(keys[x], values[x]); + } + } else { + if (left[x] != NULL) { + x = left[x]; + } else { + int y = p[x]; + while (y != NULL && x == left[y]) { + //noinspection SuspiciousNameCombination + x = y; + y = p[y]; + } + if (y == NULL) { + returnedNull = true; + return null; + } else { + returnedNull = false; + return PairIntLong.valueOf(keys[y], values[y]); + } + } + } + } + returnedNull = true; + return null; + } + + + public PairIntLong higherEntry(int key) { + int x = root; + while (x != NULL) { + if (key < keys[x]) { + if (left[x] != NULL) { + x = left[x]; + } else { + returnedNull = false; + return PairIntLong.valueOf(keys[x], values[x]); + } + } else { + if (right[x] != NULL) { + x = right[x]; + } else { + int y = p[x]; + while (y != NULL && x == right[y]) { + //noinspection SuspiciousNameCombination + x = y; + y = p[y]; + } + if (y == NULL) { + returnedNull = true; + return null; + } else { + returnedNull = false; + return PairIntLong.valueOf(keys[y], values[y]); + } + } + } + } + returnedNull = true; + return null; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TreeMapIntLong that = (TreeMapIntLong) o; + + if (size != that.size) { + return false; + } + for (int x = firstNode(), y = that.firstNode(); x != NULL; + //noinspection SuspiciousNameCombination + x = successorNode(x), y = that.successorNode(y)) { + if (keys[x] != that.keys[y] || values[x] != that.values[y]) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + int hash = 0; + for (int x = firstNode(); x != NULL; x = successorNode(x)) { + hash = hash ^ keys[x] ^ (int) values[x]; + } + return hash; + } + +} diff --git a/net/src/test/java/com/zfoo/net/util/TreeMapTest.java b/net/src/test/java/com/zfoo/net/util/TreeMapTest.java new file mode 100644 index 00000000..7a796bd0 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/util/TreeMapTest.java @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ + +package com.zfoo.net.util; + +import com.zfoo.net.packet.common.PairIntLong; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Map; +import java.util.Random; +import java.util.TreeMap; + +/** + * @author godotg + * @version 3.0 + */ +public class TreeMapTest { + + private void testPermutation(int[] array) { + int n = array.length; + TreeMapIntLong map = new TreeMapIntLong(0); + for (int it = 0; it < 3; it++) { + Assert.assertEquals(map.size(), 0); + Assert.assertTrue(map.isEmpty()); + for (int i = 0; i < n; i++) { + map.put(array[i], 2 * array[i]); + Assert.assertTrue(map.returnedNull()); + Assert.assertEquals(map.size(), i + 1); + Assert.assertFalse(map.isEmpty()); + } + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < n; i++) { + Assert.assertEquals(map.put(array[i], 3 * array[i]), 2 * array[i]); + Assert.assertFalse(map.returnedNull()); + Assert.assertEquals(map.size(), n); + Assert.assertFalse(map.isEmpty()); + } + for (int i = -1; i <= n; i++) { + if (0 <= i && i < n) { + Assert.assertTrue(map.containsKey(array[i])); + Assert.assertEquals(map.get(array[i]), 3 * array[i]); + Assert.assertFalse(map.returnedNull()); + } else { + Assert.assertFalse(map.containsKey(i)); + map.get(i); + Assert.assertTrue(map.returnedNull()); + } + } + Assert.assertEquals(map.getFirstKey(), 0); + Assert.assertFalse(map.returnedNull()); + Assert.assertEquals(map.getFirstEntry(), PairIntLong.valueOf(0, 0L)); + Assert.assertFalse(map.returnedNull()); + Assert.assertEquals(map.getLastKey(), n - 1); + Assert.assertFalse(map.returnedNull()); + Assert.assertEquals(map.getLastEntry(), PairIntLong.valueOf(n - 1, (long) (3 * (n - 1)))); + Assert.assertFalse(map.returnedNull()); + for (int i = -2; i <= n + 1; i++) { + int lowerKey = map.lowerKey(i); + if (i > 0) { + Assert.assertEquals(lowerKey, Math.min(i - 1, n - 1)); + Assert.assertFalse(map.returnedNull()); + } else { + Assert.assertTrue(map.returnedNull()); + } + int higherKey = map.higherKey(i); + if (i < n - 1) { + Assert.assertEquals(higherKey, Math.max(i + 1, 0)); + Assert.assertFalse(map.returnedNull()); + } else { + Assert.assertTrue(map.returnedNull()); + } + int floorKey = map.floorKey(i); + if (i >= 0) { + Assert.assertEquals(floorKey, Math.min(i, n - 1)); + Assert.assertFalse(map.returnedNull()); + } else { + Assert.assertTrue(map.returnedNull()); + } + int ceilingKey = map.ceilingKey(i); + if (i < n) { + Assert.assertEquals(ceilingKey, Math.max(i, 0)); + Assert.assertFalse(map.returnedNull()); + } else { + Assert.assertTrue(map.returnedNull()); + } + PairIntLong lowerEntry = map.lowerEntry(i); + if (i > 0) { + Assert.assertEquals(lowerEntry, PairIntLong.valueOf(lowerKey, (long) (3 * lowerKey))); + Assert.assertFalse(map.returnedNull()); + } else { + Assert.assertNull(lowerEntry); + Assert.assertTrue(map.returnedNull()); + } + PairIntLong higherEntry = map.higherEntry(i); + if (i < n - 1) { + Assert.assertEquals(higherEntry, PairIntLong.valueOf(higherKey, (long) (3 * higherKey))); + Assert.assertFalse(map.returnedNull()); + } else { + Assert.assertNull(higherEntry); + Assert.assertTrue(map.returnedNull()); + } + PairIntLong floorEntry = map.floorEntry(i); + if (i >= 0) { + Assert.assertEquals(floorEntry, PairIntLong.valueOf(floorKey, (long) (3 * floorKey))); + Assert.assertFalse(map.returnedNull()); + } else { + Assert.assertNull(floorEntry); + Assert.assertTrue(map.returnedNull()); + } + PairIntLong ceilingEntry = map.ceilingEntry(i); + if (i < n) { + Assert.assertEquals(ceilingEntry, PairIntLong.valueOf(ceilingKey, (long) (3 * ceilingKey))); + Assert.assertFalse(map.returnedNull()); + } else { + Assert.assertNull(ceilingEntry); + Assert.assertTrue(map.returnedNull()); + } + } + int removed = 0; + for (int i = -1; i <= n; i++) { + if (0 <= i && i < n) { + Assert.assertEquals(map.remove(array[i]), 3 * array[i]); + Assert.assertFalse(map.returnedNull()); + removed++; + } else { + map.remove(i); + Assert.assertTrue(map.returnedNull()); + } + Assert.assertEquals(map.size(), n - removed); + } + map.clear(); + } + } + + private void genPermutations(int[] permutation, int left, int length) { + if (left >= length - 1) { + testPermutation(permutation); + return; + } + for (int i = left; i < length; i++) { + swap(permutation, i, left); + genPermutations(permutation, left + 1, length); + swap(permutation, i, left); + } + } + + private void swap(int[] a, int i, int j) { + int tmp = a[i]; + a[i] = a[j]; + a[j] = tmp; + } + + private void shuffle(int[] a, Random rnd) { + int n = a.length; + for (int i = 0; i < n; i++) { + int j = i + rnd.nextInt(n - i); + swap(a, i, j); + } + } + + @Test + public void testAllSmallPermutations() { + for (int length = 1; length <= 8; length++) { + int[] a = new int[length]; + for (int i = 0; i < length; i++) { + a[i] = i; + } + genPermutations(a, 0, length); + } + } + + @Test + public void testManyPermutationsLength100() { + Random rnd = new Random(-228); + for (int it = 0; it < 7000; it++) { + int length = 9 + rnd.nextInt(92); + int[] a = new int[length]; + for (int i = 0; i < length; i++) { + a[i] = i; + } + shuffle(a, rnd); + testPermutation(a); + } + } + + @Test + public void testFewPermutationsLength10000() { + Random rnd = new Random(-322); + for (int it = 0; it < 35; it++) { + int length = 9 + rnd.nextInt(9992); + int[] a = new int[length]; + for (int i = 0; i < length; i++) { + a[i] = i; + } + shuffle(a, rnd); + testPermutation(a); + } + } + + @Test + public void testOperationsOnEmptyMap() { + TreeMapIntLong map = new TreeMapIntLong(0); + Assert.assertTrue(map.isEmpty()); + Assert.assertEquals(map.size(), 0); + for (int key = -200; key <= 200; key++) { + Assert.assertFalse(map.containsKey(key)); + map.get(key); + Assert.assertTrue(map.returnedNull()); + map.remove(key); + Assert.assertTrue(map.returnedNull()); + map.getFirstKey(); + Assert.assertTrue(map.returnedNull()); + map.getLastKey(); + Assert.assertTrue(map.returnedNull()); + map.higherKey(key); + Assert.assertTrue(map.returnedNull()); + map.lowerKey(key); + Assert.assertTrue(map.returnedNull()); + map.ceilingKey(key); + Assert.assertTrue(map.returnedNull()); + map.floorKey(key); + Assert.assertTrue(map.returnedNull()); + Assert.assertEquals(map.higherEntry(key), null); + Assert.assertTrue(map.returnedNull()); + Assert.assertEquals(map.lowerEntry(key), null); + Assert.assertTrue(map.returnedNull()); + Assert.assertEquals(map.ceilingEntry(key), null); + Assert.assertTrue(map.returnedNull()); + Assert.assertEquals(map.floorEntry(key), null); + Assert.assertTrue(map.returnedNull()); + Assert.assertEquals(map.getFirstEntry(), null); + Assert.assertTrue(map.returnedNull()); + Assert.assertEquals(map.getLastEntry(), null); + Assert.assertTrue(map.returnedNull()); + } + Assert.assertArrayEquals(map.keys(), new int[0]); + Assert.assertArrayEquals(map.values(), new long[0]); + } + + private void assertSortedMapMethods(TreeMapIntLong map, TreeMap javaMap, int key) { + { + Map.Entry javaEntry = javaMap.floorEntry(key); + int ezResultKey = map.floorKey(key); + if (javaEntry == null) { + Assert.assertTrue(map.returnedNull()); + } else { + Assert.assertEquals(ezResultKey, (int) javaEntry.getKey()); + } + PairIntLong ezResultEntry = map.floorEntry(key); + if (javaEntry == null) { + Assert.assertTrue(map.returnedNull()); + Assert.assertNull(ezResultEntry); + } else { + Assert.assertEquals(ezResultEntry, PairIntLong.valueOf(javaEntry.getKey(), (long) javaEntry.getValue())); + } + } + { + Map.Entry javaEntry = javaMap.ceilingEntry(key); + int ezResultKey = map.ceilingKey(key); + if (javaEntry == null) { + Assert.assertTrue(map.returnedNull()); + } else { + Assert.assertEquals(ezResultKey, (int) javaEntry.getKey()); + } + PairIntLong ezResultEntry = map.ceilingEntry(key); + if (javaEntry == null) { + Assert.assertTrue(map.returnedNull()); + Assert.assertNull(ezResultEntry); + } else { + Assert.assertEquals(ezResultEntry, PairIntLong.valueOf(javaEntry.getKey(), (long) javaEntry.getValue())); + } + } + { + Map.Entry javaEntry = javaMap.lowerEntry(key); + int ezResultKey = map.lowerKey(key); + if (javaEntry == null) { + Assert.assertTrue(map.returnedNull()); + } else { + Assert.assertEquals(ezResultKey, (int) javaEntry.getKey()); + } + PairIntLong ezResultEntry = map.lowerEntry(key); + if (javaEntry == null) { + Assert.assertTrue(map.returnedNull()); + Assert.assertNull(ezResultEntry); + } else { + Assert.assertEquals(ezResultEntry, PairIntLong.valueOf(javaEntry.getKey(), (long) javaEntry.getValue())); + } + } + { + Map.Entry javaEntry = javaMap.higherEntry(key); + int ezResultKey = map.higherKey(key); + if (javaEntry == null) { + Assert.assertTrue(map.returnedNull()); + } else { + Assert.assertEquals(ezResultKey, (int) javaEntry.getKey()); + } + PairIntLong ezResultEntry = map.higherEntry(key); + if (javaEntry == null) { + Assert.assertTrue(map.returnedNull()); + Assert.assertNull(ezResultEntry); + } else { + Assert.assertEquals(ezResultEntry, PairIntLong.valueOf(javaEntry.getKey(), (long) javaEntry.getValue())); + } + } + } + + @Test + public void testSignificantMethodsOnRandomData() { + TreeMapIntLong map = new TreeMapIntLong(); + TreeMap javaMap = new TreeMap(); + Random rnd = new Random(~42); + for (int i = 0; i < 1000000; i++) { + Assert.assertEquals(map.size(), javaMap.size()); + double prob = rnd.nextDouble(); + if (prob < 0.301) { + int key = rnd.nextInt(50); + long newValue = rnd.nextInt(); + Long oldValue = javaMap.put(key, newValue); + if (oldValue == null) { + map.put(key, newValue); + Assert.assertTrue(map.returnedNull()); + } else { + long actualOldValue = map.put(key, newValue); + Assert.assertFalse(map.returnedNull()); + Assert.assertEquals(actualOldValue, (long) oldValue); + } + } else if (prob < 0.6) { + int key = rnd.nextInt(50); + Long oldValue = javaMap.remove(key); + if (oldValue == null) { + map.remove(key); + Assert.assertTrue(map.returnedNull()); + } else { + long actualOldValue = map.remove(key); + Assert.assertFalse(map.returnedNull()); + Assert.assertEquals(actualOldValue, (long) oldValue); + } + } else { + int key = rnd.nextInt(20); + assertSortedMapMethods(map, javaMap, key); + } + } + } + + + @Test + public void testKeysValuesArrays() { + int[] keys = new int[100]; + long[] values = new long[100]; + for (int i = 0; i < 100; i++) { + keys[i] = i * 322 + 228; + values[i] = i * 228 + 322; + } + for (int len = 0; len <= 100; len++) { + TreeMapIntLong map = new TreeMapIntLong(); + for (int i = 0; i < len; i++) { + map.put(keys[i], values[i]); + Assert.assertTrue(map.returnedNull()); + } + Assert.assertArrayEquals(map.keys(), Arrays.copyOfRange(keys, 0, len)); + Assert.assertArrayEquals(map.values(), Arrays.copyOfRange(values, 0, len)); + } + } + + @Test + public void testEqualsHashcode() { + for (int mask1 = 0; mask1 < 32; mask1++) { + for (int mask2 = 0; mask2 < 32; mask2++) { + TreeMapIntLong map1 = new TreeMapIntLong(1); + TreeMapIntLong map2 = new TreeMapIntLong(1); + for (int i = 0; i < 5; i++) { + if ((mask1 & (1 << i)) != 0) { + map1.put(1000 * i, i); + } + if ((mask2 & (1 << i)) != 0) { + map2.put(1000 * i, i); + } + } + int hashCode1 = map1.hashCode(); + int hashCode2 = map2.hashCode(); + if (mask1 == mask2) { + Assert.assertTrue(map1.equals(map2)); + Assert.assertTrue(map2.equals(map1)); + Assert.assertTrue(hashCode1 == hashCode2); + } else { + Assert.assertFalse(map1.equals(map2)); + Assert.assertFalse(map2.equals(map1)); + } + } + } + } + + @Test + public void testCelling() { + var max = 10000; + TreeMapIntLong treeMap = new TreeMapIntLong(200); + for (int i = 0; i < max; i++) { + if (i % 2 == 0) { + treeMap.put(i, i); + } + } + for (int i = 0; i < max; i++) { + if (i % 2 != 0 && i != (max - 1)) { + Assert.assertEquals(treeMap.ceilingEntry(i).getValue(), i + 1); + } + if (i % 2 != 0) { + Assert.assertEquals(treeMap.floorEntry(i).getValue(), i - 1); + } + } + } + +} diff --git a/net/src/test/resources/protocol.xml b/net/src/test/resources/protocol.xml index d2fbfb69..3dd3fb83 100644 --- a/net/src/test/resources/protocol.xml +++ b/net/src/test/resources/protocol.xml @@ -26,6 +26,7 @@ +