diff --git a/src/main/kotlin/it/norangeb/algorithms/exam/ThreeWayHeap.kt b/src/main/kotlin/it/norangeb/algorithms/exam/ThreeWayHeap.kt new file mode 100644 index 0000000..231ef7e --- /dev/null +++ b/src/main/kotlin/it/norangeb/algorithms/exam/ThreeWayHeap.kt @@ -0,0 +1,128 @@ +/* + * MIT License + * + * Copyright (c) 2019 norangebit + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package it.norangeb.algorithms.exam + +import arrow.core.None +import arrow.core.Option +import arrow.core.toOption + +class ThreeWayHeap private constructor( + private val shouldExchange: (T, T) -> Boolean +) { + private val array: Array = arrayOfNulls(1000) as Array + private var size = 0 + + fun insert(elem: T) { + array[++size] = elem + + pushUp(size) + } + + fun pop(): Option { + if (size <= 0) + return None + + val elem = array[FIRST_ELEMENT] + exchange(FIRST_ELEMENT, size) + array[size--] = null + pullDown(FIRST_ELEMENT) + + return elem.toOption() + } + + fun peek(): Option { + if (size <= 0) + return None + + return array[FIRST_ELEMENT].toOption() + } + + private fun pushUp(k: Int) { + val parent = k.parent() + when { + k <= FIRST_ELEMENT -> return + shouldExchange( + array[parent] ?: return, + array[k] ?: return + ) -> { + exchange(k, parent) + pushUp(parent) + } + } + } + + private fun pullDown(k: Int) { + var i = k + var child = child(i) + + while (child >= 0) { + exchange(i, child) + i = child + child = child(i) + } + + pushUp(i) + } + + private fun child(k: Int): Int { + val leftChild = array[k.leftChild()] ?: return -1 + val child = array[k.child()] ?: return k.leftChild() + val rightChild = array[k.rightChild()] ?: return if ( + shouldExchange(leftChild, child) + ) k.child() else k.leftChild() + + return when { + shouldExchange(leftChild, child) -> if ( + shouldExchange(child, rightChild) + ) k.rightChild() else k.child() + else -> if (shouldExchange(leftChild, rightChild)) + k.rightChild() else k.leftChild() + } + } + + private fun exchange(i: Int, j: Int) { + array[i] = array[j] + .also { array[j] = array[i] } + } + + companion object { + + fun > createMinHeap() = ThreeWayHeap { t1, t2 -> + t1 > t2 + } + + private fun Int.child() = this * THREE_WAY + + private fun Int.leftChild() = this * THREE_WAY - 1 + + private fun Int.rightChild() = this * THREE_WAY + 1 + + private fun Int.parent(): Int = (this + 1) / 3 + + const val THREE_WAY = 3 + const val FIRST_ELEMENT = 1 + } +} diff --git a/src/test/kotlin/it/norangeb/algorithms/exam/ThreeWayHeapTest.kt b/src/test/kotlin/it/norangeb/algorithms/exam/ThreeWayHeapTest.kt new file mode 100644 index 0000000..0349f82 --- /dev/null +++ b/src/test/kotlin/it/norangeb/algorithms/exam/ThreeWayHeapTest.kt @@ -0,0 +1,161 @@ +/* + * MIT License + * + * Copyright (c) 2019 norangebit + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package it.norangeb.algorithms.exam + +import arrow.core.None +import arrow.core.Some +import org.amshove.kluent.`should equal` +import org.spekframework.spek2.Spek +import org.spekframework.spek2.style.gherkin.Feature + +object ThreeWayHeapTest : Spek({ + Feature("three way heap") { + Scenario("min heap") { + val heap by memoized { ThreeWayHeap.createMinHeap() } + + When("add 23, 12, 45 into heap") { + heap.insert(12) + heap.insert(45) + heap.insert(23) + } + + Then("pop should equal 12") { + heap.pop() `should equal` Some(12) + } + + Then("peek should equal 23") { + heap.peek() `should equal` Some(23) + } + + When("add 25, 33, 24, 11, 15, 18, 34, 12 into heap") { + heap.insert(25) + heap.insert(18) + heap.insert(11) + heap.insert(33) + heap.insert(24) + heap.insert(12) + heap.insert(15) + heap.insert(34) + } + + Then("pop should equal 11") { + heap.pop() `should equal` Some(11) + } + + Then("pop should equal 12") { + heap.pop() `should equal` Some(12) + } + + When("add 30, 10, 12, 5, 3, 8, 9, 2, 11") { + heap.insert(12) + heap.insert(8) + heap.insert(3) + heap.insert(30) + heap.insert(2) + heap.insert(10) + heap.insert(5) + heap.insert(11) + heap.insert(9) + } + + Then("pop should equal 2") { + heap.pop() `should equal` Some(2) + } + + Then("pop should equal 3") { + heap.pop() `should equal` Some(3) + } + + Then("pop should equal 5") { + heap.pop() `should equal` Some(5) + } + + Then("pop should equal 8") { + heap.pop() `should equal` Some(8) + } + + Then("pop should equal 9") { + heap.pop() `should equal` Some(9) + } + + Then("pop should equal 10") { + heap.pop() `should equal` Some(10) + } + + Then("pop should equal 11") { + heap.pop() `should equal` Some(11) + } + + Then("pop should equal 12") { + heap.pop() `should equal` Some(12) + } + + Then("pop should equal 15") { + heap.pop() `should equal` Some(15) + } + + Then("pop should equal 18") { + heap.pop() `should equal` Some(18) + } + + Then("pop should equal 23") { + heap.pop() `should equal` Some(23) + } + + Then("pop should equal 24") { + heap.pop() `should equal` Some(24) + } + + Then("pop should equal 25") { + heap.pop() `should equal` Some(25) + } + + Then("pop should equal 30") { + heap.pop() `should equal` Some(30) + } + + Then("pop should equal 33") { + heap.pop() `should equal` Some(33) + } + + Then("pop should equal 34") { + heap.pop() `should equal` Some(34) + } + + Then("pop should equal 45") { + heap.pop() `should equal` Some(45) + } + + Then("pop should equal None") { + heap.pop() `should equal` None + } + + Then("peek should equal None") { + heap.peek() `should equal` None + } + } + } +}) \ No newline at end of file