Как отфильтровать коллекцию карт в группы по карте по значению?

#clojure #clojurescript

Вопрос:

Допустим, у меня есть коллекция, подобная:

 (def xs 
  [{:name "Apple" :type "Fruit is a type"} 
  {:name "Tomato" :type "Vegetable are food"} 
  {:name "Pear" :type "the type can also be Fruit"} 
  {:name "Steak" :type "eat less Meat"}])
 

И я хочу отфильтровать и сгруппировать по коллекции во что-то вроде этого:

 {:Fruit [{:name "Apple" :type "Fruit is a type"} {:name "Pear" :type "the type can also be Fruit"}] :Vegetable [{:name "Tomato" :type "Vegetable are food"}]
 

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

 (defn filter-response [x query]
  (filter #(s/includes? (:type %) query) x))

(defn group-by-types [queries]
  (map #(filter-response xs %) queries))

(group-by-types ["Fruit" "Vegetable"])
 

Как я могу этого добиться?

Ответ №1:

Обновленный Ответ

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

 (defn- all-occurrences [xs patterns]
  (for [x xs
        pattern patterns
        :when (clojure.string/includes? (:type x) pattern)]
    [(keyword pattern) x]))
 

Или с помощью вашей filter-response функции:

 (defn- all-occurrences [xs patterns]
  (for [pattern patterns
        x (filter-response xs pattern)]
    [(keyword pattern) x]))
 

Затем используйте сокращение с обновлением, чтобы объединить список вхождений в единую карту:

 (defn group-by-patterns [xs patterns]
  (reduce (fn [m [pattern text]] (update m pattern conj text))
          {}
          (all-occurrences xs patterns)))
 

Вызов его с новым вводом:

 (def xs
  [{:name "Apple" :type "Fruit is a type"}
   {:name "Tomato" :type "Vegetable are food"}
   {:name "Pear" :type "the type can also be Fruit"}
   {:name "Steak" :type "eat less Meat"}])

(group-by-patterns xs ["Fruit" "Vegetable"])
=> {:Fruit ({:name "Pear", :type "the type can also be Fruit"} {:name "Apple", :type "Fruit is a type"}),
    :Vegetable ({:name "Tomato", :type "Vegetable are food"})}
 

Оригинальный Ответ

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

 (def xs
   [{:name "Apple" :type "Fruit"}
    {:name "Tomato" :type "Vegetable"}
    {:name "Pear" :type "Fruit"}
    {:name "Steak" :type "Meat"}])

erdos=> (group-by :type xs)
{"Fruit" [{:name "Apple", :type "Fruit"} {:name "Pear", :type "Fruit"}], 
 "Vegetable" [{:name "Tomato", :type "Vegetable"}],
 "Meat" [{:name "Steak", :type "Meat"}]}
 

Затем используйте клавиши выбора для фильтрации ключей:

 erdos=> (select-keys (group-by :type xs) ["Fruit" "Vegetable"])
{"Fruit" [{:name "Apple", :type "Fruit"} {:name "Pear", :type "Fruit"}], 
 "Vegetable" [{:name "Tomato", :type "Vegetable"}]}
 

Если вам нужны ключи ключевых слов, вам нужен дополнительный шаг сопоставления:

 erdos=> (into {}
              (for [[k v] (select-keys (group-by :type xs) ["Fruit" "Vegetable"])]
                     [(keyword k) v]))
{:Fruit [{:name "Apple", :type "Fruit"} {:name "Pear", :type "Fruit"}], 
 :Vegetable [{:name "Tomato", :type "Vegetable"}]}
 

Комментарии:

1. Оооо, это хороший ответ.

2. Я понимаю, что упустил очень важную деталь: поиск :type ключа основан на сопоставлении строк. Мои фактические данные выглядят так: {:type "Fruit is a type"}, {:type "The type can also be Fruit} и т. Д. Вот почему у меня есть clojure.string/включает. Я постараюсь включить это в решение.

3. о, в этом есть смысл. Я обновил свой ответ, чтобы отразить изменения.

4. Еще раз спасибо! Отличные ответы