Skip to content

Instantly share code, notes, and snippets.

@ryanberckmans
Created September 28, 2022 03:12
Show Gist options
  • Save ryanberckmans/d0bd3bf717ce93617a4432c4f44eeabd to your computer and use it in GitHub Desktop.
Save ryanberckmans/d0bd3bf717ce93617a4432c4f44eeabd to your computer and use it in GitHub Desktop.
A doubly-linked list in Scala
import scala.annotation.tailrec
import com.thoughtworks.enableIf
// Doubly-linked lists allow O(1) removals at the cost of having a
// reference to the doubly-linked node itself. Some game objects store
// their own next/prev links, which allows for a convenient reference to
// the doubly-linked node (i.e. it's just the value stored) however then
// each value can partake in at most one list at a time (per next/prev,
// can have multiple types of next/prev as is done with Affect).
// The book "Game Programming Patterns" discusses doubly linked lists.
// http://gameprogrammingpatterns.com/observer.html
// Naming conventions in this file:
// type parameter A - the type of node in the doubly linked list
// type parameter B - the type of value in the doubly linked list
// type parameter C - the type of association for the doubly linked list
// type parameter D - the type of value to which a B is converted using Convert
// type parameter E - the type of value to/from which a B is mapped or folded
// type parameter H - the type of GetHead container for the doubly linked list
// type parameter I - the type of SetHead container for the doubly linked list
// type parameter S - the type of a node or value that's also a SomeThis[S]
// identifier ev - instance of typeclass DoubleLinkedList
// identifier ev2 - instance of GetHead typeclass
// identifier ev3 - instance of SetHead typeclass
// identifier ev4 - instance of Convert typeclass
// identifier h - list container from perspective of GetHead typeclass
// identifier i - list container from perspective of SetHead typeclass
// identifier a - list node
// GetHead and SetHead are two separate typeclasses because clients
// may need asymmetric instances
// Intuition behind this asymmetry:
// 1. most operations just need a GetHead
// 2. remove just needs a SetHead
// 3. prepend and foreachRemove* need GetHead and SetHead, but these can or may need to be asymmetrical
// GetHead is a typeclass to get the head of a doubly linked list
// for a container of type A that uses nodes of type B via an
// association of type C. For example GetHead[Room, Mob, NextInRoom].
trait GetHead[H, A, C] {
def getHead(h: H): A // head node of list
}
final object GetHead {
@inline final def getHead[H, A, C](h: H)(implicit ev: GetHead[H, A, C]): A = ev.getHead(h)
}
// SetHead is a typeclass to set the head of a doubly linked list
// for a container of type A that uses nodes of type B via an
// association of type C. For example SetHead[Room, Mob, NextInRoom].
trait SetHead[H, A, C] {
def setHeadMaybeNull(doubleLinkedListContainer: H, newHeadMaybeNull: A): Unit // set head node of list; newHead may be null
def setHeadNotNull(doubleLinkedListContainer: H, newHeadNotNull: A): Unit // set head node of list; newHeadNotNull not null; some clients need special behavior if newHead is known to be not null, eg. MobCommanders
}
// GetSetHead is a convenience amalgamation of GetHead and SetHead
// for (the majority of) clients whose needs aren't complex enough
// to need separate typeclass instances for GetHead/SetHead.
trait GetSetHead[H, A, C] extends GetHead[H, A, C] with SetHead[H, A, C]
// InferType is my solution to the problem of the DoubleLinkedList interface being
// rather "over" parameterized which prevents the compiler from interferring the
// correct type parameters to summon the correct implicit typeclass instances.
// Coincidentally for DLL the type params can usually be inferred with a single type
// hint (the type of the DLL node) and some DLL interface functions below take a `t:
// InferType[A]` so the caller can provide this type with `InferType[ConcreteA]`.
final object InferType {
type InferType[A] = Option[A]
def apply[A]: Option[A] = None // zero allocations when caller passes InferType[ConcreteA]
}
// DoubleLinkedList is a typeclass for a doubly linked list with nodes
// of type A that contains values of type B via an association of type
// C. For example DoubleLinkedList[Mob, Mob, NextInRoom]. null is used
// as the unlinked value instead of Option to minimize allocations.
// `A >: Null <: AnyRef` constrains A to types that are nullable.
// https://stackoverflow.com/questions/2336204/how-scala-generic-constraints-to-null
trait DoubleLinkedList[A >: Null <: AnyRef, B, C] {
def value(a: A): B // value contained in A
def next(a: A): A // next node in linked list
def prev(a: A): A // previous node in linked list
def setNext(a: A, next: A): Unit
def setPrev(a: A, prev: A): Unit
// NB all concrete method implementations should properly be on the
// DoubleLinkedList object and take evidence of DoubleLinkedListHead and
// DoubleLinkedList, but I left the impls here so that I could see the
// entire DoubleLinkedList API on one screen in the DoubleLinkedList object
// (because the methods on DoubleLinkedList forward here and are thus terse).
// isEmpty returns true iff the list is empty. O(1)
@inline final def isEmpty[H](h: H)(implicit ev2: GetHead[H, A, C]): Boolean = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
ev2.getHead(h) == null
}
// nonEmpty returns true iff the list is non empty. O(1)
@inline final def nonEmpty[H](h: H)(implicit ev2: GetHead[H, A, C]): Boolean = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
ev2.getHead(h) != null
}
// atLeastTwo returns true iff the list has at least two elements. O(1)
@inline final def atLeastTwo[H](h: H)(implicit ev2: GetHead[H, A, C]): Boolean = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
ev2.getHead(h) != null && next(ev2.getHead(h)) != null
}
// Forall returns true iff passed predicate is true for all values in list a. O(N)
@inline final def forall[H](h: H, fn: B => Boolean)(implicit ev2: GetHead[H, A, C]): Boolean = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
forallInternal(ev2.getHead(h), fn)
}
@inline @tailrec private[this] final def forallInternal(a: A, fn: B => Boolean): Boolean = {
if (a == null) true
else if (fn(value(a))) forallInternal(next(a), fn)
else false
}
// Exists returns true iff passed predicate is
// true for at least one value in the list. O(N)
@inline final def exists[H](h: H, fn: B => Boolean)(implicit ev2: GetHead[H, A, C]): Boolean = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
existsInternal(ev2.getHead(h), fn)
}
@inline @tailrec private[this] final def existsInternal(a: A, fn: B => Boolean): Boolean = {
if (a == null) false
else if (fn(value(a))) true
else existsInternal(next(a), fn)
}
// Length returns the length of the list. WARNING O(N)
@inline final def length[H](h: H)(implicit ev2: GetHead[H, A, C]): Int = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
lengthInternal(ev2.getHead(h), 0)
}
@inline @tailrec private[this] final def lengthInternal(a: A, c: Int): Int = {
if (a == null) c
else lengthInternal(next(a), c+1)
}
// Counts the number of list values which satisfy a predicate. O(N)
@inline final def count[H](h: H, fn: B => Boolean)(implicit ev2: GetHead[H, A, C]): Int = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
countInternal(ev2.getHead(h), fn, 0)
}
@inline @tailrec private[this] final def countInternal(a: A, fn: B => Boolean, c: Int): Int = {
if (a == null) c
else if (fn(value(a))) countInternal(next(a), fn, c+1)
else countInternal(next(a), fn, c)
}
// Finds the first node in the list satisfying a predicate
// on its value, if any. O(N), allocates Some[A].
@inline final def find[H](h: H, fn: B => Boolean)(implicit ev2: GetHead[H, A, C]): Option[A] = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
findInternal(ev2.getHead(h), fn)
}
@inline @tailrec private[this] final def findInternal(a: A, fn: B => Boolean): Option[A] = {
if (a == null) None
else if (fn(value(a))) Some(a)
else findInternal(next(a), fn)
}
// findSomeThis is identical to find except does zero allocations
// because the found value is known to be a SomeThis.
@inline final def findSomeThis[H, S >: A <: A with SomeThis[S]](h: H, fn: B => Boolean)(implicit ev2: GetHead[H, S, C]): Option[S] = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
findSomeThisInternal(ev2.getHead(h), fn)(this)
}
@inline @tailrec private[this] final def findSomeThisInternal[S >: Null <: SomeThis[S]](s: S, fn: B => Boolean)(implicit ev: DoubleLinkedList[S, B, C]): Option[S] = {
if (s == null) None
else if (fn(ev.value(s))) s.someThis
else findSomeThisInternal(ev.next(s), fn)
}
// Finds the first node in the list satisfying a predicate
// on its converted value, if any. O(N), allocates Some[A]
@inline final def findConvert[D, H](h: H, fn: D => Boolean)(implicit ev2: GetHead[H, A, C], ev4: Convert[B, D]): Option[A] = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
findConvertInternal(ev2.getHead(h), fn)
}
@inline @tailrec private[this] final def findConvertInternal[D](a: A, fn: D => Boolean)(implicit ev4: Convert[B, D]): Option[A] = {
if (a == null) return None
val d = ev4.convert(value(a))
if (d.isDefined && fn(d.get)) Some(a)
else findConvertInternal(next(a), fn)
}
// Finds the the first node in the list whose value equals the passed b, if any.
// This convenience function is equivalent to find(a, _==b). O(N), allocates Some[A]
@inline final def findValue[H](h: H, b: B)(implicit ev2: GetHead[H, A, C]): Option[A] = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
assert(b != null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
findValueInternal(ev2.getHead(h), b)
}
@inline @tailrec private[this] final def findValueInternal(a: A, b: B): Option[A] = {
if (a == null) None
else if (b == value(a)) Some(a)
else findValueInternal(next(a), b)
}
// Finds the the first node in the list whose converted value
// equals the passed d, if any. This convenience function is
// equivalent to findConvert(a, _==d). O(N), allocates Some[A]
@inline final def findValueConvert[D, H](h: H, d: D)(implicit ev2: GetHead[H, A, C], ev4: Convert[B, D]): Option[A] = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
findValueConvertInternal(ev2.getHead(h), d)
}
@inline @tailrec private[this] final def findValueConvertInternal[D](a: A, d: D)(implicit ev4: Convert[B, D]): Option[A] = {
if (a == null) return None
val e = ev4.convert(value(a))
if (e.isDefined && e.get == d) Some(a)
else findValueConvertInternal(next(a), d)
}
// Remove passed `a` from its list. Removing during iteration
// is supported. Precondition: `a` in the list. Postcondition:
// `a` not in the list and `a`.next/prev set to null. O(1)
@inline final def remove[I](i: I, a: A)(implicit ev3: SetHead[I, A, C]): Unit = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(i != null)
assert(a != null)
DoubleLinkedList.assertDoublyLinked(getHead(a))(this)
}
if (next(a) != null) setPrev(next(a), prev(a))
if (prev(a) == null) {
// This A is head of list
ev3.setHeadMaybeNull(i, next(a))
@enableIf(noricmud.If.Sanity) val __ = {
DoubleLinkedList.assertDoublyLinked(next(a))(this)
}
} else {
setNext(prev(a), next(a))
@enableIf(noricmud.If.Sanity) val __ = {
DoubleLinkedList.assertDoublyLinked(getHead(a))(this)
}
setPrev(a, null)
}
setNext(a, null)
}
// clearList clears the passed list, setting its head to null and all of its
// nodes' next/prev to null. Required eg. if a list's container is being destroyed,
// so that the nodes involved in the list properly have next/prev set to null
// prior to potential reuse. The passed postRemove is called for each removed
// node, after the node is fully removed and so nodes passed to postRemove may be
// arbitrarily modified (eg. to return the node to a DoubleLinkedList.Pool). O(N)
@inline final def clearList[H, I](h: H, i: I, postRemove: Option[A => Unit])(implicit ev2: GetHead[H, A, C], ev3: SetHead[I, A, C]): Unit = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
assert(i != null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
val head = ev2.getHead(h)
if (head != null) {
ev3.setHeadMaybeNull(i, null)
clearListInternal(next(head), postRemove)
setNext(head, null)
if (postRemove.isDefined) postRemove.get(head)
}
}
@inline @tailrec private[this] final def clearListInternal(a: A, postRemove: Option[A => Unit]): Unit = {
if (a == null) return
val nextA = next(a)
setNext(a, null)
setPrev(a, null)
if (postRemove.isDefined) postRemove.get(a)
clearListInternal(nextA, postRemove)
}
// clearList clears the passed list, setting its head to null and all
// of its nodes' next/prev to null. Each cleared node is returned to
// the passed Pool via pool.free. Required eg. if a list's container
// is being destroyed, so that the nodes involved in the list
// properly have next/prev set to null prior to potential reuse. O(N)
@inline final def clearListWithPool[H, I](h: H, i: I, pool: DoubleLinkedList.Pool[A, B, C])(implicit ev2: GetHead[H, A, C], ev3: SetHead[I, A, C]): Unit = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
assert(i != null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
val head = ev2.getHead(h)
if (head != null) {
ev3.setHeadMaybeNull(i, null)
clearListWithPoolInternal(next(head), pool)
setNext(head, null)
pool.free(head)
}
}
@inline @tailrec private[this] final def clearListWithPoolInternal(a: A, pool: DoubleLinkedList.Pool[A, B, C]): Unit = {
if (a == null) return
val nextA = next(a)
setNext(a, null)
setPrev(a, null)
pool.free(a)
clearListWithPoolInternal(nextA, pool)
}
// Prepend passed `a` to the list. Precondition: `a` not in any list. O(1)
@inline final def prepend[H, I](h: H, i: I, a: A)(implicit ev2: GetHead[H, A, C], ev3: SetHead[I, A, C]): Unit = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
assert(i != null)
assert(a != null)
assert(next(a) == null)
assert(prev(a) == null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
setNext(a, ev2.getHead(h))
if (ev2.getHead(h) != null) setPrev(ev2.getHead(h), a)
ev3.setHeadNotNull(i, a)
}
// Foreach visits all values in the list with the passed fn. Removing during
// iteration is supported (WARNING) for any node except nextA, ie. you may not
// remove `next(a)` while processing `a` because nextA is cached on the stack
// and always processed next. You may also use a foreachRemove* function. O(N)
@inline final def foreach[H](h: H, fn: B => Unit)(implicit ev2: GetHead[H, A, C]): Unit = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
foreachInternal(ev2.getHead(h), fn)
}
@inline @tailrec private[this] final def foreachInternal(a: A, fn: B => Unit): Unit = {
if (a == null) return
val nextA = next(a)
fn(value(a))
foreachInternal(nextA, fn)
}
// foreachRemoveFilter is similar to foreach except nodes are removed
// from the list if the passed filter fn returns false. This allows the
// client's passed fn to remove nodes during iteration (by way of the
// filter function returning false). The passed postRemove is called for
// each removed node, after the node is fully removed and so nodes passed
// to postRemove may be arbitrarily modified (eg. to return the node to a
// DoubleLinkedList.Pool). Behavior is undefined if the visit function calls
// remove(node) _and_ asks foreachRemove* to also remove the node. O(N)
@inline final def foreachRemoveFilter[H, I](h: H, i: I, fn: B => Boolean, postRemove: Option[A => Unit])(implicit ev2: GetHead[H, A, C], ev3: SetHead[I, A, C]): Unit = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
assert(i != null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
while (ev2.getHead(h) != null) {
if (!fn(value(ev2.getHead(h)))) {
// visit function returned false, remove head
val nodeToBeRemoved = ev2.getHead(h)
removeHead(h, i)
if (postRemove.isDefined) postRemove.get(nodeToBeRemoved)
} else {
// head is now permanent head. Process tail
var a = next(ev2.getHead(h))
while (a != null) {
val nextA = next(a)
if (!fn(value(a))) {
// visit function returned false, remove node
removeNotHead(a)
if (postRemove.isDefined) postRemove.get(a)
}
a = nextA
}
return // entire list processed
}
}
}
// foreachRemoveConvert is similar to foreach except nodes are removed from the
// list if their values fail conversion. This allows the client function to remove
// nodes during iteration (by way of node values failing conversion). The passed
// postRemove is called for each removed node, after the node is fully removed and
// so nodes passed to postRemove may be arbitrarily modified (eg. to return the
// node to a DoubleLinkedList.Pool). Behavior is undefined if the visit function
// calls remove(node) _and_ asks foreachRemove* to also remove the node. O(N)
@inline final def foreachRemoveConvert[D, H, I](h: H, i: I, fn: D => Unit, postRemove: Option[A => Unit])(implicit ev2: GetHead[H, A, C], ev3: SetHead[I, A, C], ev4: Convert[B, D]): Unit = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
assert(i != null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
while (ev2.getHead(h) != null) {
ev4.convert(value(ev2.getHead(h))) match {
case Some(d) => {
fn(d)
// head is now permanent head. Process tail
var a = next(ev2.getHead(h))
while (a != null) {
val nextA = next(a)
ev4.convert(value(a)) match {
case Some(d) => fn(d)
case None => {
// conversion failed, remove node
removeNotHead(a)
if (postRemove.isDefined) postRemove.get(a)
}
}
a = nextA
}
return // entire list processed
}
case None => {
// conversion failed, remove head
val nodeToBeRemoved = ev2.getHead(h)
removeHead(h, i)
if (postRemove.isDefined) postRemove.get(nodeToBeRemoved)
}
}
}
}
// foreachRemoveConvertFilter is similar to foreach except nodes are removed
// from the list if their values fail conversion or the passed filter fn returns
// false. This allows the client function to remove nodes during iteration (by way
// of node values failing conversion or if filter fn returns false). The passed
// postRemove is called for each removed node, after the node is fully removed and
// so nodes passed to postRemove may be arbitrarily modified (eg. to return the
// node to a DoubleLinkedList.Pool). Behavior is undefined if the visit function
// calls remove(node) _and_ asks foreachRemove* to also remove the node. O(N)
@inline final def foreachRemoveConvertFilter[D, H, I](h: H, i: I, fn: D => Boolean, postRemove: Option[A => Unit])(implicit ev2: GetHead[H, A, C], ev3: SetHead[I, A, C], ev4: Convert[B, D]): Unit = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
assert(i != null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
while (ev2.getHead(h) != null) {
ev4.convert(value(ev2.getHead(h))) match {
case Some(d) => {
if (!fn(d)) {
// visit function returned false, head will be removed
} else {
// visit function returned true, head is now permanent head. Process tail
var a = next(ev2.getHead(h))
while (a != null) {
val nextA = next(a)
ev4.convert(value(a)) match {
case Some(d) => if (!fn(d)) {
// visit function returned false, remove node
removeNotHead(a)
if (postRemove.isDefined) postRemove.get(a)
}
case None => {
// conversion failed, remove node
removeNotHead(a)
if (postRemove.isDefined) postRemove.get(a)
}
}
a = nextA
}
return // entire list processed
}
}
case None => // conversion failed, head will be removed
}
val nodeToBeRemoved = ev2.getHead(h)
removeHead(h, i)
if (postRemove.isDefined) postRemove.get(nodeToBeRemoved)
}
}
// foldLeft applies a binary operator to a start value and all values
// of the list, returning the result of these applications. Removing
// during foldLeft is supported (WARNING) for any node except nextA,
// ie. you may not remove `next(a)` while processing `a` because
// nextA is cached on the stack and always processed next. O(N)
@inline final def foldLeft[E, H](h: H, e: E)(fn: (E, B) => E)(implicit ev2: GetHead[H, A, C]): E = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
foldLeftInternal(ev2.getHead(h), e, fn)
}
@inline @tailrec private[this] final def foldLeftInternal[E](a: A, e: E, fn: (E, B) => E): E = {
if (a == null) e
else {
val nextA = next(a)
foldLeftInternal(nextA, fn(e, value(a)), fn)
}
}
// flatMapToList creates a new List by mapping all values in the doubly linked list
// using the passed fn, discarding values that map to None. O(N), allocates new List
@inline final def flatMapToList[H, E](h: H, fn: B => Option[E])(implicit ev2: GetHead[H, A, C]): List[E] = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
if (ev2.getHead(h) == null) return Nil
var tmp = getLast(ev2.getHead(h)) // getLast() is @tailrec so flatMapToList() uses constant stack frames. We iterate backwards because List supports only O(1) prepend and must be built backwards
var list: List[E] = Nil
do {
fn(value(tmp)) match {
case Some(e) => list ::= e
case None =>
}
tmp = prev(tmp)
} while (tmp != null)
list
}
// mapToList creates a new List by mapping all values in the
// doubly linked list using the passed fn. O(N), allocates new List
@inline final def mapToList[H, E](h: H, fn: B => E)(implicit ev2: GetHead[H, A, C]): List[E] = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
if (ev2.getHead(h) == null) return Nil
var tmp = getLast(ev2.getHead(h)) // getLast() is @tailrec so mapToList() uses constant stack frames. We iterate backwards because List supports only O(1) prepend and must be built backwards
var list: List[E] = Nil
do {
list ::= fn(value(tmp))
tmp = prev(tmp)
} while (tmp != null)
list
}
// toList creates a new List from the list with
// the passed head `a`. O(N), allocates new List
@inline final def toList[H](h: H)(implicit ev2: GetHead[H, A, C]): List[B] = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) == null || prev(ev2.getHead(h)) == null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
if (ev2.getHead(h) == null) return Nil
var tmp = getLast(ev2.getHead(h)) // getLast() is @tailrec so toList() uses constant stack frames. We iterate backwards because List supports only O(1) prepend and must be built backwards
var list: List[B] = Nil
do {
list ::= value(tmp)
tmp = prev(tmp)
} while (tmp != null)
list
}
// Append `a` to the list with passed last. Returns `a`, the new last. `a` must
// not be null, but last may be null. Clients usually don't keep track of a list's
// last and instead use prepend; append is used to build lists efficently. O(1)
@inline private final def append(a: A, last: A): A = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(a != null)
if (last != null) assert(next(last) == null)
DoubleLinkedList.assertDoublyLinked(last)(this)
}
setPrev(a, last)
setNext(a, null)
if (last != null) setNext(last, a)
a
}
// removeHead removes the head of the list when that head is known to be not null.
@inline private final def removeHead[H, I](h: H, i: I)(implicit ev2: GetHead[H, A, C], ev3: SetHead[I, A, C]): Unit = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(h != null)
assert(ev2.getHead(h) != null)
assert(prev(ev2.getHead(h)) == null)
assert(i != null)
DoubleLinkedList.assertDoublyLinked(ev2.getHead(h))(this)
}
val headToBeRemoved = ev2.getHead(h)
if (next(headToBeRemoved) != null) setPrev(next(headToBeRemoved), null)
ev3.setHeadMaybeNull(i, next(headToBeRemoved))
setNext(headToBeRemoved, null)
setPrev(headToBeRemoved, null)
}
// Flavor of remove called when `a` is known to not be list head.
@inline private final def removeNotHead(a: A): Unit = {
@enableIf(noricmud.If.Sanity) val __ = {
assert(a != null)
assert(prev(a) != null)
DoubleLinkedList.assertDoublyLinked(getHead(a))(this)
}
if (next(a) != null) setPrev(next(a), prev(a))
setNext(prev(a), next(a))
setNext(a, null)
setPrev(a, null)
}
// getHead returns head of `a`'s list, used for debug
// purposes when a DoubleLinkedListHead is not in scope.
@inline @tailrec private final def getHead(a: A): A = {
if (a == null || prev(a) == null) a
else getHead(prev(a))
}
// getLast returns the last node of `a`'s list.
@inline @tailrec private final def getLast(a: A): A = {
if (a == null || next(a) == null) a
else getLast(next(a))
}
}
final object DoubleLinkedList {
import InferType.InferType // see doc on InferType
// NB for public APIs that take postRemove: postRemove takes the place
// of InferType[A] because if postRemove is defined then InferType is
// not required and if postRemove is None then the client can still
// use InferType in place of postRemove like: InferType[A => Unit]
// NB DoubleLinkedList APIs which require an instance of both GetHead and
// SetHead are overloaded in two flavors: one flavor where GetHead and
// SetHead operate on the same list container type, ie. type parameters H
// = I and only an instance `h` must be passed; and another flavor where
// GetHead and SetHead operate on different list container types, ie.
// type parameters H != I and both instances `h` and `i` must be passed.
// Most clients shouldn't use DoubleLinkedList.value/next/prev
// and instead should use the safe, convenient API below.
@inline def value[A >: Null <: AnyRef, B, C](a: A)(implicit ev: DoubleLinkedList[A, B, C]) = ev.value(a)
@inline def next[A >: Null <: AnyRef, B, C](a: A)(implicit ev: DoubleLinkedList[A, B, C]) = ev.next(a)
@inline def prev[A >: Null <: AnyRef, B, C](a: A)(implicit ev: DoubleLinkedList[A, B, C]) = ev.prev(a)
// TODO APIs that take functions should allow those functions to take a supertype of B, eg. instead of `B => Boolean` --> `F >: B, F => Boolean`
// TODO unit tests with assertions at bottom in a val _ = {}
// partial list of test cases for foreachRemove*()
// in null
// in is only node in list and removed and now head is null
// [in, a] are both removed and now head is null
// in is removed and only other node in list is now head
@inline def isEmpty[A >: Null <: AnyRef, B, C, H](h: H, t: InferType[A])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C]) = ev.isEmpty(h)
@inline def nonEmpty[A >: Null <: AnyRef, B, C, H](h: H, t: InferType[A])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C]) = ev.nonEmpty(h)
@inline def atLeastTwo[A >: Null <: AnyRef, B, C, H](h: H, t: InferType[A])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C]) = ev.atLeastTwo(h)
@inline def forall[A >: Null <: AnyRef, B, C, H](h: H, fn: B => Boolean, t: InferType[A])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C]) = ev.forall(h, fn)
@inline def exists[A >: Null <: AnyRef, B, C, H](h: H, fn: B => Boolean, t: InferType[A])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C]) = ev.exists(h, fn)
@inline def length[A >: Null <: AnyRef, B, C, H](h: H, t: InferType[A])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C]) = ev.length(h) // WARNING length is O(N)
@inline def count[A >: Null <: AnyRef, B, C, H](h: H, fn: B => Boolean, t: InferType[A])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C]) = ev.count(h, fn)
// WARNING clients should use findSomeThis and other SomeThis variants where possible because they incur zero allocations, whereas non-SomeThis variants allocate a Some.
@inline def find[A >: Null <: AnyRef, B, C, H](h: H, fn: B => Boolean, t: InferType[A])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C]) = ev.find(h, fn)
@inline def findSomeThis[A >: Null <: AnyRef with SomeThis[A], B, C, H](h: H, fn: B => Boolean, t: InferType[A])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C]) = ev.findSomeThis(h, fn)
@inline def findConvert[A >: Null <: AnyRef, B, C, D, H](h: H, fn: D => Boolean, t: InferType[A])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C], ev4: Convert[B, D]) = ev.findConvert(h, fn)
@inline def findValue[A >: Null <: AnyRef, B, C, H](h: H, b: B, t: InferType[A])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C]) = ev.findValue(h, b)
@inline def findValueConvert[A >: Null <: AnyRef, B, C, D, H](h: H, d: D, t: InferType[A])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C], ev4: Convert[B, D]) = ev.findValueConvert(h, d)
@inline def remove[A >: Null <: AnyRef, B, C, I](i: I, a: A)(implicit ev: DoubleLinkedList[A, B, C], ev3: SetHead[I, A, C]) = ev.remove(i, a)
@inline def clearList[A >: Null <: AnyRef, B, C, H](h: H, postRemove: Option[A => Unit])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C], ev3: SetHead[H /* not a typo, I = H in this overload */, A, C]) = ev.clearList(h, h, postRemove)
@inline def clearList[A >: Null <: AnyRef, B, C, H, I](h: H, i: I, postRemove: Option[A => Unit])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C], ev3: SetHead[I, A, C]) = ev.clearList(h, i, postRemove)
@inline def clearListWithPool[A >: Null <: AnyRef, B, C, H](h: H, pool: Pool[A, B, C])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C], ev3: SetHead[H /* not a typo, I = H in this overload */, A, C]) = ev.clearListWithPool(h, h, pool)
@inline def clearListWithPool[A >: Null <: AnyRef, B, C, H, I](h: H, i: I, pool: Pool[A, B, C])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C], ev3: SetHead[I, A, C]) = ev.clearListWithPool(h, i, pool)
@inline def prepend[A >: Null <: AnyRef, B, C, H](h: H, a: A)(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C], ev3: SetHead[H /* not a typo, I = H in this overload */, A, C]) = ev.prepend(h, h, a)
@inline def prepend[A >: Null <: AnyRef, B, C, H, I](h: H, i: I, a: A)(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C], ev3: SetHead[I, A, C]) = ev.prepend(h, i, a)
def foreach[A >: Null <: AnyRef, B, C, H](h: H, fn: B => Unit, t: InferType[A])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C]) = ev.foreach(h, fn)
@inline def foreachRemoveFilter[A >: Null <: AnyRef, B, C, H](h: H, fn: B => Boolean, postRemove: Option[A => Unit])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C], ev3: SetHead[H /* not a typo, I = H in this overload */, A, C]) = ev.foreachRemoveFilter(h, h, fn, postRemove)
@inline def foreachRemoveFilter[A >: Null <: AnyRef, B, C, H, I](h: H, i: I, fn: B => Boolean, postRemove: Option[A => Unit])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C], ev3: SetHead[I, A, C]) = ev.foreachRemoveFilter(h, i, fn, postRemove)
@inline def foreachRemoveConvert[A >: Null <: AnyRef, B, C, D, H](h: H, fn: D => Unit, postRemove: Option[A => Unit])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C], ev3: SetHead[H /* not a typo, I = H in this overload */, A, C], ev4: Convert[B, D]) = ev.foreachRemoveConvert(h, h, fn, postRemove)
@inline def foreachRemoveConvert[A >: Null <: AnyRef, B, C, D, H, I](h: H, i: I, fn: D => Unit, postRemove: Option[A => Unit])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C], ev3: SetHead[I, A, C], ev4: Convert[B, D]) = ev.foreachRemoveConvert(h, i, fn, postRemove)
@inline def foreachRemoveConvertFilter[A >: Null <: AnyRef, B, C, D, H](h: H, fn: D => Boolean, postRemove: Option[A => Unit])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C], ev3: SetHead[H /* not a typo, I = H in this overload */, A, C], ev4: Convert[B, D]) = ev.foreachRemoveConvertFilter(h, h, fn, postRemove)
@inline def foreachRemoveConvertFilter[A >: Null <: AnyRef, B, C, D, H, I](h: H, i: I, fn: D => Boolean, postRemove: Option[A => Unit])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C], ev3: SetHead[I, A, C], ev4: Convert[B, D]) = ev.foreachRemoveConvertFilter(h, i, fn, postRemove)
def foldLeft[A >: Null <: AnyRef, B, C, E, H](h: H, e: E, t: InferType[A])(fn: (E, B) => E)(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C]) = ev.foldLeft(h, e)(fn)
@inline def flatMapToList[A >: Null <: AnyRef, B, C, E, H](h: H, fn: B => Option[E], t: InferType[A])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C]): List[E] = ev.flatMapToList(h, fn)
@inline def mapToList[A >: Null <: AnyRef, B, C, E, H](h: H, fn: B => E, t: InferType[A])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C]): List[E] = ev.mapToList(h, fn)
@inline def toList[A >: Null <: AnyRef, B, C, H](h: H, t: InferType[A])(implicit ev: DoubleLinkedList[A, B, C], ev2: GetHead[H, A, C]) = ev.toList(h)
// fromList builds a new doubly linked list from the passed List.
// Returns the head node of the built list. The passed `fn: B => A`
// is expected to wrap the passed B in an A. Eg. the Bs are ActorRef
// and `fn: B => A` gets a DoubleLinkedListDefault from a Pool.
@inline def fromList[A >: Null <: AnyRef, B, C](bs: List[B], fn: B => A)(implicit ev: DoubleLinkedList[A, B, C]): A = {
if (bs.isEmpty) return null
val dllHead: A = fn(bs.head)
var dllLast: A = dllHead
var bsTail = bs.tail
while (bsTail.nonEmpty) {
dllLast = ev.append(fn(bsTail.head), dllLast)
bsTail = bsTail.tail
}
dllHead
}
// fromList builds a new doubly linked list from the passed List where the DLL
// node type is the same as the value type. Returns the head node of the built
// list. Ie. typically we have DLL[A,B,C] and instead here we have DLL[A,A,C].
// Eg. the `as` are Item which, in most cases, store their own DLL links.
@inline def fromList[A >: Null <: AnyRef, C](as: List[A])(implicit ev: DoubleLinkedList[A, A, C]): A = {
if (as.isEmpty) return null
val dllHead: A = as.head
var dllLast: A = dllHead
var asTail = as.tail
while (asTail.nonEmpty) {
dllLast = ev.append(asTail.head, dllLast)
asTail = asTail.tail
}
dllHead
}
// mapFromList converts the passed List[E] into a doubly linked list containing
// those Es converted to Bs in nodes of type A. Returns the head node of the
// built list. The passed `fn: B => A` is expected to wrap the passed B in
// an A. Eg. the Es are FooImmutable, Bs are Foo, but Foo doesn't store its
// own DLL links and `fn: B => A` gets a DoubleLinkedListDefault from a Pool.
@inline def mapFromList[A >: Null <: AnyRef, B, C, E](es: List[E], fn: B => A, fn2: E => B)(implicit ev: DoubleLinkedList[A, B, C]): A = {
if (es.isEmpty) return null
val dllHead: A = fn(fn2(es.head))
var dllLast: A = dllHead
var esTail = es.tail
while (esTail.nonEmpty) {
dllLast = ev.append(fn(fn2(esTail.head)), dllLast)
esTail = esTail.tail
}
dllHead
}
// mapFromList converts the passed List[E] into a doubly linked list containing
// those Es converted into As and the DLL node type is the same as the value type.
// Returns the head node of the built list. Ie. typically we have DLL[A,B,C] and
// instead here we have DLL[A,A,C] with each E converted into an A. Eg. the Es are
// ItemImmutable and As are Item which, in most cases, store their own DLL links.
@inline def mapFromList[A >: Null <: AnyRef, C, E](es: List[E], fn: E => A)(implicit ev: DoubleLinkedList[A, A, C]): A = {
if (es.isEmpty) return null
val dllHead: A = fn(es.head)
var dllLast: A = dllHead
var esTail = es.tail
while (esTail.nonEmpty) {
dllLast = ev.append(fn(esTail.head), dllLast)
esTail = esTail.tail
}
dllHead
}
// Pool is an object pool of nodes implementing DoubleLinkedList which
// can help minimize allocations. Pool has zero allocations and O(1)
// runtime, unless the pool has no nodes available on next(). Pool is not
// tool against memory fragmentation which is highly magical in the JVM.
// A separate tool against memory fragmentation (and allocations) is to
// use contiguous arrays of primitives to represent a pool of objects, see
// https://stackoverflow.com/questions/9632288/can-i-allocate-objects-contiguously-i
// Also see // http://gameprogrammingpatterns.com/object-pool.html
// . The passed makeA is expected to instantiate a new A.
final class Pool[A >: Null <: AnyRef, B, C](initialSize: Int, makeA: => A)(implicit ev: DoubleLinkedList[A, B, C]) {
private var nodeCount: Int = 1 // ie. because head is initialized to makeA
private var head: A = makeA // singly-linked list (stack) of nodes in pool
for (_ <- 0 to initialSize-2) free(makeA) // grow pool to initialSize
// Free the passed A, returning it to the pool. Assumes A is currently
// not participating in another list. WARNING ev.value(a) must be
// cleared by the client if possible. Some instances of DoubleLinkedList
// can't "clear" the value because A is itself the value (eg. Mob
// nextInRoom). DoubleLinkedList doesn't offer clearValue() because
// Pool would be the only client and it's unsafe in general. O(1)
@inline def free(a: A): Unit = {
nodeCount += 1
@enableIf(noricmud.If.Sanity) val __ = {
assert(ev.next(a) == null)
assert(ev.prev(a) == null)
}
ev.setNext(a, head)
head = a
}
// Next gets the next A for client use, removing it
// from the pool. O(1) unless no nodes available.
@inline def next(): A = {
if (nodeCount < 1) {
// pool is empty, instantiate new A for client
makeA
} else {
nodeCount -= 1
val a = head
head = ev.next(head)
ev.setNext(a, null)
a
}
}
}
@enableIf(noricmud.If.Sanity)
@inline private def assertDoublyLinked[A >: Null <: AnyRef, B, C](as: A)(implicit ev: DoubleLinkedList[A, B, C]): Unit = {
if (as == null) return
import scala.collection._
val seen = mutable.Set[A]()
var err = ""
var s = ""
var i = 0
var prev: A = ev.prev(as)
var a = as
while (a != null) {
if (seen.contains(a)) throw new RuntimeException(s"cycle in list $as $a $seen")
seen.add(a)
if (i > 0 && prev != ev.prev(a)) {
err += s"\nlinked list isn't double, prev=$prev, a.prev=${ev.prev(a)}, a=$a"
} else if (i < 1 && ev.prev(a) != null) {
err += s"\n head of linked list has prev != null, head.prev=${ev.prev(a)}, head=$a"
}
s += a.toString + "-"
prev = a
a = ev.next(a)
i += 1
}
s += s"null $err"
if (err != "") throw new RuntimeException(s)
}
}
// DoubleLinkedListDefault is a convenience class to serve as the
// node type for DoubleLinkedList when it's unsuitable for the
// value type to contain its own next/prev, eg. for 3rd-party types.
final class DoubleLinkedListDefault[A](var v: A) {
var next: DoubleLinkedListDefault[A] = null
var prev: DoubleLinkedListDefault[A] = null
}
final object DoubleLinkedListDefault {
// Make a typeclass instance of DoubleLinkedList
// for use with DoubleLinkedListDefault
def doubleLinkedListNodeDefaultDLL[A, C]: DoubleLinkedList[DoubleLinkedListDefault[A], A, C] = new DoubleLinkedList[DoubleLinkedListDefault[A], A, C] {
type T = DoubleLinkedListDefault[A]
@inline def value(a: T) = a.v
@inline def next(a: T) = a.next
@inline def prev(a: T) = a.prev
@inline def setNext(a: T, b: T) = a.next = b
@inline def setPrev(a: T, b: T) = a.prev = b
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment