【问题标题】:How do I return a clojure map with fixed keys and conditional values?如何返回具有固定键和条件值的 clojure 映射?
【发布时间】:2011-09-10 14:15:13
【问题描述】:

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

(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

【问题讨论】:

  • 您当然可以编写一些巧妙的代码来消除 if.... 但是,为了避免输入三四个字符,是否真的值得额外的间接和/或复杂性?这可能是您应该应用 KISS 原则的情况。
  • @mikera:在这种情况下我同意你的看法,但我理解我简化了这个例子,以使其作为问题的一部分更清晰。我有点希望有人能搞定一些疯狂的 macro'n 技能并告诉我它是如何完成的。

标签: clojure idioms


【解决方案1】:

人们可以想到的另一种变体是使用merge。一种情况作为默认值,根据函数参数进行修改。通过这种方式,您可以轻松地对测试进行分组,并在必要时使用一些 cmets 来增加趣味。

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

【讨论】:

  • 不错!我曾想过类似的方法,但很高兴看到它实现了。如果有一个自然的默认地图,我认为这是有道理的。
【解决方案2】:

这个怎么样:

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

【讨论】:

  • @stand,假设您要定义很多这样的条件映射,我建议您编写一个宏来在这个答案中生成后一个代码。这将提供最高的效率和最高的可读性。该宏将接受一个四元组列表、一个键、test-fn、参数、如果为真则为值、如果为假则为值。
  • 是的,我目前的方法就像你的第二个例子一样。我没有考虑过使用conj,所以我必须尝试一下(或者可能是merge)。我怀疑可能需要一个宏,所以我很高兴听到@bmillare 同意。思考一个广义的宏观解决方案。
  • @stand 这真的取决于范围。我可以想象在某些情况下这甚至会导致逻辑编程和规则引擎。 :-) 宏是朝这个方向迈出的一步。
【解决方案3】:

看看你在问什么,我想说一种方法是构造一个看起来像这样的函数:

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

其中 x 是第一个提到的函数的所有引用的参数,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)]))))

【讨论】:

    【解决方案4】:

    另一种方法:-)

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

    【讨论】:

      【解决方案5】:

      您的第一个代码示例对我来说似乎最具可读性。可读性通常比效率更可取,除非您的代码有性能关键部分。但这是一种只评估一次条件的方法。我高度怀疑它的性能对您的代码有很大影响。就优雅而言,我仍然更喜欢您的第一个示例,因为直接查看键值对是什么更清楚。

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

      【讨论】:

        【解决方案6】:

        无耻地窃取别人的想法。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-10-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多