/* __ __ __ __ __ ___
* \ \ / / \ \ / / __/
* \ \/ / /\ \ \/ / /
* \____/__/ \__\____/__/.ɪᴏ
* ᶜᵒᵖʸʳᶦᵍʰᵗ ᵇʸ ᵛᵃᵛʳ ⁻ ˡᶦᶜᵉⁿˢᵉᵈ ᵘⁿᵈᵉʳ ᵗʰᵉ ᵃᵖᵃᶜʰᵉ ˡᶦᶜᵉⁿˢᵉ ᵛᵉʳˢᶦᵒⁿ ᵗʷᵒ ᵈᵒᵗ ᶻᵉʳᵒ
*/
package io.vavr.collection;
import io.vavr.Tuple;
import io.vavr.Tuple2;
import io.vavr.control.Option;
import org.junit.Test;
import java.util.Random;
import java.util.function.Function;
import static org.assertj.core.api.Assertions.assertThat;
public class HashArrayMappedTrieTest {
@Test
public void testLeafSingleton() {
HashArrayMappedTrie<WeakInteger, Integer> hamt = empty();
hamt = hamt.put(new WeakInteger(1), 1);
assertThat(hamt.get(new WeakInteger(1))).isEqualTo(Option.some(1));
assertThat(hamt.get(new WeakInteger(11))).isEqualTo(Option.none());
assertThat(hamt.getOrElse(new WeakInteger(1), 2)).isEqualTo(1);
assertThat(hamt.getOrElse(new WeakInteger(11), 2)).isEqualTo(2);
assertThat(hamt.get(new WeakInteger(2))).isEqualTo(Option.none());
assertThat(hamt.getOrElse(new WeakInteger(2), 2)).isEqualTo(2);
}
@Test
public void testLeafList() {
HashArrayMappedTrie<WeakInteger, Integer> hamt = empty();
hamt = hamt.put(new WeakInteger(1), 1).put(new WeakInteger(31), 31);
assertThat(hamt.get(new WeakInteger(1))).isEqualTo(Option.some(1));
assertThat(hamt.get(new WeakInteger(11))).isEqualTo(Option.none());
assertThat(hamt.get(new WeakInteger(31))).isEqualTo(Option.some(31));
assertThat(hamt.getOrElse(new WeakInteger(1), 2)).isEqualTo(1);
assertThat(hamt.getOrElse(new WeakInteger(11), 2)).isEqualTo(2);
assertThat(hamt.getOrElse(new WeakInteger(31), 2)).isEqualTo(31);
assertThat(hamt.get(new WeakInteger(2))).isEqualTo(Option.none());
assertThat(hamt.getOrElse(new WeakInteger(2), 2)).isEqualTo(2);
}
@Test
public void testGetExistingKey() {
HashArrayMappedTrie<Integer, Integer> hamt = empty();
hamt = hamt.put(1, 2).put(4, 5).put(null, 7);
assertThat(hamt.containsKey(1)).isTrue();
assertThat(hamt.get(1)).isEqualTo(Option.some(2));
assertThat(hamt.getOrElse(1, 42)).isEqualTo(2);
assertThat(hamt.containsKey(4)).isTrue();
assertThat(hamt.get(4)).isEqualTo(Option.some(5));
assertThat(hamt.containsKey(null)).isTrue();
assertThat(hamt.get(null)).isEqualTo(Option.some(7));
}
@Test
public void testGetUnknownKey() {
HashArrayMappedTrie<Integer, Integer> hamt = empty();
assertThat(hamt.get(2)).isEqualTo(Option.none());
assertThat(hamt.getOrElse(2, 42)).isEqualTo(42);
hamt = hamt.put(1, 2).put(4, 5);
assertThat(hamt.containsKey(2)).isFalse();
assertThat(hamt.get(2)).isEqualTo(Option.none());
assertThat(hamt.getOrElse(2, 42)).isEqualTo(42);
assertThat(hamt.containsKey(null)).isFalse();
assertThat(hamt.get(null)).isEqualTo(Option.none());
}
@Test
public void testRemoveFromEmpty() {
HashArrayMappedTrie<Integer, Integer> hamt = empty();
hamt = hamt.remove(1);
assertThat(hamt.size()).isEqualTo(0);
}
@Test
public void testRemoveUnknownKey() {
HashArrayMappedTrie<Integer, Integer> hamt = empty();
hamt = hamt.put(1, 2).remove(3);
assertThat(hamt.size()).isEqualTo(1);
hamt = hamt.remove(1);
assertThat(hamt.size()).isEqualTo(0);
}
@Test
public void testDeepestTree() {
final List<Integer> ints = List.tabulate(Integer.SIZE, i -> 1 << i).sorted();
HashArrayMappedTrie<Integer, Integer> hamt = empty();
hamt = ints.foldLeft(hamt, (h, i) -> h.put(i, i));
assertThat(List.ofAll(hamt.keysIterator()).sorted()).isEqualTo(ints);
}
@Test
public void testBigData() {
testBigData(5000, t -> t);
}
@Test
public void testBigDataWeakHashCode() {
testBigData(5000, t -> Tuple.of(new WeakInteger(t._1), t._2));
}
private <K extends Comparable<? super K>, V> void testBigData(int count, Function<Tuple2<Integer, Integer>, Tuple2<K, V>> mapper) {
final Comparator<K, V> cmp = new Comparator<>();
final java.util.Map<K, V> rnd = rnd(count, mapper);
for (java.util.Map.Entry<K, V> e : rnd.entrySet()) {
cmp.set(e.getKey(), e.getValue());
}
cmp.test();
for (K key : new java.util.TreeSet<>(rnd.keySet())) {
rnd.remove(key);
cmp.remove(key);
}
cmp.test();
}
@Test
public void shouldLookupNullInZeroKey() {
HashArrayMappedTrie<Integer, Integer> trie = empty();
// should contain all node types
for (int i = 0; i < 5000; i++) {
trie = trie.put(i, i);
}
trie = trie.put(null, 2);
assertThat(trie.get(0).get()).isEqualTo(0); // key.hashCode = 0
assertThat(trie.get(null).get()).isEqualTo(2); // key.hashCode = 0
}
// -- equals
@SuppressWarnings("EqualsWithItself")
@Test
public void shouldEqualSameHAMTInstance() {
final HashArrayMappedTrie<Integer, Integer> trie = empty();
assertThat(trie.equals(trie)).isTrue();
}
@Test
public void shouldEmptyNotEqualsDifferentType() {
assertThat(empty().equals("")).isFalse();
}
@Test
public void shouldNonEmptyNotEqualsDifferentType() {
assertThat(of(1).equals("")).isFalse();
}
@Test
public void shouldRecognizeEqualityOfNils() {
assertThat(empty().equals(empty())).isTrue();
}
@Test
public void shouldRecognizeEqualityOfNonNils() {
assertThat(of(1, 2, 3).equals(of(1, 2, 3))).isTrue();
}
@Test
public void shouldRecognizeNonEqualityOfHAMTOfSameSize() {
assertThat(of(1, 2, 3).equals(of(1, 2, 4))).isFalse();
}
@Test
public void shouldRecognizeNonEqualityOfHAMTOfDifferentSize() {
assertThat(of(1, 2, 3).equals(of(1, 2))).isFalse();
assertThat(of(1, 2).equals(of(1, 2, 3))).isFalse();
}
@Test
public void shouldEqualsIgnoreOrder() {
HashArrayMappedTrie<String, Integer> map = HashArrayMappedTrie.<String, Integer> empty().put("Aa", 1).put("BB", 2);
HashArrayMappedTrie<String, Integer> map2 = HashArrayMappedTrie.<String, Integer> empty().put("BB", 2).put("Aa", 1);
assertThat(map.hashCode()).isEqualTo(map2.hashCode());
assertThat(map).isEqualTo(map2);
}
@Test
public void shouldCalculateHashCodeOfLongLeafList() {
HashArrayMappedTrie<WeakInteger, Integer> h1 = HashArrayMappedTrie.empty();
for (int i = 0; i < 100000; i++) {
h1 = h1.put(new WeakInteger(i), i);
}
assertThat(h1.hashCode()).isNotZero();
}
// -- hashCode
@Test
public void shouldCheckHashCodeInLeafList() {
HashArrayMappedTrie<Integer, Integer> trie = empty();
trie = trie.put(0, 1).put(null, 2); // LeafList.hash == 0
final Option<Integer> none = trie.get(1 << 6); // (key.hash & BUCKET_BITS) == 0
assertThat(none).isEqualTo(Option.none());
}
@Test
public void shouldCalculateHashCodeOfNil() {
assertThat(empty().hashCode()).isEqualTo(1);
}
@Test
public void shouldCalculateHashCodeOfCollision() {
assertThat(empty().put(null, 1).put(0, 2).hashCode()).isEqualTo(empty().put(null, 1).put(0, 2).hashCode());
}
@Test
public void shouldCalculateDifferentHashCodesForDifferentHAMT() {
assertThat(of(1, 2).hashCode()).isNotEqualTo(of(2, 3).hashCode());
}
@Test
public void shouldCalculateBigHashCode() {
HashArrayMappedTrie<Integer, Integer> h1 = empty();
HashArrayMappedTrie<Integer, Integer> h2 = empty();
final int count = 1234;
for (int i = 0; i <= count; i++) {
h1 = h1.put(i, i);
h2 = h2.put(count - i, count - i);
}
assertThat(h1.hashCode() == h2.hashCode()).isTrue();
}
// - toString
@Test
public void shouldMakeString() {
assertThat(empty().toString()).isEqualTo("HashArrayMappedTrie()");
assertThat(empty().put(1, 2).toString()).isEqualTo("HashArrayMappedTrie(1 -> 2)");
}
// -- helpers
private HashArrayMappedTrie<Integer, Integer> of(int... ints) {
HashArrayMappedTrie<Integer, Integer> h = empty();
for (int i : ints) {
h = h.put(h.size(), i);
}
return h;
}
private <K, V> HashArrayMappedTrie<K, V> empty() {
return HashArrayMappedTrie.empty();
}
private class WeakInteger implements Comparable<WeakInteger> {
final int value;
@Override
public boolean equals(Object o) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }
final WeakInteger that = (WeakInteger) o;
return value == that.value;
}
WeakInteger(int value) {
this.value = value;
}
@Override
public int hashCode() {
return Math.abs(value) % 10;
}
@Override
public int compareTo(WeakInteger other) {
return Integer.compare(value, other.value);
}
}
private class Comparator<K, V> {
private final java.util.Map<K, V> classic = new java.util.HashMap<>();
private Map<K, V> hamt = HashMap.empty();
void test() {
assertThat(hamt.size()).isEqualTo(classic.size());
hamt.iterator().forEachRemaining(e -> assertThat(classic.get(e._1)).isEqualTo(e._2));
classic.forEach((k, v) -> {
assertThat(hamt.get(k).get()).isEqualTo(v);
assertThat(hamt.getOrElse(k, null)).isEqualTo(v);
});
}
void set(K key, V value) {
classic.put(key, value);
hamt = hamt.put(key, value);
}
void remove(K key) {
classic.remove(key);
hamt = hamt.remove(key);
}
}
private <K, V> java.util.Map<K, V> rnd(int count, Function<Tuple2<Integer, Integer>, Tuple2<K, V>> mapper) {
final Random r = new Random();
final java.util.HashMap<K, V> mp = new java.util.HashMap<>();
for (int i = 0; i < count; i++) {
final Tuple2<K, V> entry = mapper.apply(Tuple.of(r.nextInt(), r.nextInt()));
mp.put(entry._1, entry._2);
}
return mp;
}
}