Как вернуть карту clojure с фиксированными ключами и условными значениями? - PullRequest
6 голосов
/ 08 июня 2011

У меня есть функция, которая возвращает карту.Ключи статические, но значения условные.Например:

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

5 голосов
/ 08 июня 2011

Еще один вариант, о котором можно подумать, это использовать 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})))
4 голосов
/ 08 июня 2011

Как насчет этого:

(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 голосов
/ 08 июня 2011

Если посмотреть на то, что вы спрашиваете, я бы сказал, что один из способов сделать это - создать функцию, которая выглядит примерно так:

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

, где x - аргумент для всех ссылок первого упоминанияfunction и 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)])))

или в стиле pointfree:

(def pred-map (comp (partial into {}) (partial map (fn [[k [p tv fv]]] [k (if p tv fv)]))))
1 голос
/ 12 июня 2011

Еще один способ сделать это: -)

(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)))
...)
0 голосов
/ 12 июня 2011

Бесстыдно воруя чужие идеи.

(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))))
0 голосов
/ 08 июня 2011

Ваш первый пример кода кажется мне наиболее читаемым. Читаемость часто предпочтительнее, чем эффективность, если только у вас нет критически важной части вашего кода. Но вот способ оценить ваши условия только один раз. Я очень сомневаюсь, что его производительность сильно влияет на ваш код. С точки зрения элегантности я все же предпочитаю ваш первый пример, так как яснее сразу увидеть, что такое пары ключ-значение.

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