这是可怜的clojure吗?
我的数据结构如下(简化): 我有一个递归函数,在任何迭代中,我都对这是可怜的clojure吗?,clojure,Clojure,我的数据结构如下(简化): 我有一个递归函数,在任何迭代中,我都对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创建绑定,这是因为我不希望每次需要
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>完成
如果有一种方法可以使用
map
而不是loop
遍历一棵树,我会印象深刻,这可能会有帮助:
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])
将
每个元素的定义替换为从“a”、“b”、“c”和“d”分别操作一个元素的函数。而不是在let
绑定中分别绑定a
、b
、c
和d
,您可以使用解构以更简洁的方式完成:
(loop [i 0]
(let [[a b c d] (map (fn [[ltr nums]] (nums i)) m)]
; ... do stuff with a, b, c & d ...
要使其正常工作,您需要使用排序映射
,以保持映射中a、b、c和d的顺序:
(def m (sorted-map "a" [1 2 3], "b" [4 5 6], "c" [7 8 9], "d" [10 11 12]))
这仍然不是很实用,因为您仍在使用循环
。。。可能有一种更实用的方法,但这取决于您正试图使用此数据地图做什么 看起来您真正想要做的是使用相同的键和所有转换后的值创建一个新映射,而不需要索引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函数将其作为向量接收,我们将用[k v]对其进行分解
在您的代码中,您似乎希望一次分割所有列表,我们可以这样做:
(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
进行操作,一切都会变得更容易,使代码更具表达力
- 将感兴趣的节点的数据提取到地图中,然后
- 用于生成处理它的函数
易于书写(和阅读)
以下函数从数据中提取节点节点编号
的映射:
(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]
你已经在回答问题时看到了类似的情况
如果要同时转换所有数据,以下函数将其转换为节点映射向量:
(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}]
注释
使用循环
可能比其他任何方法都要好。几乎所有Clojure的重复抽象都涉及序列:map
、reduce
、iterate
。由于您的计算是一个简单的循环,因此它们对您没有任何用处。您可以使用iterate
和reduce
(使用reduced
)对其进行修饰,但似乎没有意义
以上假设该树始终不变。如果您在导航时正在更改树,则最好查看
另外,字符串键是自然标识符吗?如果没有,请选择关键字 您也许可以在Clojure中阅读Alex Miller关于树遍历的这篇文章:每个键总是有相同数量的值吗?你需要一个递归函数,还是可以将数据线性处理为序列集合?你试过“映射”吗?例如,下面将添加数据结构中的所有第n个值(apply map+(map second m)),该值返回(22 26 30)。您可以用dostuff函数替换+,我认为如果您正在遍历一棵树,并在每个节点上执行逻辑,使用循环是表达您所做操作的最清晰的方法之一。另一个可能很有趣的选择是使用tree-seq
。感谢tree-seq
的建议Daniel,我会看看。是的。这是理想的clojure方法loop
。我正在用clojure/data.json解析一个json字符串来创建映射,我刚刚意识到我可以将关键字解析为关键字而不是字符串。谢谢你的建议
(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}]