在Clojure中的某个范围内的地图列表中填写缺失值(Fill in missing values in a list of list of maps over a certain range in Clojure)

如果我们的输入看起来像这样:

(({:a 1 :b 100} {:a 2 :b 300} {:a 4 :b 0}) ({:a 0 :b 10} {:a 4 :b 50}))

我们想要监管的范围是(0 1 2 3 4)我们希望输出为:

(({:a 0 :b 0} {:a 1 :b 100} {:a 2 :b 300} {:a 3 :b 0} {:a 4 :b 0}) ({:a 0 :b 10} {:a 1 :b 0} {:a 2 :b 0} {:a 3 :b 0} {:a 4 :b 50}))

基本上它应该做的是查看第一个地图列表然后看第二个等等,然后找出范围是什么:a 。 我们可以使用min / max函数轻松完成。 现在它创建一个范围并将其应用于两个列表。 如果在一个列表中缺少:a ,则会添加:a ,a :b为0 。 (即在第一个列表中添加{:a 0 :b 0}或{:a 3 :b 0} 。我们有一个功能可以稍微做到,但还没有完全存在。这里是:

(map #(doseq [i (vec myRange)] (if (some (fn [list] (= i list)) (map :a %)) nil (println (conj % {:a i :b 0})))) myList)

显然,由于Clojures不可变数据结构,这个函数失败了。 如果我们的输入是这样的:

(({:a 1, :b 1} {:a 2, :b 3} {:a 4, :b 5}) ({:a 0, :b 3} {:a 4, :b 1}))

我们的输出是:

(nil nil)

但是如果我们打印出来:

({:a 0, :b 0} {:a 1, :b 1} {:a 2, :b 3} {:a 4, :b 5}) ({:a 3, :b 0} {:a 1, :b 1} {:a 2, :b 3} {:a 4, :b 5}) ({:a 1, :b 0} {:a 0, :b 3} {:a 4, :b 1}) ({:a 2, :b 0} {:a 0, :b 3} {:a 4, :b 1}) ({:a 3, :b 0} {:a 0, :b 3} {:a 4, :b 1}) (nil nil)

我们希望输出看起来像:

(({:a 0, :b 0} {:a 1, :b 1} {:a 2, :b 3} {:a 3, :b 0} {:a 4, :b 5}) ({:a 0, :b 3} {:a 1, :b 0} {:a 2, :b 0} {:a 3, :b 0} {:a 4, :b 1}))

不使用println。 有什么建议么?

If our input were to look something like this:

(({:a 1 :b 100} {:a 2 :b 300} {:a 4 :b 0}) ({:a 0 :b 10} {:a 4 :b 50}))

Our range that we would like to police over would be (0 1 2 3 4) ​ We would like the output to be:

(({:a 0 :b 0} {:a 1 :b 100} {:a 2 :b 300} {:a 3 :b 0} {:a 4 :b 0}) ({:a 0 :b 10} {:a 1 :b 0} {:a 2 :b 0} {:a 3 :b 0} {:a 4 :b 50}))

Basically what it should do is look at the first list of maps then the second and so on and figure out what the range is for :a. We can easily do that with a min/max function. Now it creates a range and applies it to both lists. If an :a is missing on one list it adds in that :a with a :b of 0. (ie the addition of {:a 0 :b 0} or {:a 3 :b 0} in the first list. We have a function that can somewhat do it, but aren't quite there yet. Here it is:

(map #(doseq [i (vec myRange)] (if (some (fn [list] (= i list)) (map :a %)) nil (println (conj % {:a i :b 0})))) myList)

Obviously because of Clojures immutable data structures this function fails. If our input is something like:

(({:a 1, :b 1} {:a 2, :b 3} {:a 4, :b 5}) ({:a 0, :b 3} {:a 4, :b 1}))

our output is:

(nil nil)

but if we println:

({:a 0, :b 0} {:a 1, :b 1} {:a 2, :b 3} {:a 4, :b 5}) ({:a 3, :b 0} {:a 1, :b 1} {:a 2, :b 3} {:a 4, :b 5}) ({:a 1, :b 0} {:a 0, :b 3} {:a 4, :b 1}) ({:a 2, :b 0} {:a 0, :b 3} {:a 4, :b 1}) ({:a 3, :b 0} {:a 0, :b 3} {:a 4, :b 1}) (nil nil)

We want the output to look like:

(({:a 0, :b 0} {:a 1, :b 1} {:a 2, :b 3} {:a 3, :b 0} {:a 4, :b 5}) ({:a 0, :b 3} {:a 1, :b 0} {:a 2, :b 0} {:a 3, :b 0} {:a 4, :b 1}))

without the use of a println. Any suggestions?

最满意答案

在循环中使用不可变数据的想法是将最新迭代的结果传递给下一个迭代。 你可以用loop/recur来做,但在你的情况下,通常使用reduce函数(这实际上是函数式编程的基石之一):

(defn update-coll [range items] (reduce (fn [items i] (if (some #(= (:a %) i) items) items (conj items {:a i :b 0}))) items range))

第一个参数,用于reduce范围( i )的每个值的“更新” items ,将更新的值传递给下一次迭代。

现在你只需要用它映射你的输入数据:

(def input '(({:a 1 :b 100} {:a 2 :b 300} {:a 4 :b 0}) ({:a 0 :b 10} {:a 4 :b 50}))) (map (comp (partial sort-by :a) (partial update-coll [0 1 2 3 4])) input)

输出:

(({:a 0, :b 0} {:a 1, :b 100} {:a 2, :b 300} {:a 3, :b 0} {:a 4, :b 0}) ({:a 0, :b 10} {:a 1, :b 0} {:a 2, :b 0} {:a 3, :b 0} {:a 4, :b 50}))

你也可以使用clojure的集合而不用累积来做到这一点:

(defn process-input [input r] (let [r (map #(hash-map :a % :b 0) r)] (map (fn [items] (into (apply sorted-set-by #(compare (:a %1) (:a %2)) items) r)) input))) (process-input input [0 1 2 3 4])

输出:

(#{{:b 0, :a 0} {:a 1, :b 100} {:a 2, :b 300} {:b 0, :a 3} {:a 4, :b 0}} #{{:a 0, :b 10} {:b 0, :a 1} {:b 0, :a 2} {:b 0, :a 3} {:a 4, :b 50}})

the idea of working with immutable data in loop, is to pass the result of the latest iteration to the next one. You could do it with loop/recur, but in your case it is common to use reduce function (which is literally one of the cornerstones of functional programming):

(defn update-coll [range items] (reduce (fn [items i] (if (some #(= (:a %) i) items) items (conj items {:a i :b 0}))) items range))

the first parameter to reduce "updates" items for every value of range (i), passing the updated value to the next iteration.

now you just have to map your input data with it:

(def input '(({:a 1 :b 100} {:a 2 :b 300} {:a 4 :b 0}) ({:a 0 :b 10} {:a 4 :b 50}))) (map (comp (partial sort-by :a) (partial update-coll [0 1 2 3 4])) input)

output:

(({:a 0, :b 0} {:a 1, :b 100} {:a 2, :b 300} {:a 3, :b 0} {:a 4, :b 0}) ({:a 0, :b 10} {:a 1, :b 0} {:a 2, :b 0} {:a 3, :b 0} {:a 4, :b 50}))

also you can do it without accumulation using clojure's sets:

(defn process-input [input r] (let [r (map #(hash-map :a % :b 0) r)] (map (fn [items] (into (apply sorted-set-by #(compare (:a %1) (:a %2)) items) r)) input))) (process-input input [0 1 2 3 4])

output:

(#{{:b 0, :a 0} {:a 1, :b 100} {:a 2, :b 300} {:b 0, :a 3} {:a 4, :b 0}} #{{:a 0, :b 10} {:b 0, :a 1} {:b 0, :a 2} {:b 0, :a 3} {:a 4, :b 50}})

更多推荐