Подтвердить что ты не робот

Инициализация элементов карты условно в Clojure

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

(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-in равны nil (поле пола в данных не существует). Есть ли способ сделать это, когда я создаю карту? Я могу удалить все ключи, значение которых равно nil после создания карты, но в некоторых случаях я хочу, чтобы некоторые ключи имели значения nil и другие, чтобы они вообще не были на карте, если бы они имели значения nil.

4b9b3361

Ответ 1

Я использовал бы комбинацию 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))

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

Ответ 2

Создание карты и 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 на том этапе, где вы извлекайте значения из вашей входной карты, потому что записи, рассматриваемые как карты, всегда, похоже, содержат ключи, соответствующие их полям. Например.

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

Ответ 3

Вы можете сделать что-то вроде

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

Ответ 4

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

Ответ 5

Вот попытка:

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

Ответ 6

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

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

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

Ответ 7

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