Clojure:列表替换函数
我正在尝试编写一个函数,给定一个集合和一系列替换项,该函数将集合中的任何列表按其出现的顺序替换为下一个替换项 例如:Clojure:列表替换函数,clojure,Clojure,我正在尝试编写一个函数,给定一个集合和一系列替换项,该函数将集合中的任何列表按其出现的顺序替换为下一个替换项 例如: (substitute '(+ 1 (* 2 3) 4 (* 5 6) [:a :b]) => (+ 1 :a 4 :b) (substitute '[1 (2 3 4) (5 6 7)] [:x :y :z]) => [1 :x :y] (substitute '[(1 2 3) (4 5 6) (7 8 9)] [:x :y]) => [:x :y (7 8
(substitute '(+ 1 (* 2 3) 4 (* 5 6) [:a :b]) => (+ 1 :a 4 :b)
(substitute '[1 (2 3 4) (5 6 7)] [:x :y :z]) => [1 :x :y]
(substitute '[(1 2 3) (4 5 6) (7 8 9)] [:x :y]) => [:x :y (7 8 9)]
目前我有:
(defn substitute
[form syms]
(if (seq form)
(lazy-seq
(if (and (not-empty syms) (list? (first form)))
(cons
(first syms)
(substitute (rest form) (rest syms)))
(cons
(first form)
(substitute (rest form) syms))))))
然而,我有两个问题。首先,我希望输出的类型与form
相同。我尝试执行(转换为(空表单)(替换表单syms))
,但这会导致在表单
是列表时输出反转。第二,我正在努力找到一种方法来在地图上实现这一点(我想检查每个条目的键和值的列表)
任何提示或指点都将不胜感激。谢谢。这里有一种方法,使用
clojure.walk/prewalk
按顺序(pre-order)遍历表单
,并使用atom跟踪要替换的syms
剩余内容:
(defn substitute [form syms]
(let [syms' (atom syms)
depth (atom 0)]
(walk/prewalk
(fn [v]
(cond
(= 1 (swap! depth inc)) v ;; don't examine the input form itself
(list? v) (if-let [sym (first @syms')]
(do (swap! syms' rest)
sym)
v)
:else v))
form)))
depth
atom是为了确保我们不会对第一个值(即form
本身)采取行动,如果它是一个列表,我们就不想替换整个值。起初,我只是检查了(not=form v)
,但我认为如果表单
包含与外部表单
相同/相等的嵌套表单,可能会适得其反。我怀疑有更好的方法来实现这一点
prewalk
(和postwwalk
)还可以让您不用担心正在浏览的收藏类型,即列表将以正确的顺序出现
(substitute '(+ 1 (* 2 3) 4 (* 5 6)) [:a :b])
=> (+ 1 :a 4 :b)
(substitute '[1 (2 3 4) (5 6 7)] [:x :y :z])
=> [1 :x :y]
(substitute '[(1 2 3) (4 5 6) (7 8 9)] [:x :y])
=> [:x :y (7 8 9)]
使用prewalk
也可以在地图上进行此操作,而无需额外的工作:
(substitute {:foo '(1 2 3) '(4 5 6) "hey"} [:a :b])
=> {:foo :a, :b "hey"}
您还可以使用prewalk demo
演示如何遍历表单:
(walk/prewalk-demo {:foo '(1 2 3) '(4 5 6) "hey"})
Walked: {:foo (1 2 3), (4 5 6) "hey"}
Walked: [:foo (1 2 3)]
Walked: :foo
Walked: (1 2 3)
Walked: 1
Walked: 2
Walked: 3
Walked: [(4 5 6) "hey"]
Walked: (4 5 6)
Walked: 4
Walked: 5
Walked: 6
Walked: "hey"
感谢所有回复的人。我想我现在可能已经破解了
(defn map-to-vec
[map]
(reduce-kv (fn [vec k v] (into vec [k v])) [] map))
(defn substitute-seq
[form syms]
(if (seq form)
(lazy-seq
(if (and (not-empty syms) (list? (first form)))
(cons
(first syms)
(substitute-seq (rest form) (rest syms)))
(cons
(first form)
(substitute-seq (rest form) syms))))))
(defn substitute
[form syms]
(cond
(list? form) (apply list (sub-syms-seq form syms))
(map? form)
(reduce
(fn [m [k v]] (conj m [k v]))
(empty form)
(partition 2 (sub-syms-seq (map-to-vec form) syms)))
(coll? form) (into (empty form) (sub-syms-seq form syms))))
我编写了一个函数map-to-vec
,它将映射转换为向量,这样{:a1:b2:c3}
就变成了[:a1:b2:c3]
,并添加了一个助手函数,确保输出的类型与输入相同,并且在输入是映射的情况下,执行主函数(现在称为substitute seq
)在映射到vec
ed映射上,然后再次将其转换回映射
我相信还有更好、更有效的方法可以做到这一点,但我想这是可行的。对不起,我不是直接回答你的问题,但是……如果我遇到这种问题,我的第一反应是接触幽灵。你可以尝试一下:按照@leetwinski的建议,使用上面的
prewalk
检查答案.