我有以下结构的数据(简化):
(def m {"a" [1 2 3] "b" [4 5 6] "c" [7 8 9] "d" [10 11 12]})
我有一个递归函数,在任何迭代中,我都对a
,b
,c
和d
中每个元素的第i个元素感兴趣。
我一直在做的是:
(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...
换句话说,我正在为a
,b
,c
和d
创建绑定,这是因为我不想重复((m "a") i)
之类的内容每当我需要这个值时,我的代码会多次。
这似乎有点笨重,而不是一个非常实用的风格。有没有更好的方法来实现这一目标?也就是说,要么是更优雅的方式来创建绑定,要么甚至是避免绑定的方式?
修改:添加解释为什么我需要循环而不是地图:
我的数据代表一棵树,我的函数遍历树以找到合适的终端节点。每个向量的i'th
元素是与i'th
节点相关的数据。因此,我从i = 0
开始,因为这是根节点。我做了一些逻辑,这告诉我下一个节点。每个节点的逻辑都相同,这就是我使用loop
和recur
的原因。实际上可能有200个节点,因此通过树的一条路线可能是0 > 6 > 45 > 67 > 123 > 130 > 156 > done
。
如果有办法使用map
而非loop
遍历树,我会非常感动。
答案 0 :(得分:3)
根据您要解决的更大问题,这可能有所帮助:
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])
你要用你的函数替换each-element
的定义,每个元素来自" a"," b"," c" ,和" d"。
答案 1 :(得分:3)
看起来你真正想要做的是创建一个具有相同键的新映射,并转换所有值,这不需要索引i或任何绑定:
e.g。递增所有地图的值
(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
可以是任何转换值的函数)
另一种说法是:
(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]}))
在这里我们不需要绑定任何东西! juxt, comp
和partial
为我们构建了转型
另一种方法是:
(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]})
其中r是我们正在构建的结果,并且k,v是输入映射中每个映射条目的键和值,reduce函数接收为我们用[kv]解构的向量
在您的代码中,您似乎希望一次切换所有列表,我们可以这样做:
(apply (partial map list) [[1 2 3] [4 5 6] [7 8 9]])
=> ((1 4 7) (2 5 8) (3 6 9))
给出了所有第一个元素的序列,所有第二个元素......
(顺便说一下,我们可以做到这一点:
(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))
无需编制索引: - )
无论如何,你可以用那些来做点什么:
(map (partial reduce +) (apply (partial map list) [[1 2 3] [4 5 6] [7 8 9]]))
=> (12 15 18)
这是将地图转换为新地图的另一个问题,但无论如何。
一般来说,在Clojure中,几乎不需要对事物进行索引,并且经常(如您所怀疑的)代码更清晰而没有绑定事物。使用map, reduce, apply, juxt, partial
和comp
进行游戏,一切都会变得更轻松
答案 2 :(得分:1)
为了让您的代码更具表现力,......
以下函数从node-no
:
data
的地图
(defn node-data [data node-no]
(into {} (map (fn [[k v]] [k (get v node-no)]) data)))
例如......
(node-data m 1)
; {"a" 2, "b" 5, "c" 8, "d" 11}
这是一个使用解构来生成绑定到与相应字符串键相关联的值的局部参数的函数示例:
(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]
你在your separate question的回答中看到了类似的内容。
如果要一次转换所有数据,以下函数会将其转换为节点映射的向量:
(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}]
备注强>
您最好使用loop
而不是其他任何东西。几乎所有Clojure的重复抽象都涉及序列:map
,reduce
,iterate
....由于你的计算是一个简单的循环,它们对你没用。你可以使用iterate
和reduce
(reduced
)来装扮,但似乎没有意义。
以上假设树在整个过程中保持不变。如果您在导航时更改树,则最好查看zippers。
P.S。字符串键是自然标识符吗?如果没有,请选择关键字。
答案 3 :(得分:0)
不是在a
绑定中分别绑定b
,c
,d
和let
中的每一个,而是可以更简洁的方式进行绑定使用解构:
(loop [i 0]
(let [[a b c d] (map (fn [[ltr nums]] (nums i)) m)]
; ... do stuff with a, b, c & d ...
为此,您需要使用sorted-map
,以便在地图中保留a,b,c和d的顺序:
(def m (sorted-map "a" [1 2 3], "b" [4 5 6], "c" [7 8 9], "d" [10 11 12]))
这仍然不是很实用,因为你仍在使用loop
...可能有更多功能方法,但这取决于你正在尝试用这个数据图做什么。
答案 4 :(得分:0)
你也许可以在Clojure的树遍历中阅读Alex Miller的这篇文章:http://www.ibm.com/developerworks/library/j-treevisit/index.html