While these sorts of type system questions _may_ be important for your exact
use case, they may also be _unimportant_. You should be sure of what you need
before you get too tangled up in types.
For example, the following program does the union-find (I think "join root"
expresses the ideas better...) graph algorithm for connected components using
just undirected arc 2-tuples of (likely small) integers:
import tables
template iniTab[K,V](n: auto): auto = initTable[K,V](rightSize(n))
proc root(up: var seq[int], x: int): int {.inline.} =
result = x # Find root defined by parent ==
self
while up[result] != result:
result = up[result]
var x = x # Compress path afterwards
while up[x] != result: #..by doing up[all x in path] <-
result
let up0 = up[x]; up[x] = result; x = up0
proc join(up, sz: var seq[int], x, y: int) {.inline.} =
let x = up.root(x) # Join/union by size
let y = up.root(y)
if x == y: return # x & y are already joined
if sz[x] < sz[y]: # Attach smaller tree..
up[x] = y #..to root of larger
sz[y] += sz[x] # and update size of larger
else: # Mirror case of above
up[y] = x
sz[x] += sz[y]
iterator components*(arcs: openArray[tuple[x, y: int]], nV: int): seq[int] =
## yields connected components given arcs and number of unique vertices
var up = newSeq[int](nV) # vtxId -> parent id
for i in 0 ..< nV: up[i] = i # initial parents all self
var sz = newSeq[int](nV) # vtxId -> sz
for i in 0 ..< nV: sz[i] = 1 # initial sizes all 1
for arc in arcs: # Incorp arcs via
union-find/join-root
join up, sz, arc.x, arc.y # Post loop up.root(i)==component
label
var cs = iniTab[int, seq[int]](nV) # component id -> all members
for i in 0 ..< nV: # for each unique vertex:
cs.mgetOrPut(up.root(i), @[]).add i # update root[id] => members map
for c in cs.values: yield c # Then yield blocks of components
proc vtxId*[T](vi: var Table[T, int]; vn: var seq[T]; vo: T): int
{.inline.} =
## Return a vertex id for maybe-already-seen obj `vo`, updating `vi` &
`vn`.
try : result = vi[vo] # Already known =>
done
except: result = vn.len; vi[vo] = result; vn.add vo # Put into nm->id &
id->nm
when isMainModule:
import cligen
proc conncomp(idelim='\t', odelim="\t") =
## Print connected components of the graph of arcs/edges on stdin.
var vi = iniTab[string, int](4096) # vertex name -> int id number
var vn = newSeqOfCap[string](4096) # vertex int id -> name
var arcs = newSeqOfCap[tuple[x, y: int]](4096)
for ln in lines(stdin): # Parse input, assign vertex ids,
let cs = ln.split(idelim) #..and load up `arcs`.
arcs.add (vtxId(vi, vn, cs[0]), vtxId(vi, vn, cs[1]))
for c in components(arcs, vn.len): # Emit output
for i, e in c: stdout.write vn[e], if i < c.len - 1: odelim else: ""
stdout.write "\n"
dispatch(conncomp, help = { "idelim": "arc delimiter",
"odelim": "in-cluster delimiter" })
Run
Note that `T` in `vtxId[T]` can be anything that can be a `Table` key. The
example program just maps vertex/node names based on `stdin` lines split on
`idelim`, and `vtxId` is only really used in the UI.
Anyway, that is just an example I happened to have lying around that seemed
small enough to just Forum-post to maybe lubricate some creativity in the
solution of your actual problem with simpler type requirements. E.g. as per
your above mentioned concern, `node[idx].someExtraField` may be enough,
depending upon what your ultimate/actual problem is.