Skip to content

Commit 4397d42

Browse files
committed
Added utility functions for aggregating table items into trees
1 parent 2033f1b commit 4397d42

File tree

1 file changed

+86
-4
lines changed

1 file changed

+86
-4
lines changed

src/excel_clj/tree.clj

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
(value t))
1111
; => {:usd 15M :mxn 7M}
1212
13-
See the example at the bottom of the ns for some code to render a balance
14-
sheet."
13+
For some example code, see the functions `balance-sheet-example` or
14+
`tree-table-example` in this namespace."
1515
:author "Matthew Downey"} excel-clj.tree
1616
(:require
1717
[clojure.string :as string]))
@@ -78,7 +78,9 @@
7878
[node (mapv #(trampoline traverse %) (children node))])))]
7979
(trampoline traverse root))))
8080

81-
(defn force-map [tree-or-map]
81+
(defn force-map
82+
"Returns the argument if it's a map, otherwise calls `value` on the arg."
83+
[tree-or-map]
8284
(if (map? tree-or-map)
8385
tree-or-map
8486
(value tree-or-map)))
@@ -268,7 +270,7 @@
268270
["Equity"
269271
[["Common Stock" {2018 102M, 2017 80M}]]]]]))
270272

271-
(defn example []
273+
(defn balance-sheet-example []
272274
;; Render the tree as a table
273275
(-> mock-balance-sheet tree->raw-table raw-table->rendered print-table)
274276

@@ -316,3 +318,83 @@
316318
(complement leaf?) children (second mock-balance-sheet)
317319
:edge->descriptor (fn [x y] (when (leaf? y) {:label (label y)}))
318320
:node->descriptor #(->{:label (if (leaf? %) (value %) [(label %) (value %)])})))
321+
322+
;;; Coerce a tabular format to a tree format
323+
324+
(defn ordered-group-by
325+
"Like `group-by`, but returns a [k [v]] seq and doesn't rearrange values except
326+
to include them in a group. Probably less performant because it has to search
327+
the built up seq to find the proper key-value store."
328+
[f xs]
329+
(letfn [(update-or-add [xs pred update default]
330+
(loop [xs' [], xs xs]
331+
(if-let [x (first xs)]
332+
(if (pred x)
333+
(into xs' (cons (update x) (rest xs)))
334+
(recur (conj xs' x) (rest xs)))
335+
(conj xs' default))))
336+
(assign-to-group [groups x]
337+
(let [group (f x)]
338+
(update-or-add
339+
groups
340+
#(= (first %) group)
341+
#(update % 1 conj x)
342+
[group [x]])))]
343+
(reduce assign-to-group [] xs)))
344+
345+
(defn table->trees
346+
"Collapse a tabular collection of maps into a collection of trees, where the
347+
label at each level of the tree is given by each of `node-fns` and the columns
348+
displayed are the result of `format-leaf`, which returns a tabular map.
349+
350+
See the (comment ...) block under this method declaration for an example."
351+
[tabular format-leaf & node-fns]
352+
(letfn [(inner-build
353+
([root items]
354+
(vector
355+
root
356+
(if (= (count items) 1)
357+
(format-leaf (first items))
358+
(map #(->["" (format-leaf %)]) items))))
359+
([root items below-root & subsequent]
360+
(vector
361+
root
362+
(->> (ordered-group-by below-root items)
363+
(mapv (fn [[next-root next-items]]
364+
(apply inner-build next-root next-items subsequent)))))))]
365+
(second (apply inner-build "" tabular node-fns))))
366+
367+
(defn tree-table-example []
368+
(-> (table->trees
369+
;; The table we'll convert to a tree
370+
[{:from "MXN" :to "AUD" :on "BrokerA" :return (rand)}
371+
{:from "MXN" :to "USD" :on "BrokerB" :return (rand)}
372+
{:from "MXN" :to "JPY" :on "BrokerB" :return (rand)}
373+
{:from "USD" :to "AUD" :on "BrokerA" :return (rand)}]
374+
375+
;; The data fields we want to look at
376+
#(-> {"Return" (format "%.2f%%" (:return %))
377+
"Trade Description" (format "%s -> %s" (:from %) (:to %))})
378+
379+
;; The top level label -- split by above/below 50% return
380+
#(if (> (:return %) 0.5) "High Return" "Some Return")
381+
382+
;; Then split by which currency we start with
383+
#(str "Trading " (:from %))
384+
385+
;; Finally, by broker
386+
:on)
387+
(tree->raw-table :sum-totals? false)
388+
(raw-table->rendered :indent-width 5)
389+
(print-table {:empty-str "" :pad-width 5})))
390+
391+
; => Return Trade Description
392+
; High Return
393+
; Trading MXN
394+
; BrokerA 0.70% MXN -> AUD
395+
; BrokerB
396+
; 0.68% MXN -> USD
397+
; 0.93% MXN -> JPY
398+
; Some Return
399+
; Trading USD
400+
; BrokerA 0.20% USD -> AUD"

0 commit comments

Comments
 (0)