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
检查答案.