From cf3843c589ecc39e202989dacabe1a54072e5769 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 26 Jun 2023 14:51:45 +0200 Subject: [PATCH] Implement PairPriorityQueue wrapper --- .../wot/dijkstra/PairPriorityQueue.kt | 42 ++++++ .../wot/dijkstra/PriorityQueueTest.kt | 142 ++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/PairPriorityQueue.kt create mode 100644 wot-dijkstra/src/test/kotlin/org/pgpainless/wot/dijkstra/PriorityQueueTest.kt diff --git a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/PairPriorityQueue.kt b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/PairPriorityQueue.kt new file mode 100644 index 00000000..3c37ab1b --- /dev/null +++ b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/PairPriorityQueue.kt @@ -0,0 +1,42 @@ +package org.pgpainless.wot.dijkstra + +import java.util.PriorityQueue + +/** + * A de-duplicating max-priority queue for key-value pairs. + * + * When an element is popped, the queue entry with the *largest value* + * is popped (if there are multiple elements with the same max value, + * one of them is returned.) + * + * When inserting an element, if there is already an element with the same + * key, the element with the larger value is kept. + */ +internal class PairPriorityQueue>() { + + // NOTE: This implementation is not optimized for efficient inserts! + // - Each insert() involves a linear search by key + // - Each insert() sorts eagerly (via j.u.PriorityQueue.add()) + + private val internal: PriorityQueue> = PriorityQueue { + // Order priority queue entries by value (max first) + o1, o2 -> + o2.second.compareTo(o1.second) + } + + fun insert(key: K, value: V) { + when (val element = internal.find { it.first == key }) { + null -> internal.add(Pair(key, value)) // Add as a new element + else -> { + // If the new value is bigger: replace the element + if (value > element.second) { + internal.remove(element) + internal.add(Pair(key, value)) + } + } + } + } + + fun pop(): Pair? = internal.poll() + +} \ No newline at end of file diff --git a/wot-dijkstra/src/test/kotlin/org/pgpainless/wot/dijkstra/PriorityQueueTest.kt b/wot-dijkstra/src/test/kotlin/org/pgpainless/wot/dijkstra/PriorityQueueTest.kt new file mode 100644 index 00000000..5069aabd --- /dev/null +++ b/wot-dijkstra/src/test/kotlin/org/pgpainless/wot/dijkstra/PriorityQueueTest.kt @@ -0,0 +1,142 @@ +package org.pgpainless.wot.dijkstra + +import kotlin.test.Test +import kotlin.test.assertEquals + +// Priority queue tests. + +// Test data from `sequoia-wot:src/priority_queue.rs`. +class PriorityQueueTest { + + @Test + fun simple1() { + val pq: PairPriorityQueue = PairPriorityQueue(); + + pq.insert(0, 0); + pq.insert(1, 1); + pq.insert(2, 2); + pq.insert(3, 3); + pq.insert(4, 4); + pq.insert(5, 5); + + assertEquals(pq.pop(), Pair(5, 5)); + assertEquals(pq.pop(), Pair(4, 4)); + assertEquals(pq.pop(), Pair(3, 3)); + assertEquals(pq.pop(), Pair(2, 2)); + assertEquals(pq.pop(), Pair(1, 1)); + assertEquals(pq.pop(), Pair(0, 0)); + assertEquals(pq.pop(), null); + assertEquals(pq.pop(), null); + } + + @Test + fun simple2() { + val pq: PairPriorityQueue = PairPriorityQueue(); + + pq.insert(0, 0); + pq.insert(1, -1); + pq.insert(2, -2); + pq.insert(3, -3); + pq.insert(4, -4); + pq.insert(5, -5); + + assertEquals(pq.pop(), Pair(0, 0)); + assertEquals(pq.pop(), Pair(1, -1)); + assertEquals(pq.pop(), Pair(2, -2)); + assertEquals(pq.pop(), Pair(3, -3)); + assertEquals(pq.pop(), Pair(4, -4)); + assertEquals(pq.pop(), Pair(5, -5)); + assertEquals(pq.pop(), null); + assertEquals(pq.pop(), null); + } + + @Test + fun simple3() { + val pq: PairPriorityQueue = PairPriorityQueue(); + + pq.insert(0, 0); + pq.insert(1, 1); + pq.insert(5, 5); + pq.insert(2, 2); + pq.insert(4, 4); + pq.insert(3, 3); + + assertEquals(pq.pop(), Pair(5, 5)); + assertEquals(pq.pop(), Pair(4, 4)); + assertEquals(pq.pop(), Pair(3, 3)); + assertEquals(pq.pop(), Pair(2, 2)); + assertEquals(pq.pop(), Pair(1, 1)); + assertEquals(pq.pop(), Pair(0, 0)); + assertEquals(pq.pop(), null); + assertEquals(pq.pop(), null); + } + + @Test + fun simple4() { + val pq: PairPriorityQueue = PairPriorityQueue(); + assertEquals(pq.pop(), null); + + pq.insert(0, 0); + pq.insert(0, 0); + assertEquals(pq.pop(), Pair(0, 0)); + assertEquals(pq.pop(), null); + } + + @Test + fun simple5() { + val pq: PairPriorityQueue = PairPriorityQueue(); + assertEquals(pq.pop(), null); + + pq.insert(0, 0); + pq.insert(0, 0); + assertEquals(pq.pop(), Pair(0, 0)); + pq.insert(0, 0); + assertEquals(pq.pop(), Pair(0, 0)); + assertEquals(pq.pop(), null); + } + + + @Test + fun duplicates() { + val pq: PairPriorityQueue = PairPriorityQueue(); + + // Insert different keys with the same value. + for (i in 0 until 20) { + pq.insert(i, 0); + } + // Insert the same keys with their own value. This should + // overwrite the old keys. + for (i in 0 until 20) { + pq.insert(i, i); + } + + // Insert different keys with the same value. + for (i in 0 until 20) { + pq.insert(i, 0); + } + + for (i in 19 downTo 0) { + assertEquals(pq.pop(), Pair(i, i)); + } + assertEquals(pq.pop(), null); + assertEquals(pq.pop(), null); + } + + @Test + fun insert_pop() { + val pq: PairPriorityQueue = PairPriorityQueue(); + + // Insert different keys with the same value. + for (i in 0 until 10) { + pq.insert(i, 0); + } + // Insert the same keys with their own value. This should + // overwrite the old keys. + for (i in 9 downTo 0) { + pq.insert(i, i); + assertEquals(pq.pop(), Pair(i, i)); + } + assertEquals(pq.pop(), null); + assertEquals(pq.pop(), null); + } +}