【问题标题】:More idiomatic and elegant way of Clojure functionClojure 函数的更惯用和优雅的方式
【发布时间】:2013-01-28 04:45:01
【问题描述】:

我有一个函数可以找到图中节点之间的最小距离,用 Ruby 编写。我将它翻译成 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 的方式吗?那些嵌套的 if 很糟糕。

【问题讨论】:

    标签: function clojure refactoring idioms


    【解决方案1】:

    如果您使用集合而不是向量并使用 cond 而不是嵌套的 ifs,至少在我看来,它更像 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 生成一个惰性序列允许通过删除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)))))
      

      【讨论】:

      • filter 可能是懒惰的,但如果你给map 一个分块序列,那么它会在评估时实现第一个块。对于这个递归调用,最好避免这种情况 - 如果图中存在循环,可能会破坏堆栈。
      • 没关系 - maplazy-seq 内完成所有操作。所以它应该是安全的——只是当你取第一个元素时,它实现了第一个块。
      【解决方案3】:

      如果你感觉 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))))
      

      【讨论】:

        【解决方案4】:

        无需任何算法更改,您就可以大大缩短您的代码(cmets inline):

        (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 并不是一个好主意。

        【讨论】:

        • 第一个when-not不应该是when吗?假设是这种情况,(h src) 的重复是在乞求when-let
        • @Alex 是的,我的错,我会改正的。
        • 谢谢,你太棒了。这样,我不需要更改我现有的代码。另外,我相信您在第一个 when 之后添加了太多括号。 :)
        • @Wintre,是的,看来我做到了,我显然需要睡觉,我也会解决这个问题 :)
        【解决方案5】:

        另一个建议。计算所有路线,然后计算最小距离:

        (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))))
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-12-08
          • 1970-01-01
          • 2021-05-27
          • 1970-01-01
          • 1970-01-01
          • 2014-03-12
          • 2016-03-17
          • 2011-05-12
          相关资源
          最近更新 更多