2014-10-10 19 views
1

我試圖更新由嵌套的地圖和序列組成的結構中的值,但update-in將無法​​工作,因爲我想允許通配符。我的手動方法導致我很難看,很大,嵌套forinto {}調用。我最終創建了一個採用結構,類似選擇器的序列和更新函數的函數。Clojure:更新,但通配符和路徑跟蹤

(defn update-each-in 
    ([o [head & tail :as path] f] 
    (update-each-in o path f [])) 
    ([o [head & tail :as path] f current-path] 
    (cond 
    (empty? path) (f o current-path) 
    (identical? * head) 
     (cond 
     (map? o) 
      (into {} (for [[k v] o] 
      [k (update-each-in v tail f (conj current-path k))])) 
     :else (for [[i v] (map-indexed vector o)] 
      (update-each-in v tail f (conj current-path i)))) 
    :else (assoc o head 
     (update-each-in (get o head) tail f (conj current-path head)))))) 

這讓我簡化我的更新以下

(def sample {"TR" [{:geometry {:ID12 {:buffer 22}}} 
        {:geometry {:ID13 {:buffer 33} 
           :ID14 {:buffer 55}}} 
        {:geometry {:ID13 {:buffer 44}}}] 
      "BR" [{:geometry {:ID13 {:buffer 22} 
           :ID18 {:buffer 11}}} 
        {:geometry {:ID13 {:buffer 33}}} 
        {:geometry {:ID13 {:buffer 44}}}]}) 

(update-each-in sample [* * :geometry * :buffer] 
    (fn [buf path] (inc buf))) 

顯然,這與深度嵌套結構堆棧溢出的問題;儘管我遠沒有達到這個目標,但擁有一個強大的解決方案會很好。任何人都可以提出更簡單/更快/更優雅的解決方案嗎?這可以用減速器/換能器完成嗎?

UPDATE這是一個要求,更新函數也獲得它正在更新的值的完整路徑。

+0

你能提供樣本預期的輸出到你的問題嗎? – Symfrog 2014-10-10 12:55:23

+1

@Symfrog你可以評估整個例子,它會給你預期的輸出。 – skrat 2014-10-10 16:34:33

回答

2

update-in與您創建的函數具有完全相同的簽名,它幾乎完全相同。有兩點不同:它不允許在「路徑」中使用通配符,並且它不會將中間路徑傳遞給更新函數。

添加通配符update-in

我從source code for update-in適應這一點。

(defn update-in-* 
    [m [k & ks] f & args] 
     (if (identical? k *) 
      (let [idx (if (map? m) (keys m) (range (count m)))] 
       (if ks 
        (reduce #(assoc % %2 (apply update-in-* (get % %2) ks f args)) 
          m 
          idx) 
        (reduce #(assoc % %2 (apply f (get % %2) args)) 
          m 
          idx))) 
      (if ks 
       (assoc m k (apply update-in-* (get m k) ks f args)) 
       (assoc m k (apply f (get m k) args))))) 

現在這兩條線產生相同的結果:

(update-in-* sample [* * :geometry * :buffer] (fn [buf] (inc buf))) 
(update-each-in sample [* * :geometry * :buffer] (fn [buf path] (inc buf))) 

我到update-in所做的更改只是通過轉移對通配符進行檢查。如果遇到通配符,則必須修改該級別的每個子節點。我使用reduce來保持集合的累積更新。

此外,另一種說法,爲了健壯性:我試圖使用通配符以外的*。它可能會作爲地圖中的關鍵字出現。

添加路徑跟蹤如果需要的更新功能獲得的完整路徑,然後我只想修改update-in一個更多的時間來update-in

。函數簽名發生變化,並添加(conj p k),但這就是它。

(defn update-in-* 
    [m ks f & args] (apply update-in-*-with-path [] m ks f args)) 

(defn- update-in-*-with-path 
    [p m [k & ks] f & args] 
     (if (identical? k *) 
      (let [idx (if (map? m) (keys m) (range (count m)))] 
       (if ks 
        (reduce #(assoc % %2 (apply update-in-*-with-path (conj p k) (get % %2) ks f args)) 
          m 
          idx) 
        (reduce #(assoc % %2 (apply f (conj p k) (get % %2) args)) 
          m 
          idx))) 
      (if ks 
       (assoc m k (apply update-in-*-with-path (conj p k) (get m k) ks f args)) 
       (assoc m k (apply f (conj p k) (get m k) args))))) 

現在這兩條線產生相同的結果:

(update-in-* sample [* * :geometry * :buffer] (fn [path val] (inc val))) 
(update-each-in sample [* * :geometry * :buffer] (fn [buf path] (inc buf))) 

這是比你原來更好的解決方案?我不知道。我喜歡它,因爲它是update-in之後的模型,其他人可能會比我關心我自己更仔細考慮update-in

+0

查看更新,此建議是否仍然成立? – skrat 2014-10-10 16:33:44

+0

@skrat,而不是這項工作? – galdre 2014-10-12 15:37:30

+0

非常好的嘗試,我根本沒有想到'update-in'的源代碼。然而。如果任何嵌套結構不是'IAssociative',這就行不通了。輸入數據通常是產生惰性序列的函數的產物,而不是由僅由地圖和向量組成的一些手工編碼的「樣本」。我的原始版本確實起作用,因爲它使用'for'形式。它實際上教會了我'核心'的'update-in'不適用於懶序列。 – skrat 2014-10-12 20:49:49