Skip to content
This repository has been archived by the owner on Feb 24, 2021. It is now read-only.

Make IQueue size O(1) #189

Merged
merged 6 commits into from
Jun 22, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 49 additions & 29 deletions arrow-fx-coroutines/src/main/kotlin/arrow/fx/coroutines/IQueue.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,33 @@ package arrow.fx.coroutines

/**
* Port of `scala.collection.immutable.Queue`
*
* `Queue` objects implement data structures that allow to
* insert and retrieve elements in a first-in-first-out (FIFO) manner.
*
* `Queue` is implemented as a pair of `List`s, one containing the ''in'' elements and the other the ''out'' elements.
* Elements are added to the ''in'' list and removed from the ''out'' list. When the ''out'' list runs dry, the
* queue is pivoted by replacing the ''out'' list by ''in.reverse'', and ''in'' by ''Nil''.
*
*/
internal class IQueue<A> internal constructor(
class IQueue<A> internal constructor(
private val listIn: List<A>,
private val listOut: List<A>
private val listOut: List<A>,
/** an O(1) size method. */
val size: Int
) : Iterable<A> {

private fun <A> Iterable<A>.tail() = drop(1)

private fun cons(a: A, l: List<A>): List<A> = listOf(a) + l

fun isEmpty(): Boolean = listIn.isEmpty() && listOut.isEmpty()
fun isEmpty(): Boolean =
size == 0

fun isNotEmpty(): Boolean = listIn.isNotEmpty() || listOut.isNotEmpty()
fun isNotEmpty(): Boolean =
!isEmpty()

fun first(): A =
internal fun first(): A =
firstOrNull() ?: throw NoSuchElementException("first on empty queue")

fun firstOrNull(): A? =
Expand All @@ -31,13 +38,10 @@ internal class IQueue<A> internal constructor(
else -> null
}

fun tail(): IQueue<A> =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

tailOrNull() ?: throw NoSuchElementException("tail on empty queue")

fun tailOrNull(): IQueue<A>? =
when {
listOut.isNotEmpty() -> IQueue(listIn, listOut.tail())
listIn.isNotEmpty() -> IQueue(emptyList(), listIn.reversed().tail())
listOut.isNotEmpty() -> IQueue(listIn, listOut.tail(), size - 1)
listIn.isNotEmpty() -> IQueue(emptyList(), listIn.reversed().tail(), size - 1)
else -> null
}

Expand All @@ -47,52 +51,68 @@ internal class IQueue<A> internal constructor(
fun exists(p: (A) -> Boolean): Boolean =
listIn.any(p) || listOut.any(p)

fun length(): Int = listIn.size + listOut.size

fun enqueue(elem: A): IQueue<A> =
IQueue(cons(elem, listIn), listOut)
IQueue(cons(elem, listIn), listOut, size + 1)

fun enqueue(elems: Iterable<A>): IQueue<A> =
IQueue(elems.reversed() + listIn, listOut)
IQueue(elems.reversed() + listIn, listOut, size + elems.size())

fun prepend(elem: A): IQueue<A> =
IQueue(listIn, cons(elem, listOut))
IQueue(listIn, cons(elem, listOut), size + 1)

fun dequeue(): Pair<A, IQueue<A>> =
when {
listOut.isEmpty() && listIn.isNotEmpty() -> {
val rev = listIn.reversed()
Pair(rev.first(), IQueue(emptyList(), rev.tail()))
Pair(rev.first(), IQueue(emptyList(), rev.tail(), size - 1))
}
listOut.isNotEmpty() -> Pair(listOut.first(), IQueue(listIn, listOut.tail()))
listOut.isNotEmpty() -> Pair(listOut.first(), IQueue(listIn, listOut.tail(), size - 1))
else -> throw NoSuchElementException("dequeue on empty queue")
}

fun drop(n: Int): IQueue<A> = when {
listOut.isEmpty() && listIn.isNotEmpty() -> IQueue(emptyList(), listIn.reversed().drop(n))
listOut.isNotEmpty() -> IQueue(listIn, listOut.drop(n))
else -> throw NoSuchElementException("dequeue on empty queue")
listOut.isEmpty() && listIn.isNotEmpty() -> IQueue(emptyList(), listIn.reversed().drop(n), size - n.coerceAtLeast(0))
listOut.isNotEmpty() -> IQueue(listIn, listOut.drop(n), size - n.coerceAtLeast(0))
else -> empty()
}

fun dequeueOrNull(): Pair<A, IQueue<A>>? =
if (isEmpty()) null
else dequeue()

fun filter(p: (A) -> Boolean): IQueue<A> =
IQueue(listIn.filter(p), listOut.filter(p))
fun filter(p: (A) -> Boolean): IQueue<A> {
val newIn = listIn.filter(p)
val newOut = listOut.filter(p)
return IQueue(newIn, newOut, newIn.size + newOut.size)
}

fun filterNot(p: (A) -> Boolean): IQueue<A> {
val newIn = listIn.filterNot(p)
val newOut = listOut.filterNot(p)
return IQueue(newIn, newOut, newIn.size + newOut.size)
}

fun filterNot(p: (A) -> Boolean): IQueue<A> =
IQueue(listIn.filterNot(p), listOut.filterNot(p))
override fun equals(other: Any?): Boolean =
nomisRev marked this conversation as resolved.
Show resolved Hide resolved
if (other is IQueue<*>) {
size == other.size &&
listIn == other.listIn &&
listOut == other.listOut
} else false

override fun hashCode(): Int =
fold(1) { acc, o ->
31 * acc + o.hashCode()
}

override fun toString(): String =
"Queue(${listIn.joinToString(separator = ", ")}, ${listOut.joinToString(separator = ", ")})"
"IQueue(${listIn.joinToString(separator = ", ")}, ${listOut.joinToString(separator = ", ")})"

companion object {
@Suppress("UNCHECKED_CAST")
fun <A> empty(): IQueue<A> = EmptyQueue as IQueue<A>
operator fun <A> invoke(vararg a: A): IQueue<A> = IQueue(emptyList(), a.toList())
operator fun <A> invoke(a: A): IQueue<A> = IQueue(emptyList(), listOf(a))
operator fun <A> invoke(a: List<A>): IQueue<A> = IQueue(emptyList(), a)
operator fun <A> invoke(vararg a: A): IQueue<A> = IQueue(emptyList(), a.toList(), a.size)
operator fun <A> invoke(a: A): IQueue<A> = IQueue(emptyList(), listOf(a), 1)
operator fun <A> invoke(a: List<A>): IQueue<A> = IQueue(emptyList(), a, a.size)
}

override fun iterator(): Iterator<A> = toList().iterator()
Expand All @@ -103,4 +123,4 @@ internal class IQueue<A> internal constructor(
internal infix fun <A> A.prependTo(q: IQueue<A>): IQueue<A> =
q.prepend(this)

private val EmptyQueue: IQueue<Nothing> = IQueue(emptyList(), emptyList())
private val EmptyQueue: IQueue<Nothing> = IQueue(emptyList(), emptyList(), 0)
Original file line number Diff line number Diff line change
Expand Up @@ -321,11 +321,11 @@ private class SemaphoreDefault(private val state: Atomic<SemaphoreState>) : Sema
val waiting = prev.a

val newSize = when (new) {
is Either.Left -> new.a.length()
is Either.Left -> new.a.size
is Either.Right -> 0
}

val released = waiting.length() - newSize
val released = waiting.size - newSize
waiting.take(released).foldRight(Unit) { (_, gate), _ ->
open(gate)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ fun <A> List<A>.deleteFirst(f: (A) -> Boolean): Pair<A, List<A>>? {
return go(this, emptyList())
}

fun Iterable<*>.size(): Int =
when (this) {
is Collection -> size
else -> fold(0) { acc, _ -> acc + 1 }
}

/** Represents a unique identifier using object equality. */
internal class Token {
override fun toString(): String = "Token(${Integer.toHexString(hashCode())})"
Expand Down