Условная инициализация элементов карты в Clojure - PullRequest
24 голосов
/ 24 января 2012

Я ищу лучший способ условно избежать добавления элемента на карту, когда он инициализируется / определяется.В этом случае я хочу избежать добавления элемента на карту, если значение ключа равно nil.

(defn create-record [data]
  (let [res {
    :username (data :username)
    :first-name (get-in data [:user-info :name :first])
    :last-name (get-in data [:user-info :name :last])
    :gender (get-in data [:user-info :sex])
   }])
)

Я не хочу добавлять пол на карту, если результаты get-в ноль (поле данных в данных не существует).Есть ли способ сделать это, когда я создаю карту?Я мог бы удалить все ключи, значение которых равно nil, после создания карты, но в некоторых случаях я хочу, чтобы некоторые ключи имели нулевые значения, а другие вообще не были бы на карте, если бы они имели нулевые значения.

Ответы [ 7 ]

20 голосов
/ 25 января 2012

Я бы использовал комбинацию merge и when-let для этих необязательных параметров.

Основная идея состоит в том, чтобы объединить либо карту одного элемента, либо nil для каждого из необязательных параметров. Слияние в ноль ничего не изменит, поэтому вы не увидите ноль на карте.

(defn create-record [data]
  (let [res (merge {:username (data :username)
                    :first-name (get-in data [:user-info :name :first])
                    :last-name (get-in data [:user-info :name :last])}
                   (when-let [gender (get-in data [:user-info :sex])]
                     {:gender gender}))]
    res))

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

1 голос
/ 05 октября 2018
(defn create-record [data]
  (let [gender (get-in data [:user-info :sex])]
    (->> {:username (data :username)
          :first-name (get-in data [:user-info :name :first])
          :last-name (get-in data [:user-info :name :last])}
         (#(if gender (assoc % :gender gender) %)))))
1 голос
/ 25 января 2012

Вы могли бы сделать что-то вроде

(let [not-nils #{:gender}]
  (defn create-record [data]
    (into {} (for [[k v] {:username (data :username)
                          :first-name (get-in data [:user-info :name :first])
                          :last-name (get-in data [:user-info :name :last])
                          :gender (get-in data [:user-info :sex])}
                   :when (not (and (nil? v) (not-nils k)))]
               [k v]))))
1 голос
/ 25 января 2012

Построение карты и dissoc использование ключей, на которые вы хотите наложить условия на основе предиката (здесь - nil?), может быть самым простым подходом (NB. Эта функция только проверяет ключи, явно упомянутые в качестве аргументов;те, которые не упомянуты, никогда не удаляются, независимо от того, соответствуют ли значения, прикрепленные к ним, предикату или нет):

(defn dissoc-when
  "Dissoc those keys from m which are mentioned among ks and whose
   values in m satisfy pred."
  [pred m & ks]
  (apply dissoc m (filter #(pred (m %)) ks)))

При REPL:

user> (dissoc-when nil? {:foo nil :bar true :quux nil} :foo :bar)
{:quux nil, :bar true}

Хотя, вообще говоря, если вы ожидаете, чтоработать с большим количеством карт, представляющих сущности реального мира определенного типа, вы можете перейти к записям - и тогда вы можете просто пропустить все nil s на этапе, на котором вы извлекаете значения из входной карты, потому что записи,при просмотре в виде карт всегда отображаются ключи, соответствующие их полям.Например,

(defrecord Person [username first-name last-name])

Затем вы можете выделить логику для "преобразования схемы" между картами:

(defn translate-map
  "Transforms the data map in accordance with the spec in table.
   Skips nil-valued entries."
  [data table]
  (->> table
       (keep (fn [[to from]]
               (when-let [from-val (get-in data from)]
                 [to from-val])))
       (into {})))

Теперь ваша функция create-record становится композицией translate-map и map->Person:

(defn create-person [data]
  (map->Person
   (translate-map data {:username [:username]
                        :first-name [:user-info :name :first]
                        :last-name [:user-info :name :last]
                        :gender [:user-info :sex]})))

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

(defn create-person [data]
  (merge (zipmap [:username :first-name :last-name] (repeat nil))
         (translate-map data {:username [:username]
                              :first-name [:user-info :name :first]
                              :last-name [:user-info :name :last]
                              :gender [:user-info :sex]})))

На REPL (версия записи в Clojure 1.3):

user> (create-person {:username "jsmith"
                      :user-info {:name {:first "John" :last "Smith"}}})
#user.Person{:username "jsmith", :first-name "John", :last-name "Smith"}
user> (create-person {:username "jsmith"
                      :user-info {:name {:first "John" :last "Smith"}
                                  :sex :male}})
#user.Person{:username "jsmith", :first-name "John", :last-name "Smith", :gender :male}
0 голосов
/ 14 июня 2019
(cond-> {:username (data :username)
     :first-name (get-in data [:user-info :name :first])
     :last-name (get-in data [:user-info :name :last])}
    (get-in data [:user-info :sex]) (assoc :gender (get-in data [:user-info :sex])))
0 голосов
/ 25 января 2012

Вы можете определить свои поля и какие из них являются необязательными:

(def fields
[[:username   [:username]]
 [:first-name [:user-info :name :first]]
 [:sex        [:user-info :sex]          true]])

, а затем написать функцию для использования этой информации:

(defn create-record [data keys]
  (->>
    (for [[n k ignore-nil?] keys 
            :let [v (get-in data k)] 
            :when (or (not ignore-nil?) v)]
      [n v])
    (into {})))

и будет работать так:

; If :sex is missing don't create a field
user=> (create-record {:username "dr" :user-info { :name {:first "Dave"} }} fields)
{:username "dr", :first-name "Dave"}

user=> (create-record {:username "dr" :user-info { :name {:first "Dave"} :sex :m }} fields)
{:username "dr", :first-name "Dave", :sex :m}

; If :first is missing, create a nil field
user=> (create-record {:username "dr" :user-info { :name {} :sex :m }} fields)
{:username "dr", :first-name nil, :sex :m}

Изменить при необходимости:)

0 голосов
/ 25 января 2012

Вот попытка:

(defn exclude-nils-for [m kw-set] 
    (apply hash-map (apply concat (remove (fn [[k v]] (and (kw-set k) (nil? v))) m))))

Тест:

user> (exclude-nils-for {:gender "m" :name "Thomas" :age "24"} #{})
{:age "21", :gender "m", :name "Thomas"}
user> (exclude-nils-for {:gender "m" :name "Thomas" :age "24"} #{:name})
{:age "21", :gender "m", :name "Thomas"}
user> (exclude-nils-for {:gender "m" :name nil :age "24"} #{:name})
{:age "21", :gender "m"}
user> (exclude-nils-for {:gender "m" :name nil :age nil} #{:age})
{:gender "m", :name nil}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...