Improved the documentation and miscelaneous
Better bounds checking. Tried to make it and documentation comply with the conflicting style guides.
Added example of usage at the top of the module as well as warnings on usage.
Also fix the back() and internal englishOrdinal() proc from previous commit.
Added {.discardable.} pragma for .pop(), when calling only for it's side effects.
Sprinkled some unlikely() for optimization.
Some new tests reflecting those changes.
This commit is contained in:
parent
d6849b87c5
commit
dac4826483
1 changed files with 103 additions and 34 deletions
|
|
@ -8,6 +8,33 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
## Implementation of a `queue`:idx:. The underlying implementation uses a ``seq``.
|
## Implementation of a `queue`:idx:. The underlying implementation uses a ``seq``.
|
||||||
|
##
|
||||||
|
## None of the procs that get an individual value from the queue can be used
|
||||||
|
## on an empty queue.
|
||||||
|
## If compiled with `boundChecks` option, those procs will raise an `IndexError`
|
||||||
|
## on such access. This should not be relied upon, as `-d:release` will
|
||||||
|
## disable those checks and may return garbage or crash the program.
|
||||||
|
##
|
||||||
|
## As such, a check to see if the queue is empty is needed before any
|
||||||
|
## access, unless your program logic guarantees it indirectly.
|
||||||
|
##
|
||||||
|
## .. code-block:: Nim
|
||||||
|
## proc foo(a, b: Positive) = # assume random positive values for `a` and `b`
|
||||||
|
## var q = initQueue[int]() # initializes the object
|
||||||
|
## for i in 1 ..< a: q.add i # populates the queue
|
||||||
|
##
|
||||||
|
## if b < q.len: # checking before indexed access
|
||||||
|
## echo "The element at index position ", b, " is ", q[b]
|
||||||
|
##
|
||||||
|
## # The following two lines don't need any checking on access due to the
|
||||||
|
## # logic of the program, but that would not be the case if `a` could be 0.
|
||||||
|
## assert q.front == 1
|
||||||
|
## assert q.back == a
|
||||||
|
##
|
||||||
|
## while q.len > 0: # checking if the queue is empty
|
||||||
|
## echo q.pop()
|
||||||
|
##
|
||||||
|
##
|
||||||
## Note: For inter thread communication use
|
## Note: For inter thread communication use
|
||||||
## a `Channel <channels.html>`_ instead.
|
## a `Channel <channels.html>`_ instead.
|
||||||
|
|
||||||
|
|
@ -30,61 +57,71 @@ proc englishOrdinal(n: SomeInteger): string =
|
||||||
import math
|
import math
|
||||||
|
|
||||||
type
|
type
|
||||||
Queue*[T] = object ## a queue
|
Queue*[T] = object ## A queue.
|
||||||
data: seq[T]
|
data: seq[T]
|
||||||
rd, wr, count, mask: int
|
rd, wr, count, mask: int
|
||||||
|
|
||||||
{.deprecated: [TQueue: Queue].}
|
{.deprecated: [TQueue: Queue].}
|
||||||
|
|
||||||
proc initQueue*[T](initialSize: int = 4): Queue[T] =
|
proc initQueue*[T](initialSize: int = 4): Queue[T] =
|
||||||
## creates a new queue. `initialSize` needs to be a power of 2.
|
## Create a new queue.
|
||||||
|
## Optionally, the initial capacity can be reserved via `initialSize` as a
|
||||||
|
## performance optimization. `initialSize` needs to be a power of 2.
|
||||||
|
## The lenght of a newly created queue will still be 0.
|
||||||
assert isPowerOfTwo(initialSize)
|
assert isPowerOfTwo(initialSize)
|
||||||
result.mask = initialSize-1
|
result.mask = initialSize-1
|
||||||
newSeq(result.data, initialSize)
|
newSeq(result.data, initialSize)
|
||||||
|
|
||||||
proc len*[T](q: Queue[T]): int {.inline.}=
|
proc len*[T](q: Queue[T]): int {.inline.}=
|
||||||
## returns the number of elements of `q`.
|
## Return the number of elements of `q`.
|
||||||
result = q.count
|
result = q.count
|
||||||
|
|
||||||
proc low*[T](q: Queue[T]): int {.inline.}=
|
proc low*[T](q: Queue[T]): int {.inline.}=
|
||||||
## returns the index of the oldest element of `q` (always 0).
|
## Return the index of the oldest element of `q` (always 0).
|
||||||
result = 0
|
result = 0
|
||||||
|
|
||||||
proc high*[T](q: Queue[T]): int {.inline.}=
|
proc high*[T](q: Queue[T]): int {.inline.}=
|
||||||
## returns the index of the last element inserted on `q` (equivalent to
|
## Return the index of the last element inserted on `q` (equivalent to
|
||||||
## `q.len - 1`).
|
## `q.len - 1`).
|
||||||
result = q.count - 1
|
result = q.count - 1
|
||||||
|
|
||||||
|
template emptyCheck(q) =
|
||||||
|
# Bounds check for the regular queue access.
|
||||||
|
when compileOption("boundChecks"):
|
||||||
|
if unlikely(q.count < 1):
|
||||||
|
raise newException(IndexError, "Empty queue.")
|
||||||
|
discard
|
||||||
|
|
||||||
|
template xBoundsCheck(q, i) =
|
||||||
|
# Bounds check for the array like accesses.
|
||||||
|
when compileOption("boundChecks"): # d:release should disable this.
|
||||||
|
if unlikely(i >= q.count): # x < q.low is taken care by the Natural parameter
|
||||||
|
raise newException(IndexError,
|
||||||
|
"Tried to access the " & englishOrdinal(i+1) &
|
||||||
|
" element of the queue but it has only " &
|
||||||
|
$q.count & " elements.")
|
||||||
|
discard
|
||||||
|
|
||||||
proc front*[T](q: Queue[T]): T {.inline.}=
|
proc front*[T](q: Queue[T]): T {.inline.}=
|
||||||
## returns the oldest element of `q`. Equivalent to `q.pop()` but does not
|
## Return the oldest element of `q`. Equivalent to `q.pop()` but does not
|
||||||
## remove it from the queue.
|
## remove it from the queue.
|
||||||
assert q.count > 0
|
emptyCheck(q)
|
||||||
result = q.data[q.rd]
|
result = q.data[q.rd]
|
||||||
|
|
||||||
proc back*[T](q: Queue[T]): T {.inline.} =
|
proc back*[T](q: Queue[T]): T {.inline.} =
|
||||||
## returns the newest element of `q` but does not remove it from the queue.
|
## Return the newest element of `q` but does not remove it from the queue.
|
||||||
assert q.count > 0
|
emptyCheck(q)
|
||||||
result = q.data[q.wr - 1]
|
result = q.data[q.wr - 1 and q.mask]
|
||||||
|
|
||||||
template xBoundsCheck(q, i) =
|
|
||||||
# Bounds check for the array like acceses.
|
|
||||||
when compileOption("boundChecks"): # d:release should disable this.
|
|
||||||
if i > q.high: # x < q.low is taken care by the Natural parameter
|
|
||||||
raise newException(IndexError,
|
|
||||||
"You tried to access the " & englishOrdinal(i+1) &
|
|
||||||
" element of the queue but it has only " &
|
|
||||||
$q.len & " elements.")
|
|
||||||
discard
|
|
||||||
|
|
||||||
proc `[]`*[T](q: Queue[T], i: Natural) : T {.inline.} =
|
proc `[]`*[T](q: Queue[T], i: Natural) : T {.inline.} =
|
||||||
## Acess the i-th element of `q` by order of insertion.
|
## Access the i-th element of `q` by order of insertion.
|
||||||
## q[0] is the oldest (the next one q.pop() will extract),
|
## q[0] is the oldest (the next one q.pop() will extract),
|
||||||
## q[^1] is the newest (last one added to the queue).
|
## q[^1] is the newest (last one added to the queue).
|
||||||
xBoundsCheck(q, i)
|
xBoundsCheck(q, i)
|
||||||
return q.data[q.rd + i and q.mask]
|
return q.data[q.rd + i and q.mask]
|
||||||
|
|
||||||
proc `[]`*[T](q: var Queue[T], i: Natural): var T {.inline.} =
|
proc `[]`*[T](q: var Queue[T], i: Natural): var T {.inline.} =
|
||||||
## Acess the i-th element of `q` and returns a mutable
|
## Access the i-th element of `q` and returns a mutable
|
||||||
## reference to it.
|
## reference to it.
|
||||||
xBoundsCheck(q, i)
|
xBoundsCheck(q, i)
|
||||||
return q.data[q.rd + i and q.mask]
|
return q.data[q.rd + i and q.mask]
|
||||||
|
|
@ -95,28 +132,28 @@ proc `[]=`* [T] (q: var Queue[T], i: Natural, val : T) {.inline.} =
|
||||||
q.data[q.rd + i and q.mask] = val
|
q.data[q.rd + i and q.mask] = val
|
||||||
|
|
||||||
iterator items*[T](q: Queue[T]): T =
|
iterator items*[T](q: Queue[T]): T =
|
||||||
## yields every element of `q`.
|
## Yield every element of `q`.
|
||||||
var i = q.rd
|
var i = q.rd
|
||||||
for c in 0 ..< q.count:
|
for c in 0 ..< q.count:
|
||||||
yield q.data[i]
|
yield q.data[i]
|
||||||
i = (i + 1) and q.mask
|
i = (i + 1) and q.mask
|
||||||
|
|
||||||
iterator mitems*[T](q: var Queue[T]): var T =
|
iterator mitems*[T](q: var Queue[T]): var T =
|
||||||
## yields every element of `q`.
|
## Yield every element of `q`.
|
||||||
var i = q.rd
|
var i = q.rd
|
||||||
for c in 0 ..< q.count:
|
for c in 0 ..< q.count:
|
||||||
yield q.data[i]
|
yield q.data[i]
|
||||||
i = (i + 1) and q.mask
|
i = (i + 1) and q.mask
|
||||||
|
|
||||||
iterator pairs*[T](q: Queue[T]): tuple[key: int, val: T] =
|
iterator pairs*[T](q: Queue[T]): tuple[key: int, val: T] =
|
||||||
## yields every (position, value) of `q`.
|
## Yield every (position, value) of `q`.
|
||||||
var i = q.rd
|
var i = q.rd
|
||||||
for c in 0 ..< q.count:
|
for c in 0 ..< q.count:
|
||||||
yield (c, q.data[i])
|
yield (c, q.data[i])
|
||||||
i = (i + 1) and q.mask
|
i = (i + 1) and q.mask
|
||||||
|
|
||||||
proc contains*[T](q: Queue[T], item: T): bool {.inline.} =
|
proc contains*[T](q: Queue[T], item: T): bool {.inline.} =
|
||||||
## Returns true if `item` is in `q` or false if not found. Usually used
|
## Return true if `item` is in `q` or false if not found. Usually used
|
||||||
## via the ``in`` operator. It is the equivalent of ``q.find(item) >= 0``.
|
## via the ``in`` operator. It is the equivalent of ``q.find(item) >= 0``.
|
||||||
##
|
##
|
||||||
## .. code-block:: Nim
|
## .. code-block:: Nim
|
||||||
|
|
@ -127,9 +164,9 @@ proc contains*[T](q: Queue[T], item: T): bool {.inline.} =
|
||||||
return false
|
return false
|
||||||
|
|
||||||
proc add*[T](q: var Queue[T], item: T) =
|
proc add*[T](q: var Queue[T], item: T) =
|
||||||
## adds an `item` to the end of the queue `q`.
|
## Add an `item` to the end of the queue `q`.
|
||||||
var cap = q.mask+1
|
var cap = q.mask+1
|
||||||
if q.count >= cap:
|
if unlikely(q.count >= cap):
|
||||||
var n {.noinit.} = newSeq[T](cap*2)
|
var n {.noinit.} = newSeq[T](cap*2)
|
||||||
for i, x in q:
|
for i, x in q:
|
||||||
shallowCopy(n[i], x) # does not use copyMem because the GC.
|
shallowCopy(n[i], x) # does not use copyMem because the GC.
|
||||||
|
|
@ -141,23 +178,23 @@ proc add*[T](q: var Queue[T], item: T) =
|
||||||
q.data[q.wr] = item
|
q.data[q.wr] = item
|
||||||
q.wr = (q.wr + 1) and q.mask
|
q.wr = (q.wr + 1) and q.mask
|
||||||
|
|
||||||
proc pop*[T](q: var Queue[T]): T =
|
proc pop*[T](q: var Queue[T]): T {.inline, discardable.} =
|
||||||
## removes and returns the first (oldest) element of the queue `q`.
|
## Remove and returns the first (oldest) element of the queue `q`.
|
||||||
assert q.count > 0
|
emptyCheck(q)
|
||||||
dec q.count
|
dec q.count
|
||||||
result = q.data[q.rd]
|
result = q.data[q.rd]
|
||||||
q.rd = (q.rd + 1) and q.mask
|
q.rd = (q.rd + 1) and q.mask
|
||||||
|
|
||||||
proc enqueue*[T](q: var Queue[T], item: T) =
|
proc enqueue*[T](q: var Queue[T], item: T) =
|
||||||
## alias for the ``add`` operation.
|
## Alias for the ``add`` operation.
|
||||||
q.add(item)
|
q.add(item)
|
||||||
|
|
||||||
proc dequeue*[T](q: var Queue[T]): T =
|
proc dequeue*[T](q: var Queue[T]): T =
|
||||||
## alias for the ``pop`` operation.
|
## Alias for the ``pop`` operation.
|
||||||
q.pop()
|
q.pop()
|
||||||
|
|
||||||
proc `$`*[T](q: Queue[T]): string =
|
proc `$`*[T](q: Queue[T]): string =
|
||||||
## turns a queue into its string representation.
|
## Turn a queue into its string representation.
|
||||||
result = "["
|
result = "["
|
||||||
for x in q:
|
for x in q:
|
||||||
if result.len > 1: result.add(", ")
|
if result.len > 1: result.add(", ")
|
||||||
|
|
@ -202,3 +239,35 @@ when isMainModule:
|
||||||
assert false
|
assert false
|
||||||
except IndexError:
|
except IndexError:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
try:
|
||||||
|
assert q.len == 4
|
||||||
|
for i in 0 ..< 5: q.pop()
|
||||||
|
assert false
|
||||||
|
except IndexError:
|
||||||
|
discard
|
||||||
|
|
||||||
|
# Similar to proc from the documentation example
|
||||||
|
proc foo(a, b: Positive) = # assume random positive values for `a` and `b`.
|
||||||
|
var q = initQueue[int]()
|
||||||
|
assert q.len == 0
|
||||||
|
for i in 1 .. a: q.add i
|
||||||
|
|
||||||
|
if b < q.len: # checking before indexed access.
|
||||||
|
assert q[b] == b + 1
|
||||||
|
|
||||||
|
# The following two lines don't need any checking on access due to the logic
|
||||||
|
# of the program, but that would not be the case if `a` could be 0.
|
||||||
|
assert q.front == 1
|
||||||
|
assert q.back == a
|
||||||
|
|
||||||
|
while q.len > 0: # checking if the queue is empty
|
||||||
|
assert q.pop() > 0
|
||||||
|
|
||||||
|
#foo(0,0)
|
||||||
|
foo(8,5)
|
||||||
|
foo(10,9)
|
||||||
|
foo(1,1)
|
||||||
|
foo(2,1)
|
||||||
|
foo(1,5)
|
||||||
|
foo(3,2)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue