What are all the options for modifying the value in a table? As folks familiar with Nim will know, strings and sequences are value types, and thus cannot be copied for modification into a var through the usual mgetOrPut workflow.
Example. The focus is on efficiently modifying values stored in seqs or strings in general, not specifically creating the values or patterns shown in this demonstration.
# Alternative 0
import tables
var tab = newTable[int, seq[int]]()
for _ in 1..10: # simulate pressure
if 0 notin tab: # lookup
tab[0] = newSeq[int](1) # allocation
inc tab[0][0] # lookup, increment
inc tab[0][0] # lookup, increment
assert tab[0][0] == 20
Pros
Cons
# Alternative 1
for _ in 1..10:
inc tab.mgetOrPut(0, newSeq[int](1))[0] # lookup, possible allocation, increment
inc tab[0][0] # lookup, increment
Pros
Cons
# Alternative 2
from std/decls import byaddr
for _ in 1..10:
let s {.byaddr.} = tab.mgetOrPut(0, newSeq[int](1)) # lookup, possible allocation
inc s[0] # increment
inc s[0] # increment
Pros
Cons
# Alternative 3
for _ in 1..10:
let s = addr tab.mgetOrPut(0, newSeq[int](1)) # lookup, possible allocation
inc s[][0] # increment
inc s[][0] # increment
Pros
Cons
These all have different trade-offs with clarity and number of table lookups. Alternative 2 looks the best to me IMO as a nice balance of clarity and efficiency, but I vaguely recall Araq cautioning against using byaddr because it will be deprecated. Personally I find byaddr incredibly helpful in a variety of situations, but that's probably only because view types aren't ironed out yet, or that I try to avoid working with pointers in Nim.
Did I miss other alternatives? What are your arguments for/against each?
the problem with that is that almost always when code like this is used the passed proc will be a closure.
I think the proper solution will be that once views are stable enough an Option[var T] can be returned.
I always use
# Alternative 3
for _ in 1..10:
let s = addr tab.mgetOrPut(0, newSeq[int](1)) # lookup, possible allocation
inc s[][0] # increment
byaddr doesn't have to be deprecated but should be patched to do borrow checking once we have it...
I could be missing something, but maybe worth mention that
var s = addr tab.mgetOrPut(0, newSeq[int](1))
inc s[0]
also already seems to work.. (unsure about borrowing semantics).Ohh, right, when indexing strings and seqs, pointers can be indexed in the raw. That's handy.
@Araq, lol what a tease.
I'm not sure it has anything to do with seq-specialness as this also seems to work (as does changing object to tuple or newTable to initTable):
import tables
type Weight = object
w: int
labels: seq[string]
var tab = newTable[string, Weight](8)
let empty = Weight(w: 0, labels: newSeqOfCap[string](1))
for token in stdin.lines:
var wcell = addr tab.mgetOrPut(token, empty)
wcell.w += 1; wcell.labels.add token
echo tab