Skip to content

Commit 4a47305

Browse files
committed
Improve MutableRingList's performance
Use a custom implementation instead of a LinkedList for the MutableRingList. In MarbleMania (day 9 of 2018) the list is rotated at most 7 steps at once and items are added and removed within that range. So, while indices (pointing to a standard list to which was delegated) might get very big, operations on an actually rotated linked list are quite local. This reduces the day 9's runtime from about half an hour to a few milliseconds.
1 parent 53bc4e5 commit 4a47305

File tree

2 files changed

+113
-31
lines changed

2 files changed

+113
-31
lines changed
Lines changed: 111 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,90 @@
11
package de.ronny_h.aoc.extensions.collections
22

3-
import java.util.*
3+
import kotlin.math.abs
44

55
class MutableRingList<T>() {
6-
private val list: MutableList<T> = LinkedList<T>()
7-
private var offset = 0
6+
private data class Node<T>(var value: T) {
7+
lateinit var prev: Node<T>
8+
lateinit var next: Node<T>
9+
}
10+
11+
private lateinit var head: Node<T>
12+
private var size = 0
813

914
constructor(initialList: List<T>) : this() {
10-
list.addAll(initialList)
15+
initialList.forEach { add(it) }
1116
}
1217

1318
constructor(size: Int, init: (index: Int) -> T) : this() {
14-
repeat(size) { index -> list.add(init(index)) }
19+
repeat(size) { index -> add(init(index)) }
1520
}
1621

1722
companion object {
1823
fun <T> mutableRingListOf(vararg elements: T): MutableRingList<T> = MutableRingList(elements.toList())
1924
fun mutableRingListOf(elements: String): MutableRingList<Char> = MutableRingList(elements.toList())
2025
}
2126

22-
operator fun get(index: Int) = list[(index + offset) % list.size]
27+
operator fun get(index: Int): T = head.goSteps(index).value
2328

2429
operator fun set(index: Int, value: T) {
25-
list[(index + offset) % list.size] = value
30+
head.goSteps(index).value = value
2631
}
2732

2833
/**
2934
* Inserts the given [value] at the current position (i.e. the current start of the ring list).
3035
*/
3136
fun insert(value: T) {
32-
list.add(offset, value)
37+
head = addAsLast(value)
38+
}
39+
40+
/**
41+
* Adds the given [value] to the end of this ring list (i.e. inserts it before the current position).
42+
*/
43+
fun add(value: T) {
44+
addAsLast(value)
3345
}
3446

3547
/**
3648
* Removes the value at the current position (i.e. the current start of the ring list) and returns it.
3749
* All elements after the current position are shifted 1 position left.
3850
*
3951
* @return The element that has been removed.
52+
* @throws IndexOutOfBoundsException If this ring list is empty.
4053
*/
41-
fun removeFirst(): T = list.removeAt(offset)
54+
fun removeFirst(): T {
55+
if (size == 0) {
56+
throw IndexOutOfBoundsException("Unable to remove an element from an empty list.")
57+
}
58+
size--
59+
val node = head.next
60+
val removed = head.value
61+
node.prev = head.prev
62+
node.prev.next = node
63+
head = node
64+
return removed
65+
}
4266

4367
/**
4468
* Makes [n] elements move from the end to the front, but maintain their order otherwise.
4569
*/
4670
fun shiftRight(n: Int): MutableRingList<T> {
47-
offset = (offset + list.size - n).mod(list.size)
71+
head = head.goSteps(-n)
4872
return this
4973
}
5074

5175
/**
5276
* Makes [n] elements move from the front to the end, but maintain their order otherwise.
5377
*/
5478
fun shiftLeft(n: Int): MutableRingList<T> {
55-
offset = (offset + list.size + n).mod(list.size)
79+
head = head.goSteps(n)
5680
return this
5781
}
5882

5983
/**
6084
* Swaps the elements at [indexA] and [indexB].
6185
*/
6286
fun swap(indexA: Int, indexB: Int): MutableRingList<T> {
63-
val tmp = get(indexA)
64-
set(indexA, get(indexB))
65-
set(indexB, tmp)
87+
swapValues(head.goSteps(indexA), head.goSteps(indexB))
6688
return this
6789
}
6890

@@ -71,35 +93,93 @@ class MutableRingList<T>() {
7193
* If not unique, only their first occurrences are swapped.
7294
*/
7395
fun swap(elemA: T, elemB: T): MutableRingList<T> {
74-
val indexA = list.indexOf(elemA)
75-
val indexB = list.indexOf(elemB)
76-
require(indexA >= 0) { "$elemA is not in the list" }
77-
require(indexB >= 0) { "$elemB is not in the list" }
78-
list[indexA] = elemB
79-
list[indexB] = elemA
96+
swapValues(findNode(elemA), findNode(elemB))
8097
return this
8198
}
8299

83-
fun reverseSubList(s: Int, length: Int): MutableRingList<T> {
84-
val start = (offset + s) % list.size
85-
if (start + length < list.size) {
86-
list.subList(start, start + length).toList()
87-
} else {
88-
list.subList(start, list.size) + list.subList(0, length - list.size + start)
100+
/**
101+
* Reverses the elements of the sublist starting at [startIndex] (inclusive) with length [length].
102+
*/
103+
fun reverseSubList(startIndex: Int, length: Int): MutableRingList<T> {
104+
var first = head.goSteps(startIndex)
105+
var last = first.goSteps(length - 1)
106+
var swaps = length / 2
107+
while (swaps > 0) {
108+
swapValues(first, last)
109+
first = first.next
110+
last = last.prev
111+
swaps--
89112
}
90-
.reversed()
91-
.forEachIndexed { i, item ->
92-
list[(start + i) % list.size] = item
93-
}
94113
return this
95114
}
96115

97116
/**
98117
* @return A snapshot of the current state of this [MutableRingList]
99118
*/
100-
fun toList(): List<T> = list.subList(offset, list.size) + list.subList(0, offset)
119+
fun toList(): List<T> {
120+
val list = ArrayList<T>(size)
121+
var node = head
122+
repeat(size) {
123+
list.add(node.value)
124+
node = node.next
125+
}
126+
return list
127+
}
101128

102129
override fun toString(): String = toList().toString()
103130

104131
fun toJoinedString(): String = toList().joinToString("")
132+
133+
private fun Node<T>.goSteps(n: Int): Node<T> {
134+
var node = this
135+
repeat(abs(n) % size) {
136+
node = (if (n > 0) node.next else node.prev)
137+
}
138+
return node
139+
}
140+
141+
private fun addAsLast(value: T): Node<T> {
142+
val newNode = Node(value)
143+
size++
144+
if (this::head.isInitialized) {
145+
insertBefore(head, newNode)
146+
} else {
147+
initHead(newNode)
148+
}
149+
return newNode
150+
}
151+
152+
private fun insertBefore(
153+
current: Node<T>,
154+
newNode: Node<T>
155+
) {
156+
val prev = current.prev
157+
current.prev = newNode
158+
newNode.next = current
159+
newNode.prev = prev
160+
prev.next = newNode
161+
}
162+
163+
private fun initHead(newNode: Node<T>) {
164+
newNode.prev = newNode
165+
newNode.next = newNode
166+
head = newNode
167+
}
168+
169+
private fun swapValues(nodeA: Node<T>, nodeB: Node<T>) {
170+
val tmp = nodeA.value
171+
nodeA.value = nodeB.value
172+
nodeB.value = tmp
173+
}
174+
175+
private fun findNode(value: T): Node<T> {
176+
var node = head
177+
while (node.value != value) {
178+
node = node.next
179+
require(node !== head) {
180+
"$value is not in the list"
181+
}
182+
}
183+
return node
184+
}
105185
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,10 @@ class MutableRingListTest : StringSpec({
134134
}
135135

136136
"reversSubList on an unmodified ringList" {
137+
mutableRingListOf(1, 2, 3, 4, 5, 6).reverseSubList(1, 3).toList() shouldBe listOf(1, 4, 3, 2, 5, 6)
137138
mutableRingListOf(1, 2, 3, 4, 5, 6).reverseSubList(1, 4).toList() shouldBe listOf(1, 5, 4, 3, 2, 6)
138139
mutableRingListOf(1, 2, 3, 4, 5, 6).reverseSubList(4, 4).toList() shouldBe listOf(6, 5, 3, 4, 2, 1)
140+
mutableRingListOf(1, 2, 3, 4, 5, 6).reverseSubList(0, 6).toList() shouldBe listOf(6, 5, 4, 3, 2, 1)
139141
}
140142

141143
"reversSubList on a shifted ringList" {

0 commit comments

Comments
 (0)