# Any suggestions on handling common problem destructuring of
# array/seq into a list of named fields?
# (This is quite common when parsing CSV's and the like)
import strutils
var first, last, phone: string # usually many more fields
# Given an input source with an array or seq of fields:
var fieldList = ["John", "Smith", "1-416-999-1234"] # usually from an external source
# Is there an more concise way to destructure the list into named fields?
(first, last, phone) = (fieldList[0], fieldList[1], fieldList[2])
# which gets tedious as the number of fields increases especially if there is
# the potential for optional trailing fields.
echo [first, last, phone].join ", "
# Ideally something like
# (first, last, phone ...) = (fieldList)
# would be nice but obviously conflicts with tuple assignment semantics.
# I suppose I could define object type with a constructor taking an openArray:
type Person = object
first, last, phone: string # Again... Usually many more fields
proc initPerson(fieldList: openArray[string]): Person =
result = Person()
if fieldList.len > 0 : result.first = fieldList[0]
if fieldList.len > 1 : result.last = fieldList[1]
if fieldList.len > 2 : result.phone = fieldList[2]
# ...
echo initPerson ["John", "Smith", "1-416-999-1234"]
echo initPerson ["Mary ", "Brown"]
# But it would be nice to avoid the type and constructor boilerplate for trivial cases.
# Probably there some macro exists which handles such simple destructering.
# Any suggestions/pointers would be appreciated.
This might get you started:
import std/macros
macro toLets[T](vals: openArray[T], vars: varargs[untyped]): untyped =
result = newStmtList()
for i, v in vars: # change| to const or var
result.add(quote do: (let `v` = `vals`[`i`]))
var fieldList = [1, 2, 3]
fieldList.toLets a, b, c
echo a, b, c
However, it may be even simpler to just define a bunch of names for your column indices and use that directly. E.g.,
import std/macros
macro mkNums(vars: varargs[untyped]): untyped =
result = newStmtList()
for i, v in vars:
result.add(quote do: (const `v` = `i`))
mkNums first, last, phone
echo first, " ", last, " ", phone
# echo field[last], ",", field[first]
Note that when processing CSV it is more efficient to not create a new seq[string] for each row, but rather iterate over the fields anyway (e.g. with the strutils.split iterator called by for not the proc). This is the most recent thread on that topic, but it comes up a lot. So, I thought I might try to get ahead of it. ;-) If you run into trouble you might do some Forum searches.
You might also look at the code generated by my row processor rp which is a hybrid approach: reusing the same seq[] object for each row, but still having one so you can reference s[last] (though that assumes your input file has column headers). You could also preprocess all your data into pre-parsed numeric binary files if you need to operate over them many times as in nio or probably load it into a database and probably 3 ways I'm neglecting to mention. There are really many ways to skin these cats... :-) Hopefully some of this helps you.
If you dont need to reassign the unpacked vars you can use := from fusion/matching
import fusion/matching
var fieldList = ["John", "Smith", "1-416-999-1234"]
[@first, @last, @phone] := fieldList
Thanks cblake & choltreppe:
I went with a slightly tailored version of cblake's first macro.
macro to[T](values: openArray[T], fieldNames: varargs[untyped]): untyped =
result = newStmtList()
for index, fieldName in fieldNames:
result.add(quote do:
(`fieldName` = (if `index` < `values`.len: `values`[`index`] else: "")))
...
for line in lines:
line.split('\t').to title, pages, author # ... and lots more fieldnames