Clojure:列出替换函数

时间:2018-01-04 18:21:07

标签: 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 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是列表时,这会导致输出反转。其次,我正在努力找到一种方法让这个工作在地图上(我想检查每个条目的键和值的列表)。

任何提示或指示都将非常感激。感谢。

2 个答案:

答案 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),然后再将其转换回地图。

我确信还有更好更有效的方法可以做到这一点,但我认为这样做有效。