我正在尝试编写一个函数,给定一个集合和一系列替换,用它们出现的顺序替换集合中的任何列表。
例如:
(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
的类型相同。我尝试了(into (empty form) (substitute form syms))
但是当form
是列表时,这会导致输出反转。其次,我正在努力找到一种方法让这个工作在地图上(我想检查每个条目的键和值的列表)。
任何提示或指示都将非常感激。感谢。
答案 0 :(得分:2)
以下是使用clojure.walk/prewalk
按顺序(预订)遍历form
并使用原子跟踪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
原子是为了确保我们不会对第一个值执行操作,而该值将是form
本身,如果它是一个我们不想要的列表替换整个事物。刚开始我刚检查了(not= form v)
但是如果您的form
包含与外部form
相同/相等的嵌套表单,则可能会适得其反。 我怀疑有更好的方法来实现这一目标!
prewalk
(以及postwalk
)也让您不必担心正在行走的收藏品的类型,即列表会以正确的顺序出现。< / p>
(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"
答案 1 :(得分:0)
感谢所有回复的人。我想我现在可能已经破解了它。
(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
,它将一个映射转换为一个向量,使{:a 1 :b 2 :c 3}
变为[:a 1 :b 2 :c 3]
并添加一个辅助函数,确保输出的类型与输入,如果输入是地图,则在substitute-seq
ed地图上执行主要功能(现在称为map-to-vec
),然后再将其转换回地图。
我确信还有更好更有效的方法可以做到这一点,但我认为这样做有效。