Say I have a tree where I want to visit - and that should include the possibility to modify the visited items - all items that match the path
(def visit-path [:b :all :x :all])
where I use :all as a wildcard to match all child nodes. In the following example tree,
(def my-tree
{:a "a"
:b
{:b-1
{:x
{:b-1-1 "b11"
:b-1-2 "b12"}}
:b-2
{:x
{:b-2-1 "b21"}}}})
that would be items
[:b-1-1 "b11"]
[:b-1-2 "b12"]
[:b-2-1 "b21"]
Is there an elegant way to do this using clojure core?
FYI, I did solve this by creating my own pattern-visitor
(defn visit-zipper-pattern
[loc pattern f]
but although this function is generically usable, it is quite complex, combing both stack-consuming recursion and tail-call recursion. So when calling that method like
(visit-zipper-pattern (map-zipper my-tree) visit-path
(fn [[k v]] [k (str "mod-" v)]))
using map-zipper from https://stackoverflow.com/a/15020649/709537, it transforms the tree to
{:a "a"
:b {:b-1
{:x
{:b-1-1 "mod-b11"
:b-1-2 "mod-b12"}}
:b-2
{:x
{:b-2-1 "mod-b21"}}}}
The following will work - note that 1) it may allocate unneeded objects when handling:all keys and 2) you need to decide how to handle edge cases like :all on non-map leaves.
(defn traverse [[k & rest-ks :as pattern] f tree]
(if (empty? pattern)
(f tree)
(if (= k :all)
(reduce #(assoc %1 %2 (traverse rest-ks f (get tree %2)))
tree (keys tree))
(cond-> tree (contains? tree k)
(assoc k (traverse rest-ks f (get tree k)))))))
For a more efficient solution, it's probably better to use https://github.com/nathanmarz/specter as recommended above.
Related
I have (for instance) a mix of data structures such as {:name "Peter" :children "Mark"} and {:name "Mark" :children ["Julia" "John"] i.e. :children value is either a single string or a collection of strings. Other functions in my code expect that the value of :children is always a collection of strings, so I need to adapt the data for them.
Of course I can use something like:
(defn data-adapter [m]
(let [children (:children m)]
(assoc m :children
(if (coll? children)
children
[children]))))
But is there a more idiomatic/laconic way?
I think you will have to take no for an answer.
(if (coll? x) x [x]) is about as terse and expressive as it gets. It’s what people usually use for this problem (sometimes with sequential? instead of coll?).
cond-> enthusiasts like me sometimes try to use it in place of a simple conditional, but here it is no improvement:
(cond-> x (not (coll? x)) vector)
In the context of your code, however, you can do a little better. A lookup and association is best expressed with update:
(defn data-adapter [m]
(update m :children #(if (coll? %) % [%])))
the only advice would be to abstract that logic to some function, to keep your actual business logic clean.
(defn data-adapter [m]
(let [children (:children m)]
(assoc m :children (ensure-coll children))))
or, more concise, with update:
(defn data-adapter [m]
(update m :children ensure-coll))
where ensure-coll could be something like this:
(defn iffun [check & {:keys [t f] :or {t identity f identity}}]
#((if (check %) t f) %))
(def ensure-coll (iffun coll? :f list))
(or whatever another implementation you like)
user> (data-adapter {:children 1})
;;=> {:children (1)}
user> (data-adapter {:children [1]})
;;=> {:children [1]}
Perhaps not idiomatic, but laconic:
(flatten [x])
https://clojuredocs.org/clojure.core/flatten
I have a case where I would like to create a new instance of a record based on the type of a record instance that is coming as an argument together with a map of attributes.
(defn record-from-instance
[other attrs]
;; Code that creates the new record based on "other"
)
What I have right now is something among the lines:
(defn record-from-instance
[other attrs]
(let [matched (s/split (subs (str (class other)) 6) #"\.")
path (s/join "." (pop matched))
class-name (peek matched)]
((resolve (symbol (str path "/" "map->" class-name))) attrs)))
Is there any other simpler more idiomatic way to do this that I cannot see?
Thanks!
EDIT
To give some more details I am constructing an AST with nodes being records and I am using a zipper to visit and possibly alter / remove parts of the AST. I have an IZipableTreeNode protocol
(defprotocol IZipableTreeNode
(branch? [node])
(children [node])
(make-node [node children]))
Between the different types that implement the IZipableTreeNode is IPersistentMap
IPersistentMap
(branch? [node] true)
(children [node] (seq node))
(make-node [node children]
(let [hmap (into {} (filter #(= (count %) 2)) children)]
(if (record? node)
(record/from-instance node hmap)
hmap)))
When a visitor say deletes a field from a node (or alters it) the make-node gets called with node being the record AST node and children the new key/value pairs (that may not contain some of the fields in node).
I thought clojure.core/empty used to do this. That is, I thought
(defrecord Foo [x])
(empty (Foo. 1))
would return
#user.Foo{:x nil}
But it certainly doesn't do that now: I'm not sure whether that changed or I misremembered. I can't find a super clean way to do this, but I do at least have something better than your approach. The user/map->Foo function you're using is based on the static method generated along with the class, user.Foo/create, and it is somewhat classier to invoke that directly instead, through reflection.
user> ((fn [r attrs]
(.invoke (.getMethod (class r) "create"
(into-array [clojure.lang.IPersistentMap]))
nil, (into-array Object [attrs])))
(Foo. 1) {:x 5})
#user.Foo{:x 5}
However, it occurs to me now you may not need to do any of this! You started with the preconception that the way to meet your goal of "build a new thing based on a previous thing" was to start from scratch, but why do that? As long as the record being passed into your function doesn't have any "extension" fields added onto it (i.e., those not part of the record definition itself), then you can simply use clojure.core/into:
(into (Foo. 1) {:x 5}) ;=> #user.Foo{:x 5}
You could also do this:
(defn clear [record]
(reduce (fn [record k]
(let [without (dissoc record k)]
(if (= (type record) (type without))
without
(assoc record k nil))))
record
(keys record)))
(defn map->record [record m]
(into (clear record) m))
Example:
(defrecord Foo [x y])
(map->record (map->Foo {:x 1 :y 2 :z 3}) {:y 4})
;;=> #example.core.Foo{:x nil, :y 4}
I'm not sure if this would be more efficient or less efficient than #amalloy's reflection approach.
I need to translate an array map that has this structure:
{A [(A B) (A C)], C [(C D)], B [(B nil)], D [(D E) (D F)]}
Into this equivalent list:
'(A (B (nil)) (C (D (E) (F))))
I have this function that works just fine for not that deep structures:
(def to-tree (memoize (fn [start nodes]
(list* start
(if-let [connections (seq (nodes start))]
(map #(to-tree (second %) nodes) connections))))))
However, as the n of nested elements grows, it gives off stack overflow error. How can I optimize this function, or rather, is there a way of doing this using walk or any other functional approach?
The input data that you provide looks a lot like an adjacency list. One approach you could take would be to convert your data into a graph and then create trees from it.
Here is a solution using loom to work with graphs. This example only uses one function from loom (loom.graph/digraph), so you could probably build something similar if adding a dependency is not an option for you.
Let's start by creating a directed graph from your data structure.
(defn adj-list
"Converts the data structure into an adjacency list."
[ds]
(into {} (map
;; convert [:a [[:a :b] [:a :c]]] => [:a [:b :c]]
(fn [[k vs]] [k (map second vs)])
ds)))
(defn ds->digraph
"Creates a directed graph that mirrors the data structure."
[ds]
(loom.graph/digraph (adj-list ds)))
Once we have the graph built, we want to generate the trees from the root nodes of the graph. In your example, there is only one root node (A), but there is really nothing limiting it to just one.
Loom stores a list of all nodes in the graph as well as a set of all nodes with incoming edges to a given node in the graph. We can use these to find the root nodes.
(defn roots
"Finds the set of nodes that are root nodes in the graph.
Root nodes are those with no incoming edges."
[g]
(clojure.set/difference (:nodeset g)
(set (keys (:in g)))))
Given the root nodes, we now just need to create a tree for each. We can query the graph for the nodes adjacent to a given node, and then create trees for those recursively.
(defn to-tree [g n]
"Given a node in a graph, create a tree (lazily).
Assumes that n is a node in g."
(if-let [succ (get-in g [:adj n])]
(cons n (lazy-seq (map #(to-tree g %) succ)))
(list n)))
(defn to-trees
"Convert a graph into a collection of trees, one for each root node."
[g]
(map #(to-tree g %) (roots g)))
...and that's it! Taking your input, we can generate the desired output:
(def input {:a [[:a :b] [:a :c]] :c [[:c :d]] :b [[:b nil]] :d [[:d :e] [:d :f]]})
(first (to-trees (ds->digraph input))) ; => (:a (:c (:d (:e) (:f))) (:b (nil)))
Here are a couple of inputs for generating structures that are deep or have multiple root nodes.
(def input-deep (into {} (map (fn [[x y z]] [x [[x y] [x z]]]) (partition 3 2 (range 1000)))))
(def input-2-roots {:a [[:a :b] [:a :c]] :b [[:b nil]] :c [[:c :d]] :e [[:e :b] [:e :d]]})
(to-trees (ds->digraph input-2-roots)) ; => ((:e (:b (nil)) (:d)) (:a (:c (:d)) (:b (nil))))
One of the cool things about this approach is that it can work with infinitely nested data structures since generating the tree is lazy. You will get a StackOverflowException if you try to render the tree (because it also infinitely nested), but actually generating it is no problem.
The easiest way to play with this is to create a structure with a cycle, such as in the following example. (Note that the :c node is necessary. If only :a and :b are in the graph, there are no root nodes!)
(def input-cycle {:a [[:a :b]] :b [[:b :a]] :c [[:c :a]]})
(def ts (to-trees (ds->digraph input-cycle)))
(-> ts first second first) ;; :a
(-> ts first second second first) ;; :b
You can test for this condition using loom.alg/dag?.
I have a list of maps where each key is associated with a list of strings.
I would like to convert each of these string list to sets instead.
(def list-of-maps-of-lists '({:a ["abc"]} {:a ["abc"]} {:a ["def"]} {:x ["xyz"]} {:x ["xx"]}))
This is my best attempt so far:
(flatten (map (fn [amap] (for [[k v] amap] {k (set v)})) list-of-maps-of-lists))
=> ({:a #{"abc"}} {:a #{"abc"}} {:a #{"def"}} {:x #{"xyz"}} {:x #{"xx"}})
What is the idiomatic solution to this problem?
This is very similar to your solution.
Using list comprehension:
(map
#(into {} (for [[k v] %] [k (set v)]))
list-of-maps-of-lists)
Alternative:
(map
#(zipmap (keys %) (map set (vals %)))
list-of-maps-of-lists)
I prefer solving such problems with fmap function from clojure.contrib:
(map (partial fmap set)
list-of-maps-of-lists)
Update: According to This Migration Guide, fmap has been moved to clojure.algo.generic.functor namespace of algo.generic library.
It's become an obsession of mine to reduce the lines of code that I write in Lisp/Clojure. I am trying to make the following code (essentially a depth first search) shorter.
(defn find-node [nodeid in-node]
(if (= nodeid (:id in-node))
in-node
(loop [node nil activities (:activities in-node)]
(if (or (empty? activities) (not (nil? node)))
node
(recur (find-node nodeid (first activities)) (rest activities))))))
(defn find-node-in-graph [nodeid node activities]
(if (empty? activities)
node
(recur nodeid (find-node nodeid (first activities)) (rest activities))))
(defrecord Graph [id name activities])
(defrecord Node [id name activities])
"activities" is a list
This might be cheating, though it's only one line ;)
(def tree {:id 1 :children [{:id 2 :children [{:id 3}]} {:id 4}]})
core> (filter #(= 3 (:id %)) (tree-seq :children :children tree))
({:id 3})
core> (filter #(= 2 (:id %)) (tree-seq :children :children tree))
({:children [{:id 3}], :id 2})
Some points on the original intent of the question:
loop/recer is often not the most compact form, it wan usually be written with map or doseq
if/recur can often be replaced with a call to filter
If you seem to really need to write something like this because of some specific requirement, often zippers will solve the problem more elegantly.