faster CountTable sort(), optional SortOrder (#11010)
* use existing sort for CountTable, and add SortOrder options to CountTable, OrderedTable sort(s) * add some tests, runnables, etc. * fix runnable imports
This commit is contained in:
parent
374a85bb9c
commit
01f09567c4
2 changed files with 60 additions and 42 deletions
|
|
@ -216,7 +216,7 @@
|
||||||
## * `hashes module<hashes.html>`_ for helper functions for hashing
|
## * `hashes module<hashes.html>`_ for helper functions for hashing
|
||||||
|
|
||||||
|
|
||||||
import hashes, math
|
import hashes, math, algorithm
|
||||||
|
|
||||||
include "system/inclrtl"
|
include "system/inclrtl"
|
||||||
|
|
||||||
|
|
@ -1467,7 +1467,7 @@ proc clear*[A, B](t: var OrderedTable[A, B]) =
|
||||||
t.first = -1
|
t.first = -1
|
||||||
t.last = -1
|
t.last = -1
|
||||||
|
|
||||||
proc sort*[A, B](t: var OrderedTable[A, B], cmp: proc (x,y: (A, B)): int) =
|
proc sort*[A, B](t: var OrderedTable[A, B], cmp: proc (x,y: (A, B)): int, order = SortOrder.Ascending) =
|
||||||
## Sorts ``t`` according to the function ``cmp``.
|
## Sorts ``t`` according to the function ``cmp``.
|
||||||
##
|
##
|
||||||
## This modifies the internal list
|
## This modifies the internal list
|
||||||
|
|
@ -1475,12 +1475,15 @@ proc sort*[A, B](t: var OrderedTable[A, B], cmp: proc (x,y: (A, B)): int) =
|
||||||
## call but key lookup and insertions remain possible after ``sort`` (in
|
## call but key lookup and insertions remain possible after ``sort`` (in
|
||||||
## contrast to the `sort proc<#sort,CountTable[A]>`_ for count tables).
|
## contrast to the `sort proc<#sort,CountTable[A]>`_ for count tables).
|
||||||
runnableExamples:
|
runnableExamples:
|
||||||
|
import algorithm
|
||||||
var a = initOrderedTable[char, int]()
|
var a = initOrderedTable[char, int]()
|
||||||
for i, c in "cab":
|
for i, c in "cab":
|
||||||
a[c] = 10*i
|
a[c] = 10*i
|
||||||
doAssert a == {'c': 0, 'a': 10, 'b': 20}.toOrderedTable
|
doAssert a == {'c': 0, 'a': 10, 'b': 20}.toOrderedTable
|
||||||
a.sort(system.cmp)
|
a.sort(system.cmp)
|
||||||
doAssert a == {'a': 10, 'b': 20, 'c': 0}.toOrderedTable
|
doAssert a == {'a': 10, 'b': 20, 'c': 0}.toOrderedTable
|
||||||
|
a.sort(system.cmp, order=SortOrder.Descending)
|
||||||
|
doAssert a == {'c': 0, 'b': 20, 'a': 10}.toOrderedTable
|
||||||
|
|
||||||
var list = t.first
|
var list = t.first
|
||||||
var
|
var
|
||||||
|
|
@ -1508,7 +1511,7 @@ proc sort*[A, B](t: var OrderedTable[A, B], cmp: proc (x,y: (A, B)): int) =
|
||||||
elif qsize == 0 or q < 0:
|
elif qsize == 0 or q < 0:
|
||||||
e = p; p = t.data[p].next; dec(psize)
|
e = p; p = t.data[p].next; dec(psize)
|
||||||
elif cmp((t.data[p].key, t.data[p].val),
|
elif cmp((t.data[p].key, t.data[p].val),
|
||||||
(t.data[q].key, t.data[q].val)) <= 0:
|
(t.data[q].key, t.data[q].val)) * order <= 0:
|
||||||
e = p; p = t.data[p].next; dec(psize)
|
e = p; p = t.data[p].next; dec(psize)
|
||||||
else:
|
else:
|
||||||
e = q; q = t.data[q].next; dec(qsize)
|
e = q; q = t.data[q].next; dec(qsize)
|
||||||
|
|
@ -1875,7 +1878,7 @@ proc clear*[A, B](t: var OrderedTableRef[A, B]) =
|
||||||
doAssert len(a) == 0
|
doAssert len(a) == 0
|
||||||
clear(t[])
|
clear(t[])
|
||||||
|
|
||||||
proc sort*[A, B](t: OrderedTableRef[A, B], cmp: proc (x,y: (A, B)): int) =
|
proc sort*[A, B](t: OrderedTableRef[A, B], cmp: proc (x,y: (A, B)): int, order = SortOrder.Ascending) =
|
||||||
## Sorts ``t`` according to the function ``cmp``.
|
## Sorts ``t`` according to the function ``cmp``.
|
||||||
##
|
##
|
||||||
## This modifies the internal list
|
## This modifies the internal list
|
||||||
|
|
@ -1883,13 +1886,16 @@ proc sort*[A, B](t: OrderedTableRef[A, B], cmp: proc (x,y: (A, B)): int) =
|
||||||
## call but key lookup and insertions remain possible after ``sort`` (in
|
## call but key lookup and insertions remain possible after ``sort`` (in
|
||||||
## contrast to the `sort proc<#sort,CountTableRef[A]>`_ for count tables).
|
## contrast to the `sort proc<#sort,CountTableRef[A]>`_ for count tables).
|
||||||
runnableExamples:
|
runnableExamples:
|
||||||
|
import algorithm
|
||||||
var a = newOrderedTable[char, int]()
|
var a = newOrderedTable[char, int]()
|
||||||
for i, c in "cab":
|
for i, c in "cab":
|
||||||
a[c] = 10*i
|
a[c] = 10*i
|
||||||
doAssert a == {'c': 0, 'a': 10, 'b': 20}.newOrderedTable
|
doAssert a == {'c': 0, 'a': 10, 'b': 20}.newOrderedTable
|
||||||
a.sort(system.cmp)
|
a.sort(system.cmp)
|
||||||
doAssert a == {'a': 10, 'b': 20, 'c': 0}.newOrderedTable
|
doAssert a == {'a': 10, 'b': 20, 'c': 0}.newOrderedTable
|
||||||
t[].sort(cmp)
|
a.sort(system.cmp, order=SortOrder.Descending)
|
||||||
|
doAssert a == {'c': 0, 'b': 20, 'a': 10}.newOrderedTable
|
||||||
|
t[].sort(cmp, order=order)
|
||||||
|
|
||||||
proc `$`*[A, B](t: OrderedTableRef[A, B]): string =
|
proc `$`*[A, B](t: OrderedTableRef[A, B]): string =
|
||||||
## The ``$`` operator for hash tables. Used internally when calling `echo`
|
## The ``$`` operator for hash tables. Used internally when calling `echo`
|
||||||
|
|
@ -2199,30 +2205,27 @@ proc clear*[A](t: var CountTable[A]) =
|
||||||
## Resets the table so that it is empty.
|
## Resets the table so that it is empty.
|
||||||
clearImpl()
|
clearImpl()
|
||||||
|
|
||||||
proc sort*[A](t: var CountTable[A]) =
|
func ctCmp[T](a, b: tuple[key: T, val: int]): int =
|
||||||
## Sorts the count table so that the entry with the highest counter comes
|
result = system.cmp(a.val, b.val)
|
||||||
## first.
|
|
||||||
|
proc sort*[A](t: var CountTable[A], order = SortOrder.Descending) =
|
||||||
|
## Sorts the count table so that, by default, the entry with the
|
||||||
|
## highest counter comes first.
|
||||||
##
|
##
|
||||||
## **This is destructive! You must not modify ``t`` afterwards!**
|
## **This is destructive! You must not modify ``t`` afterwards!**
|
||||||
##
|
##
|
||||||
## You can use the iterators `pairs<#pairs.i,CountTable[A]>`_,
|
## You can use the iterators `pairs<#pairs.i,CountTable[A]>`_,
|
||||||
## `keys<#keys.i,CountTable[A]>`_, and `values<#values.i,CountTable[A]>`_
|
## `keys<#keys.i,CountTable[A]>`_, and `values<#values.i,CountTable[A]>`_
|
||||||
## to iterate over ``t`` in the sorted order.
|
## to iterate over ``t`` in the sorted order.
|
||||||
|
runnableExamples:
|
||||||
# we use shellsort here; fast enough and simple
|
import algorithm, sequtils
|
||||||
var h = 1
|
var a = toCountTable("abracadabra")
|
||||||
while true:
|
doAssert a == "aaaaabbrrcd".toCountTable
|
||||||
h = 3 * h + 1
|
a.sort()
|
||||||
if h >= high(t.data): break
|
doAssert toSeq(a.values) == @[5, 2, 2, 1, 1]
|
||||||
while true:
|
a.sort(SortOrder.Ascending)
|
||||||
h = h div 3
|
doAssert toSeq(a.values) == @[1, 1, 2, 2, 5]
|
||||||
for i in countup(h, high(t.data)):
|
t.data.sort(cmp=ctCmp, order=order)
|
||||||
var j = i
|
|
||||||
while t.data[j-h].val <= t.data[j].val:
|
|
||||||
swap(t.data[j], t.data[j-h])
|
|
||||||
j = j-h
|
|
||||||
if j < h: break
|
|
||||||
if h == 1: break
|
|
||||||
|
|
||||||
proc merge*[A](s: var CountTable[A], t: CountTable[A]) =
|
proc merge*[A](s: var CountTable[A], t: CountTable[A]) =
|
||||||
## Merges the second table into the first one (must be declared as `var`).
|
## Merges the second table into the first one (must be declared as `var`).
|
||||||
|
|
@ -2467,16 +2470,16 @@ proc clear*[A](t: CountTableRef[A]) =
|
||||||
## Resets the table so that it is empty.
|
## Resets the table so that it is empty.
|
||||||
clearImpl()
|
clearImpl()
|
||||||
|
|
||||||
proc sort*[A](t: CountTableRef[A]) =
|
proc sort*[A](t: CountTableRef[A], order = SortOrder.Descending) =
|
||||||
## Sorts the count table so that the entry with the highest counter comes
|
## Sorts the count table so that, by default, the entry with the
|
||||||
## first.
|
## highest counter comes first.
|
||||||
##
|
##
|
||||||
## **This is destructive! You must not modify `t` afterwards!**
|
## **This is destructive! You must not modify `t` afterwards!**
|
||||||
##
|
##
|
||||||
## You can use the iterators `pairs<#pairs.i,CountTableRef[A]>`_,
|
## You can use the iterators `pairs<#pairs.i,CountTableRef[A]>`_,
|
||||||
## `keys<#keys.i,CountTableRef[A]>`_, and `values<#values.i,CountTableRef[A]>`_
|
## `keys<#keys.i,CountTableRef[A]>`_, and `values<#values.i,CountTableRef[A]>`_
|
||||||
## to iterate over ``t`` in the sorted order.
|
## to iterate over ``t`` in the sorted order.
|
||||||
t[].sort
|
t[].sort(order=order)
|
||||||
|
|
||||||
proc merge*[A](s, t: CountTableRef[A]) =
|
proc merge*[A](s, t: CountTableRef[A]) =
|
||||||
## Merges the second table into the first one.
|
## Merges the second table into the first one.
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ And we get here
|
||||||
'''
|
'''
|
||||||
joinable: false
|
joinable: false
|
||||||
"""
|
"""
|
||||||
import hashes, sequtils, tables
|
import hashes, sequtils, tables, algorithm
|
||||||
|
|
||||||
# test should not be joined because it takes too long.
|
# test should not be joined because it takes too long.
|
||||||
block tableadds:
|
block tableadds:
|
||||||
|
|
@ -274,22 +274,30 @@ block tablesref:
|
||||||
block countTableTest1:
|
block countTableTest1:
|
||||||
var s = data.toTable
|
var s = data.toTable
|
||||||
var t = newCountTable[string]()
|
var t = newCountTable[string]()
|
||||||
for k in s.keys: t.inc(k)
|
var r = newCountTable[string]()
|
||||||
for k in t.keys: assert t[k] == 1
|
for x in [t, r]:
|
||||||
t.inc("90", 3)
|
for k in s.keys:
|
||||||
t.inc("12", 2)
|
x.inc(k)
|
||||||
t.inc("34", 1)
|
assert x[k] == 1
|
||||||
|
x.inc("90", 3)
|
||||||
|
x.inc("12", 2)
|
||||||
|
x.inc("34", 1)
|
||||||
assert t.largest()[0] == "90"
|
assert t.largest()[0] == "90"
|
||||||
|
|
||||||
t.sort()
|
t.sort()
|
||||||
var i = 0
|
r.sort(SortOrder.Ascending)
|
||||||
for k, v in t.pairs:
|
var ps1 = toSeq t.pairs
|
||||||
case i
|
var ps2 = toSeq r.pairs
|
||||||
of 0: assert k == "90" and v == 4
|
ps2.reverse()
|
||||||
of 1: assert k == "12" and v == 3
|
for ps in [ps1, ps2]:
|
||||||
of 2: assert k == "34" and v == 2
|
var i = 0
|
||||||
else: break
|
for (k, v) in ps:
|
||||||
inc i
|
case i
|
||||||
|
of 0: assert k == "90" and v == 4
|
||||||
|
of 1: assert k == "12" and v == 3
|
||||||
|
of 2: assert k == "34" and v == 2
|
||||||
|
else: break
|
||||||
|
inc i
|
||||||
|
|
||||||
block SyntaxTest:
|
block SyntaxTest:
|
||||||
var x = newTable[int, string]({:})
|
var x = newTable[int, string]({:})
|
||||||
|
|
@ -308,13 +316,20 @@ block tablesref:
|
||||||
var t = newOrderedTable[string, int](2)
|
var t = newOrderedTable[string, int](2)
|
||||||
for key, val in items(data): t[key] = val
|
for key, val in items(data): t[key] = val
|
||||||
for key, val in items(data): assert t[key] == val
|
for key, val in items(data): assert t[key] == val
|
||||||
t.sort(proc (x, y: tuple[key: string, val: int]): int = cmp(x.key, y.key))
|
proc cmper(x, y: tuple[key: string, val: int]): int = cmp(x.key, y.key)
|
||||||
|
t.sort(cmper)
|
||||||
var i = 0
|
var i = 0
|
||||||
# `pairs` needs to yield in sorted order:
|
# `pairs` needs to yield in sorted order:
|
||||||
for key, val in pairs(t):
|
for key, val in pairs(t):
|
||||||
doAssert key == sorteddata[i][0]
|
doAssert key == sorteddata[i][0]
|
||||||
doAssert val == sorteddata[i][1]
|
doAssert val == sorteddata[i][1]
|
||||||
inc(i)
|
inc(i)
|
||||||
|
t.sort(cmper, order=SortOrder.Descending)
|
||||||
|
i = 0
|
||||||
|
for key, val in pairs(t):
|
||||||
|
doAssert key == sorteddata[high(data)-i][0]
|
||||||
|
doAssert val == sorteddata[high(data)-i][1]
|
||||||
|
inc(i)
|
||||||
|
|
||||||
# check that lookup still works:
|
# check that lookup still works:
|
||||||
for key, val in pairs(t):
|
for key, val in pairs(t):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue