2

I have data in the following structure (simplified):

(def m {"a" [1 2 3] "b" [4 5 6] "c" [7 8 9] "d" [10 11 12]})

I have a recursive function and at any iteration I am interested in the i'th element of each of a, b, c and d.

What I have been doing is:

(loop [i 0]
    (let [a ((m "a") i)
          b ((m "b") i)
          c ((m "c") i)
          d ((m "d") i)]
         ...do stuff with a, b, c and d...

In other words I am creating bindings for a, b, c and d and that is because I would rather not repeat something like ((m "a") i) multiple times in my code every time I need this value.

This seems a little clunky and not a very functional style. Is there a better way to achieve this? That is, either a more elegant way of creating the bindings or perhaps even a way that avoids bindings?

Edit: Adding an explanation as to why I need loop and not map:

My data represents a tree and my function traverses the tree to find the appropriate terminal node. The i'th element of each of the vectors is the data related to the i'th node. Therefore I start with i = 0 as this is the root node. I do some logic and this tells me which node to go to next. The logic is the same at each node and this is why I have used loop and recur. In reality there are perhaps 200 nodes and so one route through the tree could be 0 > 6 > 45 > 67 > 123 > 130 > 156 > done.

I would be hugely impressed if there is a way to traverse a tree with map and not loop.

user2179977
  • 656
  • 6
  • 14
  • Does each key always have the same number of values? Do you need a recursive function or could you linearly process the data as a collection of sequences? – georgek Apr 08 '14 at 02:36
  • did you try 'map'? For example the following will add all the nth values in your data structure (apply map + (map second m)) which returns (22 26 30) . You can replace + with your dostuff function – KobbyPemson Apr 08 '14 at 02:50
  • I think if you're traversing a tree, and performing logic at each node, using `loop` is one of the clearest ways to express what you're doing. Another alternative that might be fun to try is to use `tree-seq`. – Daniel Neal Apr 08 '14 at 08:18
  • Thanks for the `tree-seq` suggestion Daniel, I will take a look at that. – user2179977 Apr 08 '14 at 10:47

5 Answers5

3

Depending what your bigger problem to solve is, this may help:

user> (def m {"a" [1 2 3] "b" [4 5 6] "c" [7 8 9] "d" [10 11 12]})
#'user/m
user> (defn each-element [a b c d] (vector a b c d))
#'user/each-element
user> (apply map each-element (map m ["a" "b" "c" "d"]))
([1 4 7 10] [2 5 8 11] [3 6 9 12])

Where you would replace the definition of each-element with your function that operates one element each from "a", "b", "c", and "d".

noisesmith
  • 20,076
  • 2
  • 41
  • 49
3

It looks like what you really want to do is make a new map with the same keys and all their values transformed, which doesn't need an index i, or any bindings:

e.g. increment all the map's values

(into {} (map (fn [[k v]] [k (map inc v)]) {"a" [1 2 3] "b" [4 5 6] "c" [7 8 9] "d" [10 11 12]}))

=> {"a" (2 3 4), "b" (5 6 7), "c" (8 9 10), "d" (11 12 13)}

(inc could be any function to transform the values)

another way to say that is:

(into {} (map (juxt key (comp (partial map inc) val)) {"a" [1 2 3] "b" [4 5 6] "c" [7 8 9] "d" [10 11 12]}))

and here we didn't need to bind anything! juxt, compandpartial build our transform for us

another approach is:

(reduce (fn [r [k v]] (assoc-in r [k] (map inc v))) {} {"a" [1 2 3] "b" [4 5 6] "c" [7 8 9] "d" [10 11 12]})

where r is the result we are building, and again k,v are the key and value of the each map entry in the input map, which the reduce function receives as vectors which we're destructuring with [k v]

In your code it looks like you want to slice through all lists at once, which we can do thus:

(apply (partial map list) [[1 2 3] [4 5 6] [7 8 9]])
=> ((1 4 7) (2 5 8) (3 6 9))

which gives us a sequence of all the first elements, all the second etc...

(just as an aside, we could do that in general:

(apply (partial map list) (take 7 (partition 3 (iterate inc 0))))
=> ((0 3 6 9 12 15 18) (1 4 7 10 13 16 19) (2 5 8 11 14 17 20))

without any need for indexing :-)

anyway, then you could do something with those:

(map (partial reduce +) (apply (partial map list) [[1 2 3] [4 5 6] [7 8 9]]))
=> (12 15 18)

which is a separate matter from transforming maps into new maps, but anyway.

In general, in Clojure one hardly ever needs to index things, and often (as you suspected) code is clearer without binding things. Play around with map, reduce, apply, juxt, partial and comp and everything will become easier

Hendekagon
  • 4,565
  • 2
  • 28
  • 43
1

To make your code more expressive, ...

  • extract the data for the node you're interested in into a map and
  • use destructuring to make the functions that deal with it easier to write (and to read).

The following function extracts the map for node node-no from your data:

(defn node-data [data node-no]
  (into {} (map (fn [[k v]] [k (get v node-no)]) data)))

For example ...

(node-data m 1)
; {"a" 2, "b" 5, "c" 8, "d" 11}

And here's an example of a function using destructuring to produce local arguments bound to the values associated with the corresponding string keys:

(defn do-whatever [{:strs [a b c d]}] [a b c d (- (+ a b) (+ c d))])

(do-whatever (node-data m 1))
; [2 5 8 11 -12]

You've seen the like in an answer to your separate question.

If you want to convert all your data at once, the following function transposes it into a vector of node maps :

(defn transpose-map [a]
  (let [transpose (fn [s] (apply map vector s))
        ta (transpose a)
        tsa (transpose (second ta))]
    (mapv #(zipmap (first ta) %) tsa)))

(transpose-map m)
; [{"d" 10, "c" 7, "b" 4, "a" 1} {"d" 11, "c" 8, "b" 5, "a" 2}
;  {"d" 12, "c" 9, "b" 6, "a" 3}]

Notes

You probably do better to use loop than anything else. Almost all of Clojure's repetitive abstractions deal with sequences: map, reduce, iterate ... . Since your computation is a simple loop, they are of no use to you. You could dress it up using iterate and reduce (with reduced), but there seems no point.

The above assumes that the tree is unaltered throughout. If you are altering the tree as you navigate it, you had better look at zippers.

P.S. Are the string keys natural identifiers? If not, prefer keywords.

Community
  • 1
  • 1
Thumbnail
  • 13,293
  • 2
  • 29
  • 37
  • I'm parsing a json string with clojure/data.json to create the map and just realized I can parse keywords into keywords and not strings. Thanks for the suggestion. – user2179977 Apr 08 '14 at 10:55
  • @user2179977 If you change from string keys to keyword keys, replace `:strs` with `:keys` in the destructuring example. – Thumbnail Apr 08 '14 at 15:41
0

Instead of binding each of a, b, c and d separately in your let binding, you could do it in a more concise way using destructuring:

(loop [i 0]
  (let [[a b c d] (map (fn [[ltr nums]] (nums i)) m)]
    ; ... do stuff with a, b, c & d ...

For this to work, you would need to use a sorted-map, in order to preserve the order of a, b, c and d in your map:

(def m (sorted-map "a" [1 2 3], "b" [4 5 6], "c" [7 8 9], "d" [10 11 12]))

This is still not very functional because you're still using a loop... there is probably a more functional approach, but it would depend on what exactly you're trying to do with this map of data.

Dave Yarwood
  • 2,866
  • 1
  • 17
  • 29
0

You can perhaps read this article of Alex Miller on Tree Traversal in Clojure : http://www.ibm.com/developerworks/library/j-treevisit/index.html

Ivan Pierre
  • 808
  • 7
  • 9