I have the following lisp data for which I would like to achieve a specific output. I got this output using @(do (prinl order) (prinl location) ...)
from my TXR parser.
(defvar order '(0 1 2 3 4 5))
(defvar location
'("shape" "shape/rectangle" "shape/square" "shape/rectangle" "shape/rectangle" ""))
(defvar headings
'(("geometer") ("id" "width: cm" "height: cm")
("id" "length: m") ("id" "width: cm" "height: cm")
("angle: °") ("year" "month" "day")))
(defvar values
'(("Alice")
(("1" "13" "15") ("2" "12" "14"))
(("1" "10") ("2" "5") ("3..5" "7")
("6;8" "15;12") ("7" "20") ("9..10" "25;30"))
(("3" "5" "12.2")) ("90") ("2017" "03" "01")))
(defvar type '("meta" "data" "data" "data" "meta" "meta"))
At the end of the day, my desired output are CSV tables
[shape/rectangle]
year,month,day,geometer,angle: °,id,width: cm,height: cm
2017,03,01,90,Alice,1,13,15
2017,03,01,90,Alice,2,12,14
2017,03,01,90,Alice,3,5,12.2
[shape/square]
year,month,day,geometer,id,length: m
2017,03,01,Alice,1,10
2017,03,01,Alice,2,5
2017,03,01,Alice,3,7
2017,03,01,Alice,4,7
2017,03,01,Alice,5,7
2017,03,01,Alice,6,15
2017,03,01,Alice,8,12
2017,03,01,Alice,7,20
2017,03,01,Alice,9,25
2017,03,01,Alice,10,30
I have written some TXR lisp code for decompressing values:
(defun str-range-p (x)
(m^$ #/\d+\.\.\d+/ x))
(defun str-range-expand (x)
[apply range [mapcar int-str (split-str x "..")]])
(defun str-int-list-p (s)
(and (str-list-p s)
(all (str-list-expand s)
(lambda (x)
(or (int-str x)
(str-range-p x))))))
(defun str-list-p (x)
(search-str x ";"))
(defun str-list-expand (x)
(split-str x ";"))
(defun expand (s)
(cond ((str-int-list-p s)
(flatten [mapcar (lambda (x)
(if (str-range-p x)
(str-range-expand x)
(int-str x)))
(str-list-expand s)]))
((str-list-p s) (str-list-expand s))
((str-range-p s) (str-range-expand s))
((int-str s) (int-str s))
(t s)))
And for checking if a location string is the parent of another location string:
(defun level-up (x)
(cond ((equal x "") nil)
((search-str x "/")
(sub-str x 0 (search-str x "/" 0 t)))
(t "")))
(defun parent-location-p (x y)
(or (equal x y)
(equal x "")
(and (not (equal y ""))
(match-str (level-up y) x))))
I'm primarily interested in what TXR lisp built-in functions you think might be good for solving the remainder of this task to achieve the desired output. And, how would you approach the existing code differently to take advantage of existing TXR lisp features?
This solution works on the sample data given in an earlier edit of the question. It doesn't save the data in different
.csv
files but its output indicates what goes where.A few objects are used to organize the logic. Locations are represented by the
locations
structure which automatically breaks the path names into components for easy analysis. Headings are represented asheading
objects, which treat the type notation somewhat; currently it is only used for reformatting the integers representing years, days and months to the proper notation with leading zeros. Tables are represented astable
objects which hold various properties. Values, however, are just lists. A table contains a list of rows, and rows are just lists of values. Values are normally scalar. If one or more values in a row are value are lists, that means that the row is a compression of multiple rows (as a result of the..
and;
notation). Ranges are expanded using code straight from the Rosetta Code range expansion, adapted to the delimiters used here.The parser is modified only very slightly. The
:counter
is gone, and the maincollect
has instead a:vars (tables)
: only a list of tables emerges and these are objects constructed using thenew
macro. Also, there is a new@(rebind values (values))
so thatmeta
tables come out in the same representation: though they have just one row, we want theirrows
property to hold a list of rows, just likedata
tables.The requirement for catenating tables with the same location is handled using the
group-reduce
expression which relies on a hash table to identify like items and combine them using thetable
structure'scat
method. A table catenates another table by producing a copy of itself with therows
replaced by appending its originalrows
with that of the other one.Merging the additional properties from meta tables is performed by iterating over all of the data tables and applying the matching properties. For each data table, we iterate all of the meta tables in order of decreasing path length (most particular to least). From each meta table whose location is a prefix of the data table's location, we merge on the properties with the
table
merge
method. (This also works functionally, likecat
: it returns a new, merged table). Merge means that we stick on all the headings from the meta table, and do a cross-producting operation on the rows: each new meta row on the left is paired with every row of the table being extended on the right.Expanding rows which contain multiple values is done by
table
expand-rows
. This simply makes a copy of each row, with each list substituted by its first item (the Lispcar
). It then iterates over thecdr
: a new row is calculated where the lists are replaced by theircdr
. This repeats, until the lists are exhausted. For instance(1 (a b) 3 (x y))
will produce(1 a 3 x)
, with a "remainder" of(1 (b) 3 (y))
. This remainder produces(1 b 3 y)
with a new remainder of(1 nil 3 nil)
. This doesn't contain any moreconsp
values (all areatom
) so the iteration terminates.