2013-02-12 64 views
2

我有一個函數,它找出圖形中用Ruby編寫的節點之間的最小距離。我將它翻譯成Clojure,但在我看來它看起來很糟糕。Clojure功能的更多的慣用和優雅的方式

數據的表示是這樣的:

hash = {:v0 [:v1 :v2 :v3] 
     :v1 [:v4 :v5 :v6] 
     :v2 [:v7 :v8 :v9] 
     :v3 [:v10 :v11 :v12] 
     :v4 [:v13 :v14 :v15]} 

Ruby的功能如下:

def distance src, target, hash 
    return 0 if src == target 
    return nil if hash[src].nil? 
    dist = 1 

    if hash[src].include? target 
     return dist 
    else 
     arr = hash[src].map {|x| distance x, target, hash} 
    end 
    arr = arr.delete_if {|x| x.nil?} 

    return dist + arr.min if !arr.empty? 
    return nil 
end 

而且Clojure的功能如下:

(use 'clojure.contrib.seq-utils) 
(defn distance [src target h] 
    (if (= src target) 
    0 
    (if (nil? (h src)) 
     nil 
     (if (includes? (h src) target) 
     1 
     (let [arr (filter #(not= % nil) (map #(distance % target h) (h src)))] 
      (if (= (empty? arr) true) 
      nil 
      (+ 1 (apply min arr)))))))) 

你能向我展示一個更優雅和Clojure式的做法。那些嵌套的ifs很糟糕。

回答

3

如果使用套,而不是載體和使用cond代替嵌套if的IT看起來,至少對我來說,多一點的Clojure樣:

(def h {:v0 #{:v1 :v2 :v3} 
     :v1 #{:v4 :v5 :v6} 
     :v2 #{:v7 :v8 :v9} 
     :v3 #{:v10 :v11 :v12} 
     :v4 #{:v13 :v14 :v15}}) 

(defn distance [src target h] 
    (cond (= src target) 0 
     (nil? (h src)) nil 
     (contains? (h src) target) 
     :default (let [arr (filter #(not= % nil) (map #(distance % target h) (h src)))] 
        (if (empty? arr) 
        nil 
        (inc (apply min arr)))))) 

還值得一提的是,clojure.contrib現在已經過時了。刪除它可以讓這個代碼在大多數Clojure版本上運行。

2

注意到filter生成一個懶惰的seq允許通過刪除if來簡化Arthur Ulfeldt的答案而不犧牲性能。此外,contains?也可以省略,但在這種情況下,可讀性的增加是有爭議的。

(defn distance [src target h] 
    (let [arr (filter #(not= % nil) (map #(distance % target h) (h src)))] 
    (cond (= src target) 0 
      (nil? (h src)) nil 
      ((h src) target) 1 
      (empty? arr)  nil 
      :else   (+ 1 (apply min arr))))) 
+0

'filter'可能是懶惰的,但如果你給一個分塊以次來'map'那麼它會在評估時實現第一個塊。可能最好避免這種遞歸調用 - 如果圖中存在循環,可能會造成堆棧崩潰。 – Alex 2013-02-12 19:41:41

+0

沒關係 - 'map'在lazy-seq裏面做了一切。所以它應該是安全的 - 只是當你把第一個元素,它實現了第一個塊。 – Alex 2013-02-12 19:52:02

1

沒有任何的算法變化可以縮短你的代碼頗有幾分(評論在線):

(defn distance [src target h] 
    (if (= src target) 
    0 
    (when (h src) ; nil is "falsy" so no need to check for it. 
        ; when's else evaluates to nil 
     (if (includes? (h src) target) 
     1 
     (let [arr (keep #(distance % target h) (h src))] ; keep is same as map but drops nils 
      (when-not (empty? arr) ; Same as above. Also empty? returns true or false. 
      (inc (apply min arr)))))))) ; inc is used to increment by one 

這可以進一步縮短:

(let [arr (keep #(distance % target h) (h src))] 
    (when-not (empty? arr) 
    (inc (apply min arr)))) 

這樣:

(when-let [arr (seq (keep #(distance % target h) (h src)))] 
    (inc (apply min arr))) 

因爲seq對空集合返回nil

正如其他人所說的,現在使用Contrib並不是一個好主意。

+0

應該不是第一個'when-not'是'when'而是?假設情況如此,重複'(h src)'就是要求'when-let'。 – Alex 2013-02-12 21:23:48

+0

@Alex真的,我的錯誤,我會解決它。 – ponzao 2013-02-12 22:38:37

+0

謝謝,你太棒了。這樣,我不需要改變我現有的代碼。另外,我相信你在第一次之後添加了太多括號。 :) – mzdravkov 2013-02-12 22:51:07

0

另一個建議。計算所有的路由,然後計算最小距離:

(defn routes 
    ([src target m] 
    (if (= src target) 
     [[]] 
     (seq (routes src target m [])))) 
    ([src target m so-far] 
    (if-let [near (get m src)] 
     (if (contains? near target) 
     [(conj so-far target)] 
     (mapcat #(routes % target m (conj so-far %)) near))))) 

(defn min-distance [src target m] 
    (if-let [all-routes (routes src target m)] 
    (apply min (map count all-routes)))) 
1

如果你感覺SEQ-Y:

(defn distance [src target h] 
    (if (= src target) 
    0 
    (->> src h 
     (keep #(distance % target h)) 
     (map inc) 
     (reduce #(if %1 (min %1 %2) %2) nil))))