在Clojure中,如何正确更新嵌套映射?

Ste*_*ley 5 dictionary clojure update-in

经过多年的Java(和PHP/JavaScript)经验,我刚刚开始学习Clojure.真是个挑战:-)

如何以惯用方式更新值的映射?当我map在地图上使用该函数时,它不返回地图,它返回一个序列.

我正在开发一个小应用程序,我有一个任务列表.我想做的是改变一些单独任务中的一些值,然后更新原始任务列表.以下是我正在测试的任务:

(defrecord Task [key name duration])

(def tasks
  (atom
    {
     "t1" (->Task "t1" "Task 1" 10)
     "t2" (->Task "t2" "Task 2" 20)
     "t3" (->Task "t3" "Task 3" 30)
     }
    ))
Run Code Online (Sandbox Code Playgroud)

我已经使用字符串键将任务放在散列映射中,因此它可以快速,直接地访问映射中的任何任务.每个任务都包含密钥,因此我知道当我将各个任务传递给其他函数时,关键是什么.

更新我正在使用的持续时间,mapupdate-in迭代并有选择地更新每个任务的持续时间,并返回修改后的任务.

这是功能:

(defn update-task-durations
  "Update the duration of each task and return the updated tasks"
  [tasks]
  ; 1) Why do I have to convert the result of the map function,
  ;    from a sequence then back to a map?
  (into {}
    (map
      (fn [task]
        (println task) ; debug
        (update-in
          task
          ; 2) Why do I have to use vector index '1' here
          ;    to get the value of the map entry?
          [1 :duration]
          (fn [duration]
            (if (< duration 20)
              (+ duration 1)
              (+ duration 2)
              )
            )
          )
        ) tasks))
  )
Run Code Online (Sandbox Code Playgroud)

我用这个打印之前/之后的值:

(println "ORIGINAL tasks:")
(println @tasks)

(swap! tasks update-task-durations)

(println "\nUPDATED tasks:")
(println @tasks)
Run Code Online (Sandbox Code Playgroud)

1)我遇到的主要问题是map函数返回一个序列,而不是一个map,所以我不得不再次将序列转换回一个map,into {}这对我来说似乎是不必要和低效的.

有一个更好的方法吗?我应该使用除以外的功能map吗?

我可以更好地安排我的数据结构,同时仍然可以直接访问单个任务吗?

可以使用into {}?将(可能非常大的)序列转换为地图吗?

2)另外,在我的函数参数中,我传递给map函数,每个任务都给我map,作为表格的向量,[key value]当我期望一个地图条目时,所以从我有的地图条目中获取值将以下键传递给我update-in [1 :duration]这看起来有点难看,是否有更好/更清晰的方式来访问映射条目而不是使用向量的索引1?

Ben*_*enC 3

解决映射映射问题的一种流行方法是zipmap

(defn map-vals
  "Returns the map with f applied to each item."
  [f m]
  (zipmap (keys m)
          (map f (vals m))))

(defn update-task-durations
  [tasks]
  (let [update-duration (fn [duration]
                          (if (< duration 20)
                            (+ 1 duration)
                            (+ 2 duration)))]
    (->> tasks
         (map-vals #(update % :duration update-duration)))))

(swap! tasks update-task-durations)
Run Code Online (Sandbox Code Playgroud)

对于 Clojure < 1.7,请改用(update-in % [:duration] ...

或者,您也可以使用解构来简化当前的解决方案,而无需定义实用函数:

(->> tasks
     (map (fn [[k task]]
            [k (update task :duration update-duration)]))
     (into {})
Run Code Online (Sandbox Code Playgroud)

为什么?

map只处理序列。如果您热衷于类型签名,这意味着map始终具有相同的类型 ( map :: (a -> b) -> [a] -> [b]),但这也意味着您将得到的只是map一个 seq-of-something。

map在执行任何操作之前调用seq其集合参数,并且seq-ing 映射会为您提供一系列键值对。

这里不必太担心效率。into速度很快,而且这是非常惯用的。