diff --git a/lib/pure/collections/deques.nim b/lib/pure/collections/deques.nim index c4eb5331ecdb..db2daa5007b5 100644 --- a/lib/pure/collections/deques.nim +++ b/lib/pure/collections/deques.nim @@ -19,7 +19,7 @@ ## ## As such, a check to see if the deque is empty is needed before any ## access, unless your program logic guarantees it indirectly. - +## runnableExamples: var a = [10, 20, 30, 40].toDeque @@ -51,46 +51,55 @@ runnableExamples: import std/private/since -import math +import std / [math, hashes] type Deque*[T] = object ## A double-ended queue backed with a ringed `seq` buffer. ## - ## To initialize an empty deque, - ## use the `initDeque proc <#initDeque,int>`_. + ## To initialize an empty deque with a given capacity use + ## `initDeque proc <#initDeque,int>`_. data: seq[T] head, tail, count, mask: int const - defaultInitialSize* = 4 + nimDequeDefaultInitialCapacity* {.intdefine.} = nimDefaultInitialCapacity -template initImpl(result: typed, initialSize: int) = - let correctSize = nextPowerOfTwo(initialSize) - result.mask = correctSize - 1 - newSeq(result.data, correctSize) +template initImpl(result: typed, initialCapacity: int) = + assert isPowerOfTwo(initialCapacity) + result.mask = initialCapacity - 1 + newSeq(result.data, initialCapacity) template checkIfInitialized(deq: typed) = - when compiles(defaultInitialSize): + when declared(nimDequeDefaultInitialCapacity): if deq.mask == 0: - initImpl(deq, defaultInitialSize) + initImpl(deq, nimDequeDefaultInitialCapacity) -proc initDeque*[T](initialSize: int = defaultInitialSize): Deque[T] = - ## Creates a new empty deque. +proc initDeque*[T](initialCapacity: int = nimDequeDefaultInitialCapacity): Deque[T] = + ## Creates a new empty deque with a given capacity. An implicitly defined + ## deque will have a capacity of 0 and be grown to fit elements on the first + ## data insertion. ## - ## Optionally, the initial capacity can be reserved via `initialSize` - ## as a performance optimization - ## (default: `defaultInitialSize <#defaultInitialSize>`_). - ## The length of a newly created deque will still be 0. + ## Optionally, the initial capacity can be reserved via `initialCapacity` + ## as a performance optimization. The length of a newly created deque will + ## still be 0. + ## + ## `initialCapacity` must be a power of two (default: 4). + ## If you need to accept runtime values for this you could use the + ## `nextPowerOfTwo proc`_ from the + ## `math module`_. ## ## **See also:** ## * `toDeque proc <#toDeque,openArray[T]>`_ - result.initImpl(initialSize) + result.initImpl(initialCapacity) proc len*[T](deq: Deque[T]): int {.inline.} = - ## Returns the number of elements of `deq`. + ## Returns the number of elements in the `deq`. result = deq.count +template high*[T](deq: Deque[T]): int = + deq.len - 1 + template emptyCheck(deq) = # Bounds check for the regular deque access. when compileOption("boundChecks"): @@ -165,7 +174,7 @@ proc `[]`*[T](deq: var Deque[T], i: BackwardsIndex): var T {.inline.} = assert a[^1] == 51 xBoundsCheck(deq, deq.len - int(i)) - return deq[deq.len - int(i)] + return deq.data[(deq.head + (deq.len - int(i))) and deq.mask] proc `[]=`*[T](deq: var Deque[T], i: BackwardsIndex, x: sink T) {.inline.} = ## Sets the backwards indexed `i`-th element of `deq` to `x`. @@ -179,7 +188,7 @@ proc `[]=`*[T](deq: var Deque[T], i: BackwardsIndex, x: sink T) {.inline.} = checkIfInitialized(deq) xBoundsCheck(deq, deq.len - int(i)) - deq[deq.len - int(i)] = x + deq.data[(deq.head + (deq.len - int(i))) and deq.mask] = x iterator items*[T](deq: Deque[T]): lent T = ## Yields every element of `deq`. @@ -263,7 +272,7 @@ proc addFirst*[T](deq: var Deque[T], item: sink T) = ## **See also:** ## * `addLast proc <#addLast,Deque[T],sinkT>`_ runnableExamples: - var a = initDeque[int]() + var a: Deque[int] for i in 1 .. 5: a.addFirst(10 * i) assert $a == "[50, 40, 30, 20, 10]" @@ -279,7 +288,7 @@ proc addLast*[T](deq: var Deque[T], item: sink T) = ## **See also:** ## * `addFirst proc <#addFirst,Deque[T],sinkT>`_ runnableExamples: - var a = initDeque[int]() + var a: Deque[int] for i in 1 .. 5: a.addLast(10 * i) assert $a == "[10, 20, 30, 40, 50]" @@ -295,13 +304,26 @@ proc toDeque*[T](x: openArray[T]): Deque[T] {.since: (1, 3).} = ## **See also:** ## * `initDeque proc <#initDeque,int>`_ runnableExamples: - let a = toDeque([7, 8, 9]) - assert len(a) == 3 - assert $a == "[7, 8, 9]" - - result.initImpl(x.len) - for item in items(x): - result.addLast(item) + var x = @[10, 20, 30].toDeque + assert x.len == 3 + assert x[0] == 10 + x.addFirst 0 + x.addLast 40 + assert $x == "[0, 10, 20, 30, 40]" + result.head = 0 + result.count = x.len + result.tail = x.len + let n = + if x.len.isPowerOfTwo: + result.data.add x + x.len + else: + let n = x.len.nextPowerOfTwo + result.data = newSeqOfCap[T](n) + result.data.add x + result.data.setLen n + n + result.mask = n - 1 proc peekFirst*[T](deq: Deque[T]): lent T {.inline.} = ## Returns the first element of `deq`, but does not remove it from the deque. @@ -420,7 +442,7 @@ proc shrink*[T](deq: var Deque[T], fromFirst = 0, fromLast = 0) = ## `fromLast` elements from the back. ## ## If the supplied number of elements exceeds the total number of elements - ## in the deque, the deque will remain empty. + ## in the deque, the deque will be emptied entirely. ## ## **See also:** ## * `clear proc <#clear,Deque[T]>`_ @@ -457,3 +479,26 @@ proc `$`*[T](deq: Deque[T]): string = if result.len > 1: result.add(", ") result.addQuoted(x) result.add("]") + +proc hash*[A](d: Deque[A]): Hash = + ## Hashing of Deque. + runnableExamples: + var + x: Deque[int] + y: Deque[int] + + for i in 1..5: + x.addLast(i*10) + for i in -5..10: + y.addLast(i*10) + y.shrink(fromFirst = 6, fromLast = 5) + assert hash(x) == hash(y) + for h in d: + result = result !& hash(h) + result = !$result + +proc `==`*[A](a, b: Deque[A]): bool = + if a.len == b.len: + for i in 0..a.high: + if not a[i] == b[i]: return false + return true diff --git a/lib/pure/collections/setimpl.nim b/lib/pure/collections/setimpl.nim index 7ebd2276047d..cab2859764a7 100644 --- a/lib/pure/collections/setimpl.nim +++ b/lib/pure/collections/setimpl.nim @@ -25,7 +25,7 @@ template initImpl(s: typed, size: int) = template rawInsertImpl() {.dirty.} = if data.len == 0: - initImpl(s, defaultInitialSize) + initImpl(s, nimSetDefaultInitialCapacity) data[h].key = key data[h].hcode = hc @@ -44,7 +44,7 @@ proc enlarge[A](s: var HashSet[A]) = template inclImpl() {.dirty.} = if s.data.len == 0: - initImpl(s, defaultInitialSize) + initImpl(s, nimSetDefaultInitialCapacity) var hc: Hash var index = rawGet(s, key, hc) if index < 0: @@ -56,7 +56,7 @@ template inclImpl() {.dirty.} = template containsOrInclImpl() {.dirty.} = if s.data.len == 0: - initImpl(s, defaultInitialSize) + initImpl(s, nimSetDefaultInitialCapacity) var hc: Hash var index = rawGet(s, key, hc) if index >= 0: diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim index 114e4582a905..d6d1aa9e0459 100644 --- a/lib/pure/collections/sets.nim +++ b/lib/pure/collections/sets.nim @@ -81,7 +81,7 @@ type ## Type union representing `HashSet` or `OrderedSet`. const - defaultInitialSize* = 64 + nimSetDefaultInitialCapacity* {.intdefine.} = nimDefaultInitialCapacity * 16 include setimpl @@ -90,7 +90,7 @@ include setimpl # --------------------------------------------------------------------- -proc init*[A](s: var HashSet[A], initialSize = defaultInitialSize) = +proc init*[A](s: var HashSet[A], initialSize = nimSetDefaultInitialCapacity) = ## Initializes a hash set. ## ## Starting from Nim v0.20, sets are initialized by default and it is @@ -109,8 +109,8 @@ proc init*[A](s: var HashSet[A], initialSize = defaultInitialSize) = initImpl(s, initialSize) -proc initHashSet*[A](initialSize = defaultInitialSize): HashSet[A] = - ## Wrapper around `init proc <#init,HashSet[A]>`_ for initialization of +proc initHashSet*[A](initialSize = nimSetDefaultInitialCapacity): HashSet[A] = + ## Wrapper around `init proc <#init,HashSet[A],int>`_ for initialization of ## hash sets. ## ## Returns an empty hash set you can assign directly in `var` blocks in a @@ -591,8 +591,7 @@ proc `$`*[A](s: HashSet[A]): string = ## # --> {no, esc'aping, is " provided} dollarImpl() - -proc initSet*[A](initialSize = defaultInitialSize): HashSet[A] {.deprecated: +proc initSet*[A](initialSize = nimSetDefaultInitialCapacity): HashSet[A] {.deprecated: "Deprecated since v0.20, use 'initHashSet'".} = initHashSet[A](initialSize) proc toSet*[A](keys: openArray[A]): HashSet[A] {.deprecated: @@ -627,7 +626,7 @@ template forAllOrderedPairs(yieldStmt: untyped) {.dirty.} = h = nxt -proc init*[A](s: var OrderedSet[A], initialSize = defaultInitialSize) = +proc init*[A](s: var OrderedSet[A], initialSize = nimSetDefaultInitialCapacity) = ## Initializes an ordered hash set. ## ## Starting from Nim v0.20, sets are initialized by default and it is @@ -646,8 +645,8 @@ proc init*[A](s: var OrderedSet[A], initialSize = defaultInitialSize) = initImpl(s, initialSize) -proc initOrderedSet*[A](initialSize = defaultInitialSize): OrderedSet[A] = - ## Wrapper around `init proc <#init,OrderedSet[A]>`_ for initialization of +proc initOrderedSet*[A](initialSize = nimSetDefaultInitialCapacity): OrderedSet[A] = + ## Wrapper around `init proc <#init,OrderedSet[A],int>`_ for initialization of ## ordered hash sets. ## ## Returns an empty ordered hash set you can assign directly in `var` blocks diff --git a/lib/pure/collections/tableimpl.nim b/lib/pure/collections/tableimpl.nim index 27c33917750c..068c8ace52b0 100644 --- a/lib/pure/collections/tableimpl.nim +++ b/lib/pure/collections/tableimpl.nim @@ -31,9 +31,9 @@ proc rawInsert[X, A, B](t: var X, data: var KeyValuePairSeq[A, B], rawInsertImpl() template checkIfInitialized() = - when compiles(defaultInitialSize): + when declared(nimTableDefaultInitialCapacity): if t.dataLen == 0: - initImpl(t, defaultInitialSize) + initImpl(t, nimTableDefaultInitialCapacity) template addImpl(enlarge) {.dirty.} = checkIfInitialized() @@ -171,7 +171,7 @@ template initImpl(result: typed, size: int) = result.last = -1 template insertImpl() = # for CountTable - if t.dataLen == 0: initImpl(t, defaultInitialSize) + if t.dataLen == 0: initImpl(t, nimTableDefaultInitialCapacity) if mustRehash(t): enlarge(t) ctRawInsert(t, t.data, key, val) inc(t.counter) diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 00f71ef1d24b..ef59fee66f16 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -217,7 +217,7 @@ type ## <#newTable>`_. const - defaultInitialSize* = 32 + nimTableDefaultInitialCapacity* {.intdefine.} = nimDefaultInitialCapacity * 8 # ------------------------------ helpers --------------------------------- @@ -266,7 +266,7 @@ proc enlarge[A, B](t: var Table[A, B]) = # ------------------------------ Table ------------------------------ # ------------------------------------------------------------------- -proc initTable*[A, B](initialSize = defaultInitialSize): Table[A, B] = +proc initTable*[A, B](initialSize = nimTableDefaultInitialCapacity): Table[A, B] = ## Creates a new hash table that is empty. ## ## Starting from Nim v0.20, tables are initialized by default and it is @@ -811,7 +811,7 @@ iterator allValues*[A, B](t: Table[A, B]; key: A): B {.deprecated: # ------------------------------------------------------------------- -proc newTable*[A, B](initialSize = defaultInitialSize): TableRef[A, B] = +proc newTable*[A, B](initialSize = nimTableDefaultInitialCapacity): TableRef[A, B] = ## Creates a new ref hash table that is empty. ## ## See also: @@ -1304,7 +1304,7 @@ template forAllOrderedPairs(yieldStmt: untyped) {.dirty.} = # ---------------------------------------------------------------------- -proc initOrderedTable*[A, B](initialSize = defaultInitialSize): OrderedTable[A, B] = +proc initOrderedTable*[A, B](initialSize = nimTableDefaultInitialCapacity): OrderedTable[A, B] = ## Creates a new ordered hash table that is empty. ## ## Starting from Nim v0.20, tables are initialized by default and it is @@ -1808,7 +1808,7 @@ iterator mvalues*[A, B](t: var OrderedTable[A, B]): var B = # --------------------------- OrderedTableRef ------------------------------- # --------------------------------------------------------------------------- -proc newOrderedTable*[A, B](initialSize = defaultInitialSize): OrderedTableRef[A, B] = +proc newOrderedTable*[A, B](initialSize = nimTableDefaultInitialCapacity): OrderedTableRef[A, B] = ## Creates a new ordered ref hash table that is empty. ## ## See also: @@ -2270,7 +2270,7 @@ proc inc*[A](t: var CountTable[A], key: A, val = 1) # ---------------------------------------------------------------------- -proc initCountTable*[A](initialSize = defaultInitialSize): CountTable[A] = +proc initCountTable*[A](initialSize = nimTableDefaultInitialCapacity): CountTable[A] = ## Creates a new count table that is empty. ## ## Starting from Nim v0.20, tables are initialized by default and it is @@ -2628,7 +2628,7 @@ iterator mvalues*[A](t: var CountTable[A]): var int = proc inc*[A](t: CountTableRef[A], key: A, val = 1) -proc newCountTable*[A](initialSize = defaultInitialSize): CountTableRef[A] = +proc newCountTable*[A](initialSize = nimTableDefaultInitialCapacity): CountTableRef[A] = ## Creates a new ref count table that is empty. ## ## See also: diff --git a/lib/system.nim b/lib/system.nim index 4080fee06470..57ad82940db4 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1139,6 +1139,12 @@ const # emit this flag # for string literals, it allows for some optimizations. + nimDefaultInitialCapacity* {.intdefine.} = 4 + ## Default initial capacity for various containers + + #defaultInitialSize* {.deprecated: "Use nimDefaultInitialCapacity instead".} = nimDefaultInitialCapacity + defaultInitialSize* = nimDefaultInitialCapacity + const hasThreadSupport = compileOption("threads") and not defined(nimscript) hasSharedHeap = defined(boehmgc) or defined(gogc) # don't share heaps; every thread has its own diff --git a/tests/collections/tcollections.nim b/tests/collections/tcollections.nim index 7677f7c1a2be..5ca01f61d98a 100644 --- a/tests/collections/tcollections.nim +++ b/tests/collections/tcollections.nim @@ -4,7 +4,7 @@ discard """ # see also: tdeques, tlists, tcritbits -import sets, tables, sequtils +import sets, tables, sequtils, deques block tapply: var x = @[1, 2, 3] @@ -13,6 +13,19 @@ block tapply: x.applyIt(it+5000) doAssert x == @[5111, 5112, 5113] +block tdeques: + proc index(self: Deque[int], idx: Natural): int = + self[idx] + + proc main = + var testDeque: Deque[int] + testDeque.addFirst(1) + assert testDeque.index(0) == 1 + assert testDeque[^1] == 1 + + main() + + block tmapit: var x = @[1, 2, 3] # This mapIt call will run with preallocation because ``len`` is available. diff --git a/tests/collections/thashsets.nim b/tests/collections/thashsets.nim index 359eaa51ec5a..8797caa10b72 100644 --- a/tests/collections/thashsets.nim +++ b/tests/collections/thashsets.nim @@ -3,9 +3,9 @@ import sets, hashes, algorithm block setEquality: var - a = initHashSet[int]() - b = initHashSet[int]() - c = initHashSet[string]() + a: HashSet[int] + b: HashSet[int] + c: HashSet[string] for i in 0..5: a.incl(i) for i in 1..6: b.incl(i) @@ -16,27 +16,27 @@ block setEquality: block setsContainingTuples: - var set = initHashSet[tuple[i: int, i64: int64, f: float]]() + var set: HashSet[tuple[i: int, i64: int64, f: float]] set.incl( (i: 123, i64: 123'i64, f: 3.14) ) doAssert set.contains( (i: 123, i64: 123'i64, f: 3.14) ) doAssert( not set.contains( (i: 456, i64: 789'i64, f: 2.78) ) ) block setWithTuplesWithSeqs: - var s = initHashSet[tuple[s: seq[int]]]() + var s: HashSet[tuple[s: seq[int]]] s.incl( (s: @[1, 2, 3]) ) doAssert s.contains( (s: @[1, 2, 3]) ) doAssert( not s.contains((s: @[4, 5, 6])) ) block setWithSequences: - var s = initHashSet[seq[int]]() + var s: HashSet[seq[int]] s.incl( @[1, 2, 3] ) doAssert s.contains(@[1, 2, 3]) doAssert( not s.contains(@[4, 5, 6]) ) block setClearWorked: - var s = initHashSet[char]() + var s: HashSet[char] for c in "this is a test": s.incl(c) diff --git a/tests/stdlib/tdeques.nim b/tests/stdlib/tdeques.nim index 03e951fce476..11f6c684506d 100644 --- a/tests/stdlib/tdeques.nim +++ b/tests/stdlib/tdeques.nim @@ -183,6 +183,14 @@ proc main() = clear(a) doAssert len(a) == 0 + block: + var a = @[10, 20, 30].toDeque + doAssert a.len == 3 + doAssert a[0] == 10 + a.addFirst 0 + a.addLast 40 + doAssert $a == "[0, 10, 20, 30, 40]" + static: main() main()