Skip to content

Commit 5814229

Browse files
committed
Allow MutableRingList to become empty
* Introducing an explicit EmptyNode allows to get rid of one `lateinit` * make size's getter public and add some assertions in the tests * add tests for some exceptional cases
1 parent 4a47305 commit 5814229

File tree

2 files changed

+81
-11
lines changed

2 files changed

+81
-11
lines changed

src/main/kotlin/de/ronny_h/aoc/extensions/collections/MutableRingList.kt

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ package de.ronny_h.aoc.extensions.collections
33
import kotlin.math.abs
44

55
class MutableRingList<T>() {
6-
private data class Node<T>(var value: T) {
7-
lateinit var prev: Node<T>
8-
lateinit var next: Node<T>
6+
private interface Node<T> {
7+
var value: T
8+
var prev: Node<T>
9+
var next: Node<T>
910
}
1011

11-
private lateinit var head: Node<T>
12-
private var size = 0
12+
private var head: Node<T> = EmptyNode()
13+
var size = 0
14+
private set
1315

1416
constructor(initialList: List<T>) : this() {
1517
initialList.forEach { add(it) }
@@ -56,8 +58,12 @@ class MutableRingList<T>() {
5658
throw IndexOutOfBoundsException("Unable to remove an element from an empty list.")
5759
}
5860
size--
59-
val node = head.next
6061
val removed = head.value
62+
if (size == 0) {
63+
head = EmptyNode()
64+
return removed
65+
}
66+
val node = head.next
6167
node.prev = head.prev
6268
node.prev.next = node
6369
head = node
@@ -139,12 +145,12 @@ class MutableRingList<T>() {
139145
}
140146

141147
private fun addAsLast(value: T): Node<T> {
142-
val newNode = Node(value)
148+
val newNode = NodeWithValue(value)
143149
size++
144-
if (this::head.isInitialized) {
145-
insertBefore(head, newNode)
146-
} else {
150+
if (head is EmptyNode) {
147151
initHead(newNode)
152+
} else {
153+
insertBefore(head, newNode)
148154
}
149155
return newNode
150156
}
@@ -182,4 +188,21 @@ class MutableRingList<T>() {
182188
}
183189
return node
184190
}
191+
192+
private data class NodeWithValue<T>(override var value: T) : Node<T> {
193+
override lateinit var prev: Node<T>
194+
override lateinit var next: Node<T>
195+
}
196+
197+
private class EmptyNode<T>() : Node<T> {
198+
override var value: T
199+
get() = throw IllegalStateException("An EmptyNode has no value.")
200+
set(_) = throw IllegalStateException("An EmptyNode has no value.")
201+
override var prev: Node<T>
202+
get() = this
203+
set(_) = throw IllegalStateException("Setting prev of an EmptyNode is not allowed.")
204+
override var next: Node<T>
205+
get() = this
206+
set(_) = throw IllegalStateException("Setting next of an EmptyNode is not allowed.")
207+
}
185208
}

src/test/kotlin/de/ronny_h/aoc/extensions/collections/MutableRingListTest.kt

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package de.ronny_h.aoc.extensions.collections
22

33
import de.ronny_h.aoc.extensions.collections.MutableRingList.Companion.mutableRingListOf
4+
import io.kotest.assertions.throwables.shouldThrow
45
import io.kotest.core.spec.style.StringSpec
6+
import io.kotest.data.forAll
7+
import io.kotest.data.row
58
import io.kotest.matchers.shouldBe
69

710
class MutableRingListTest : StringSpec({
@@ -15,7 +18,25 @@ class MutableRingListTest : StringSpec({
1518
}
1619

1720
"a MutableRingList can be created with an initializer function" {
18-
MutableRingList(6) { it }.toJoinedString() shouldBe "012345"
21+
forAll(
22+
row(0, ""),
23+
row(1, "0"),
24+
row(6, "012345"),
25+
) { size, string ->
26+
val list = MutableRingList(size) { it }
27+
list.size shouldBe size
28+
list.toJoinedString() shouldBe string
29+
}
30+
}
31+
32+
"the size of a MutableRingList" {
33+
forAll(
34+
row("", 0),
35+
row("a", 1),
36+
row("abcdef", 6),
37+
) { data, size ->
38+
mutableRingListOf(data).size shouldBe size
39+
}
1940
}
2041

2142
"get(index) returns the element at index" {
@@ -73,6 +94,25 @@ class MutableRingListTest : StringSpec({
7394
list.toJoinedString() shouldBe "eabc"
7495
}
7596

97+
"removeFirst that makes a list of size 1 empty" {
98+
val list = mutableRingListOf("a")
99+
100+
list.size shouldBe 1
101+
list.removeFirst() shouldBe 'a'
102+
list.size shouldBe 0
103+
list.toJoinedString() shouldBe ""
104+
105+
list.add('b')
106+
list.size shouldBe 1
107+
list.toJoinedString() shouldBe "b"
108+
}
109+
110+
"removeFirst fails on an empty list" {
111+
shouldThrow<IndexOutOfBoundsException> {
112+
mutableRingListOf("").removeFirst()
113+
}
114+
}
115+
76116
"shiftRight() moves elements from the end to the front" {
77117
mutableRingListOf("abcde").shiftRight(1).toJoinedString() shouldBe "eabcd"
78118

@@ -133,6 +173,13 @@ class MutableRingListTest : StringSpec({
133173
mutableRingListOf("abcde").swap('c', 'c').toJoinedString() shouldBe "abcde"
134174
}
135175

176+
"swap with a non-existent value terminates with an error" {
177+
val exception = shouldThrow<IllegalArgumentException> {
178+
mutableRingListOf("abc").swap('a', 'd')
179+
}
180+
exception.message shouldBe "d is not in the list"
181+
}
182+
136183
"reversSubList on an unmodified ringList" {
137184
mutableRingListOf(1, 2, 3, 4, 5, 6).reverseSubList(1, 3).toList() shouldBe listOf(1, 4, 3, 2, 5, 6)
138185
mutableRingListOf(1, 2, 3, 4, 5, 6).reverseSubList(1, 4).toList() shouldBe listOf(1, 5, 4, 3, 2, 6)

0 commit comments

Comments
 (0)