2013-10-29 23 views
2

我想用Clojure hashmaps來定義廣義載體。在這幅圖中,散列圖{:x 3:y 2:z( - 3)}表示符號表達式3x + 2y - 3z。Clojure hashmaps作爲廣義載體

我想的函數代+充當上包含HashMap加法運算,以及滿足以下約束條件:

  • 應用根+兩個包含HashMap具有重疊鍵返回與求和那些鍵值散列映射。例如,

(gen+ {:x 1} {:x 2})

;= {:x 3}

  • 應用根+兩個包含HashMap與不同的鍵返回與這些鍵值組合的散列映射。例如,

(gen+ {:x 1} {:y 2})

;= {:x 1 :y 2}

  • 的空HashMap {}是根+功能的添加劑的身份。例如,

(gen+ {:x 1} {})

:= {:x 1}

  • 通過上述的限制,與非零項的任何散列映射也是一種添加劑身份。例如,{:z 0}。由於這種冗餘,gen +函數應該總是返回沒有任何0值的hashmaps。例如,

(gen+ {:x 1 :y 0} {:z 0})

;= {:x 1}

  • Clojure的解釋丟失的鑰匙爲具有值零,而不是0,與({:x 3} :y) ;= nil。因此,根+應該把零值的方法相同爲0。例如,

(gen+ {:x 1 :y 0} {:x nil :y nil})

{:x 1}

  • 我的問題:我們如何可以編寫一個函數代+滿足上述約束條件?一旦到位,是否可以使用gen +函數超載+運算符,從而使基本加法與hashmaps?

爲什麼這種形式主義對待包含HashMap作爲廣義載體應該是顯而易見的。 Clojure解釋了一個類似於[x0 x1 x2]的向量,它與hashmap {:0 x0:1 x1:2 x2}幾乎相同,稍有不同,我不太明白。因此,如果我們將gen +應用於兩個向量,那麼它應該與應用於它們的效果相同。這使我們能夠輕鬆地使用稀疏向量,以及添加不同大小的向量。例如,

(gen+ [0 0 0 4] [0 0 0 0 0 0 0 0 0 9])

;= {:4 4 :9 9}

這就是我不理解包含HashMap和載體。如果我將hashmap作爲函數調用,那麼我需要應用一個關鍵參數,如:2。在另一方面,如果我叫一個向量作爲功能,我需要申請一個索引參數一樣2.如

({:2 2} :2)

;= 2

([0 1 2] 2]

;= 2

即使你不能幫助gen +函數,你能解釋爲什麼hashmaps和vector在被稱爲函數時表現不同嗎?

+1

就像中號史密斯,我不會建議超載+,[但這裏的一些黑暗魔法(HTTP://無論如何,stackoverflow.com/questions/5433691/idiomatic-clojure-way-to-repeat-a-string-n-times/5438379#5438379)。 –

+0

如果這是一個學校項目,道具給你的學校接受clojure。 – muhuk

+0

@muhuk:我是一名專業數學家,主要從事功能分析(無限維線性代數)。當感興趣的空間/類型是線性空間時,這些工具適用,點/術語是矢量,地圖/函數是線性運算符。能夠使用hashmaps作爲向量,意味着我們可以部署現代數學技術,而不必重新發明輪子。這個觀點在MapReduce體系結構中特別有用,其中感興趣的基本對象是鍵/值對,就像hashmaps一樣。 –

回答

2

這會先刪除nil0值,然後添加的地圖,merge-with,如建議由M·史密斯:

(defn filter-vals 
    [pred m] 
    (into {} (filter (fn [[k v]] (pred v)) 
        m))) 

(defn gen+ 
    [m1 m2] 
    (letfn [(keep-val? [val] 
      (and (not (nil? val)) 
       (not (zero? val))))] 
    (merge-with + 
       (filter-vals keep-val? m1) 
       (filter-vals keep-val? m2)))) 

您需要過濾掉任何nil是你開始添加之前 - 否則你」最終最終會嘗試將nil添加到一個數字,這是一個錯誤。但是,我概述的gen+可能會返回一個值爲0的條目(請考慮(gen+ {:x 1} {:x -1}))。

如果可能的話根據您輸入的,並且需要避免的,你需要在合併後添加另一個過濾器:

(defn gen+ 
    [m1 m2] 
    (letfn [(keep-val? [val] 
      (and (not (nil? val)) 
       (not (zero? val))))] 
    (->> (merge-with + 
        (filter-vals keep-val? m1) 
        (filter-vals keep-val? m2)) 
     (filter-vals keep-val?)))) 

最後,這裏是一個可以處理可變數量的輸入的一個版本地圖:

(defn gen+ 
    [& maps] 
    (letfn [(keep-val? [val] 
      (and (not (nil? val)) 
       (not (zero? val))))] 
    (->> (apply merge-with 
       + 
       (map #(filter-vals keep-val? %) maps)) 
     (filter-vals keep-val?)))) 

所以,舉例來說:

(gen+ {:x 1} {:x 3} {:x 4 :y 5}) ;=> {:x 8, :y 5} 

關於差值b在地圖和矢量之間,可以這樣想:地圖是從鍵到值的函數,而矢量是從索引到值的函數。在這兩種情況下,如果你有一個「關鍵字」(對於向量是索引),你可以使用它來查找關聯的值。

我同意你所得到的關於超載的答案和評論clojure.core/+(我不推薦它);這麼說,這裏有一個方法來排序做到這一點,有很多需要注意的地方:

(ns overload.example 
    (:refer-clojure :exclude [+]) 
    (:require [clojure.core :as clj])) 

(in-ns 'overload.example) 

(defn gen+ ...) 

(defn + [& xs] 
    (if (every? number? xs) 
    (reduce clj/+ 0 xs) 
    (apply gen+ xs))) 
+0

謝謝你的全面回答! –

+0

@TomLaGatta,很高興我能幫到你! – jbm

4

的答案,你的第一個問題是使用merge-withhttp://clojuredocs.org/clojure_core/clojure.core/merge-with

從文檔:

返回一個地圖,由地圖連詞-ED的其餘部分到 第一。如果一個鍵出現在多個映射中,則後者的映射 (從左到右)將與 中的映射相結合,調用(f val-in-result val-in-後者)。

然後,您可以編寫一個函數來合併合併值(可能是+)。然後折騰零和0值。

重載+在Clojure中並不是一個好主意,因爲它不是真的超載,而是取代。

地圖和矢量之間的區別就像一個數組,而地圖更像是一個鍵值對的字典。這兩個結構也是它們成員的功能。對於其成員可通過偏移進行訪問的矢量/數組,它有意義地接受偏移量。對於其成員通過鍵訪問的映射/字典,它接受鍵是有意義的。

+0

謝謝你,馬修! merge-with +是一個很好的解決方案,並且看起來正如我所希望的那樣工作。但是,我怎樣才能拋棄nil&0值?我不確定如何按值過濾地圖。 –

+2

@TomLaGatta,你可以做些事情[像這樣](https://gist.github.com/johnmastro/7224520) – jbm