簡體   English   中英

在Clojure中,如何正確更新嵌套地圖?

[英]In Clojure, How do I update a nested map correctly?

經過多年的Java(和PHP / JavaScript)經驗,我才剛剛開始學習Clojure。 多么挑戰:-)

如何慣用地更新值映射? 當我在map上使用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)
     }
    ))

我已使用字符串鍵將任務放在哈希圖中,以便可以快速,直接地訪問映射中的任何任務。 每個任務也都具有鍵,因此我知道將單個任務傳遞給其他功能時的關鍵。

為了更新持續時間,我使用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))
  )

我用這個來打印之前/之后的值:

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

(swap! tasks update-task-durations)

(println "\nUPDATED tasks:")
(println @tasks)

1)我遇到的主要問題是map函數返回一個序列,而不是一個映射,因此我不得不使用into {}重新將序列轉換回map,這在我看來似乎是不必要且效率低下的。

有一個更好的方法嗎? 我應該使用map以外的功能嗎?

我可以更好地安排我的數據結構,同時仍然可以直接訪問單個任務嗎?

是否可以使用into {}將(可能非常大的)序列轉換為圖譜?

2)另外,在我傳遞給map函數的函數參數中,當我希望有一個map條目時,每個任務都由map作為[key value]形式的向量提供給我,以便獲取值從地圖項中,我必須將以下密鑰傳遞給我的update-in [1 :duration]這似乎有點難看,是否有比使用向量的索引1更好/更清晰的方法來訪問地圖項?

解決此映射映射問題的一種流行方法是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)

對於Clojure <1.7,請使用(update-in % [:duration] ...

另外,您也可以使用分解來簡化當前解決方案,而無需定義實用程序功能:

(->> tasks
     (map (fn [[k task]]
            [k (update task :duration update-duration)]))
     (into {})

為什么?

map僅處理序列。 如果您對類型簽名感興趣,這意味着map總是具有相同的類型( map :: (a -> b) -> [a] -> [b] ),但這也意味着您將一勞永逸map是一連串的事。

map在執行任何操作之前先對其集合參數調用seq對序列進行seq -ing會給您一系列鍵-值對。

在這里不必太擔心效率。 into速度很快,這很慣用。

只需獲得更多選擇即可:您可以使用for代替map

(into {}
   (for [[key value] your-map]
         [key (do-stuff value)]))

更快的方法是reduce-kv

(reduce-kv 
   (fn [new-map key value] 
         (assoc new-map key (do-stuff value))) 
   {}
   your-map))

當然你也可以使用簡單的reduce

(reduce (fn [m key]
          (update m key do-stuff))
   your-map
   (keys your-map))  

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM