如何使用固定键和条件值返回clojure贴图?

时间:2011-06-08 05:10:11

标签: clojure idiomatic

我有一个返回地图的函数。键是静态的,但值是有条件的。像这样:

(defn map-returning-function [x y]
  {:a (if (some-test-fn x)  "one value" "other value"
   :b (if (some-test-fn x)   15         25
   :c (if (different-test y) :one       :two})

是否有更优雅的方法来实现这一点,而无需为每个值编写if测试?我能想到的另一种方式是

(defn another-map-returning-function [x y]
  (if (some-test-fn x)
    {:a "one value",  :b 15, :c (if (different-test y) :one :two)}
    {:a "other value" :b 25, :c (if (different-test y) :one :two)}))

这对我来说似乎没有那么好,因为它重复了条件的每个分支的键名,并重复了different-test上的函数调用。天堂禁止我需要cond而不是if

6 个答案:

答案 0 :(得分:5)

可以想到的另一个变体是使用merge。一个案例用作默认值,根据函数参数进行修改。通过这种方式,您可以轻松地对测试进行分组,并在必要时添加一些注释。

(defn map-returning-function
  [x y]
  (merge {:a "other value"
          :b 25
          :c :two}
         (when (some-test-fn x)
           {:a "one value"
            :b 15})
         (when (different-test y)
           {:c :one})))

另一种方式,取决于您认为是默认值。

(defn map-returning-function
  [x y]
  (merge {:a "one value"
          :b 15
          :c :one}
         (when-not (some-test-fn x)
           {:a "other value"
            :b 25})
         (when-not (different-test y)
           {:c :two})))

答案 1 :(得分:4)

这个怎么样:

(let [test-x (some-test x) test-y (some-test y)] 
  (conj 
    (if test-x {:a "one value" :b 15} {:a "other value" :b 25}) 
    (if test-y {:c :one} {:c :two})))

条件在一个地方执行一次,然后在另一个地方使用。取决于背景和个人偏好。更像你的例子可能更干净:

(let [test-x (some-test x) test-y (some-test y)] 
  {:a (if test-x "one value" "other value")
   :b (if test-x 15 25)
   :c (if test-y :one :two)})

答案 2 :(得分:2)

看看你问的问题我会说一种方法就是构建一个看起来像这样的函数:

(pred-map 
  {:a [some-test-fn "one-value" "other-value"] 
   :b [some-test-fn 15 25] 
   :c [different-test :one :two]} 
  x 
  y)

其中x是第一个menti0oned函数的所有引用的参数,y是第二个函数的参数

实现这一目标的方法可能如下:

(defn get-value [p tv fv a]
  (if (p a) tv fv))

(defn get-predicate-set [m]
  (set (map first (vals m))))

(defn get-arg-map [m args]
  (zipmap (get-predicate-set m) args))

(defn get-arg [p m args]
   ((get-arg-map m args) p)) 

(defn get-key-value-pair-creator [m args]
  (fn [[k [p tv fv]]]
    [k
     (get-value 
       p
       tv
       fv 
       (get-arg p m args))]))


(defn pred-map [m & args]
  (into {}
    (map 
      (get-key-value-pair-creator m args)
      m)))

然而,这些函数依赖于通过相等映射到函数的参数(它似乎与引用一起使用),因此它不会将两个相同的匿名函数理解为相同的函数。

如果你不介意重复参数,你可以创建一个更简单的函数,如下所示:

(pred-map
  {:a [(some-test-fn x) "one value" "other-value"]
   :b [(some-test-fn x) 15 25]
   :c [(different-test y) :one :two]})

遵循简单的功能:

(defn pred-map [m] (into {} (for [[k [p tv fv]] m] [k (if p tv fv)])))

或以无点样式:

(def pred-map (comp (partial into {}) (partial map (fn [[k [p tv fv]]] [k (if p tv fv)]))))

答案 3 :(得分:1)

另一种方法: - )

(defmulti map-returning-function 
    (fn [x y] [ (some-test-fn x) (different-test y) ]))

(let [x-values {true  {:a "one value" :b 15}
                false {:a "other value" :b 25}}
      y-values {true {:c :one} false {:c :two}}]

  (defmethod map-returning-function [false false] [x y]
     (merge (x-values false) (y-values false)))

  (defmethod map-returning-function [true true] [x y]
     (merge (x-values true) (y-values true)))
...)

答案 4 :(得分:0)

您的第一个代码示例对我来说似乎最具可读性。可读性通常优于效率,除非您的代码具有性能关键部分。但这是一种只评估你的条件一次的方法。我非常怀疑它的性能与你的代码有很大不同。在优雅方面,我仍然更喜欢你的第一个例子,因为直接看到键值对是更清楚的。

(defn another-map-returning-function [x y]
  (let [first-map (if (some test-fn x) {:a "one value" :b 15}
                                       {:a "other value" :b 25})]
    (assoc first-map :c
                        (if (different-test y) :one :two))))

答案 5 :(得分:0)

通过无耻地窃取他人的想法。

(defn map-returning-function [x y]
  (let [x-values  {true  {:a "one value" :b 15}
                   false {:a "other value" :b 25}}
        y-values  {true {:c :one} false {:c :two}}
        x-key     (some-test-fn x)
        y-key     (different-test y) ]
    (merge (x-values x-key) (y-values y-key))))