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:
ReneSac 2016-06-15 18:19:51 -03:00
commit dac4826483

View file

@ -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)