Идиоматический способ проверки того, имеет ли ключ на карте значение

Каков идиоматический способ проверки того, имеет ли ключ на карте значение? Например, если у нас есть:

=> (def seq-of-maps [{:foo 1 :bar "hi"} {:foo 0 :bar "baz"}])

Чтобы узнать все карты с помощью: foo == 0, мне нравится:

=> (filter (comp zero? :foo) seq-of-maps)
({:foo 0, :bar "baz"})

Но если я хочу найти все карты с: bar == "hi", лучшее, что я могу придумать:

=> (filter #(= (:bar %) "hi") seq-of-maps)
({:foo 1, :bar "hi"})

который я не считаю очень удобочитаемым. Есть ли лучший/более идиоматический способ сделать это?

Ответ 1

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

(def seq-of-maps [{:foo 1 :bar "hi"} {:foo 0 :bar "baz"}])

(defn has-value [key value]
  "Returns a predicate that tests whether a map contains a specific value"
  (fn [m]
    (= value (m key))))

(filter (has-value :bar "hi") seq-of-maps)
=> ({:foo 1, :bar "hi"})

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

Ответ 2

Идиоматик субъективен, но я бы сделал

=> (filter (comp #{"hi"} :bar) seq-of-maps)

или что вы сделали.

Ответ 3

clojure.set/index можно также использовать здесь

((index seq-of-maps [:foo]) {:foo 0})
((index seq-of-maps [:bar]) {:bar "hi"})

Если вы хотите, вы можете обернуть его в функцию

(defn select-maps [xrel m]
   ((index xrel (keys m)) m))

затем

(select-maps seq-of-maps {:foo 0})
(select-maps seq-of-maps {:bar "hi"})

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

(select-maps seq-of-maps {:foo 0 :bar "baz"}) 

выбирает все карты, содержащие foo 0 и bar "baz"

Ответ 4

user> (def seq-of-maps [{:foo 1 :bar "hi"} {:foo 0 :bar "baz"}])
#'user/seq-of-maps
user> (filter #(-> % :bar (= "hi")) seq-of-maps)
({:foo 1, :bar "hi"})

Как говорит Пепийн, я думаю, что идиоматические способы варьируются в зависимости от личных мнений. Я иногда использую макрос -> для извлечения вложенных круглых скобок.

Ответ 5

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

Ответ 6

Ваш код выглядит хорошо для меня. Другим возможным решением может быть использование макроса for, как показано ниже.

(for [m seq-of-maps 
      :let [v (:bar m)] 
      :when (= v "hi")] 
   m)

Ответ 7

Просто замените zero? на (partial = "hi") следующим образом:

=> (filter (comp (partial = "hi") :bar) seq-of-maps)