From 801e737683c197052e7784770198185cef7596b4 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Sun, 14 Jul 2019 03:55:27 +0900 Subject: [PATCH] newly written circulararray --- .../terrarum/tests/CircularArrayTest.kt | 40 +++- src/net/torvald/util/CircularArray.kt | 175 ++++++++++++++---- 2 files changed, 167 insertions(+), 48 deletions(-) diff --git a/src/net/torvald/terrarum/tests/CircularArrayTest.kt b/src/net/torvald/terrarum/tests/CircularArrayTest.kt index 29c04ffbc..4be4ea2c8 100644 --- a/src/net/torvald/terrarum/tests/CircularArrayTest.kt +++ b/src/net/torvald/terrarum/tests/CircularArrayTest.kt @@ -8,38 +8,58 @@ import net.torvald.util.CircularArray class CircularArrayTest { operator fun invoke() { - val testSet = CircularArray(5) - val testSet2 = CircularArray(5) + val testSet = CircularArray(5, true) + val testSet2 = CircularArray(5, true) + testSet2.overwritingPolicy = { println("Overwritten: $it") } + val testSet3 = CircularArray(5, true) for (i in 1..5) { - testSet.add(i) + testSet.appendHead(i) } println("Metadata:") println(testSet) println("forEach():") - testSet.forEach { print("$it ") } + testSet.forEach { print("$it ") } // 1 2 3 4 5 println("\nfold(0, sum):") - println(testSet.fold(0) { acc, v -> acc + (v ?: 0) }) + println(testSet.fold(0) { acc, v -> acc + (v ?: 0) }) // 15 println("Raw:") - testSet.buffer.forEach { print("$it ") } + testSet.buffer.forEach { print("$it ") } // 1 2 3 4 5 println() println() for (i in 1..6) { - testSet2.add(i) + testSet2.appendHead(i) // must print "Overwritten: 1" } println("Metadata:") println(testSet2) - println("forEach():") + println("forEach():") // 2 3 4 5 6 testSet2.forEach { print("$it ") } println("\nfold(0, sum):") - println(testSet2.fold(0) { acc, v -> acc + (v ?: 0) }) + println(testSet2.fold(0) { acc, v -> acc + (v ?: 0) }) // 20 println("Raw:") - testSet2.buffer.forEach { print("$it ") } + testSet2.buffer.forEach { print("$it ") } // 6 2 3 4 5 println() + println() + for (i in 1..5) { + testSet3.appendHead(i) + } + + val a1 = testSet3.removeTail() + val a2 = testSet3.removeTail() + println("Removed elems: $a1 $a2") + + println("Metadata:") + println(testSet3) + println("forEach():") // 3 4 5 + testSet3.forEach { print("$it ") } + println("\nfold(0, sum):") + println(testSet3.fold(0) { acc, v -> acc + (v ?: 0) }) // 12 + println("Raw:") + testSet3.buffer.forEach { print("$it ") } // 1 2 3 4 5 + println() } } diff --git a/src/net/torvald/util/CircularArray.kt b/src/net/torvald/util/CircularArray.kt index 89f4f5da9..85cdf2fa1 100644 --- a/src/net/torvald/util/CircularArray.kt +++ b/src/net/torvald/util/CircularArray.kt @@ -1,5 +1,7 @@ package net.torvald.util +import java.util.* + /** * buffer[head] contains the most recent item, whereas buffer[tail] contains the oldest one. @@ -11,13 +13,25 @@ package net.torvald.util * * Created by minjaesong on 2017-01-22. */ -class CircularArray(val size: Int) { +class CircularArray(val size: Int, val overwriteOnOverflow: Boolean): Iterable { + + /** + * What to do when old element is being overridden by the new element (only makes sense when ```overwriteOnOverflow = true```) + * + * This function will not be called when ```removeHead()``` or ```removeTail()``` is called. + */ + var overwritingPolicy: (T) -> Unit = { + // do nothing + } val buffer: Array = arrayOfNulls(size) as Array - var tail: Int = 0; private set - var head: Int = -1; private set - private var unreliableAddCount = 0 + /** Tail stands for the oldest element. The tail index points AT the tail element */ + var tail: Int = 0; private set + /** Head stands for the youngest element. The head index points AFTER the head element */ + var head: Int = 0; private set + + private var overflow = false val lastIndex = size - 1 @@ -25,58 +39,143 @@ class CircularArray(val size: Int) { * Number of elements that forEach() or fold() would iterate. */ val elemCount: Int - get() = minOf(unreliableAddCount, size) + get() = if (overflow) size else head - tail + val isEmpty: Boolean + get() = !overflow && head == tail - fun add(item: T) { - if (unreliableAddCount <= size) unreliableAddCount += 1 - - head = (head + 1) % size - if (unreliableAddCount > size) { - tail = (tail + 1) % size - } - - buffer[head] = item // overwrites oldest item when eligible - - - //println("$this $unreliableAddCount") - } - - fun getHeadElem(): T = buffer[head] - fun getTailElem(): T = buffer[tail] + private inline fun incHead() { head = (head + 1).wrap() } + private inline fun decHead() { head = (head - 1).wrap() } + private inline fun incTail() { tail = (tail + 1).wrap() } + private inline fun decTail() { tail = (tail - 1).wrap() } /** - * Iterates the array with oldest element first. + * When the overflowing is enabled, tail element (ultimate element) will be changed into the penultimate element. */ - fun forEach(action: (T) -> Unit) { - // has slightly better iteration performance than lambda - if (unreliableAddCount <= size) { - for (i in 0..head) - action(buffer[i]) + fun appendHead(item: T) { + if (overflow && !overwriteOnOverflow) { + throw StackOverflowError() } else { - for (i in 0..size - 1) - action(buffer[(i + tail) % size]) + if (overflow) { + overwritingPolicy.invoke(buffer[head]) + } + + buffer[head] = item + incHead() } + + if (overflow) { + incTail() + } + + // must be checked AFTER the actual head increment; otherwise this condition doesn't make sense + if (tail == head) { + overflow = true + } + } + + fun appendTail(item: T) { + // even if overflowing is enabled, appending at tail causes head element to be altered, therefore such action + // must be blocked by throwing overflow error + + if (overflow) { + throw StackOverflowError() + } + else { + decTail() + buffer[tail] = item + } + + // must be checked AFTER the actual head increment; otherwise this condition doesn't make sense + if (tail == head) { + overflow = true + } + } + + fun removeHead(): T { + if (isEmpty) throw EmptyStackException() + + decHead() + overflow = false + + return buffer[head] + } + + fun removeTail(): T { + if (isEmpty) throw EmptyStackException() + + val ret = buffer[tail] + incTail() + overflow = false + + return ret + } + + /** Returns the youngest (last of the array) element */ + fun getHeadElem(): T = if (isEmpty) throw EmptyStackException() else buffer[(head - 1).wrap()] + /** Returns the oldest (first of the array) element */ + fun getTailElem(): T = buffer[tail] + + private fun getAbsoluteRange() = 0 until when { + head == tail -> buffer.size + tail > head -> buffer.size - (((head - 1).wrap()) - tail) + else -> head - tail + } + + override fun iterator(): Iterator { + if (isEmpty) { + return object : Iterator { + override fun next(): T = throw EmptyStackException() + override fun hasNext() = false + } + } + + val rangeMax = getAbsoluteRange().last + var counter = 0 + return object : Iterator { + override fun next(): T { + val ret = buffer[(counter + tail).wrap()] + counter += 1 + return ret + } + + override fun hasNext() = (counter <= rangeMax) + } + } + + /** + * Iterates the array with oldest element (tail) first. + */ + fun forEach(action: (T) -> Unit) { + // for (element in buffer) action(element) + // return nothing + + iterator().forEach(action) } fun fold(initial: R, operation: (R, T) -> R): R { + // accumulator = initial + // for (element in buffer) accumulator = operation(accumulator, element) + // return accumulator + var accumulator = initial - //for (element in buffer) accumulator = operation(accumulator, element) - if (unreliableAddCount <= size) { - for (i in 0..head) - accumulator = operation(accumulator, buffer[i]) - } + + if (isEmpty) + return initial else { - for (i in 0..size - 1) - accumulator = operation(accumulator, buffer[(i + tail) % size]) + iterator().forEach { + accumulator = operation(accumulator, it) + } } return accumulator } - + private fun Int.wrap() = this fmod size override fun toString(): String { - return "CircularArray(size=" + buffer.size + ", head=" + head + ", tail=" + tail + ")" + return "CircularArray(size=${buffer.size}, head=$head, tail=$tail, overflow=$overflow)" } + + private infix fun Int.fmod(other: Int) = Math.floorMod(this, other) } \ No newline at end of file