如何在 Clojure 中通过多个键过滤映射向量

How to filter vector of maps by multiple keys in Clojure

假设我们有这样一个数据结构:

(def data
     (atom [{:id 1 :first-name "John1" :last-name "Dow1" :age "14"}
            {:id 2 :first-name "John2" :last-name "Dow2" :age "54"}
            {:id 3 :first-name "John3" :last-name "Dow3" :age "34"}
            {:id 4 :first-name "John4" :last-name "Dow4" :age "12"}
            {:id 5 :first-name "John5" :last-name "Dow5" :age "24"}]))

一键筛选我学会了,例如:

(defn my-filter
  [str-input]
  (filter #(re-find (->> (str str-input)
                         (lower-case)
                         (re-pattern))
                         (lower-case (:first-name %)))
            @data))

> (my-filter "John1")
> ({:last-name "Dow1", :age "14", :first-name "John1", :id 1})

但现在我有点困惑如何通过:first-name:last-name:age简单的方法过滤数据?

更新:很抱歉在解释问题时不够清楚...实际上,我想要所有键 :first-name:last-name:age参与过滤功能,这样,如果str-input不匹配:first-name的val,检查它是否匹配:last-name的val等等。

更新 2: 在尝试 some-fnevery-predtransducers 之后,我没有得到我需要的东西,例如过滤谓词中的正则表达式,我想现在是缺乏知识。 所以,我最终得到了这个工作正常的函数,但代码很丑陋且重复。我怎样才能摆脱代码重复?

(defn my-filter [str-input]
  (let [firstname (filter #(re-find (->> (str str-input)
                                         (upper-case)
                                         (re-pattern))
                                    (upper-case (:first-name %)))
                     @data)
        lastname (filter #(re-find (->> (str str-input)
                                        (upper-case)
                                        (re-pattern))
                                   (upper-case (:last-name %)))
                    @data)
        age (filter #(re-find (->> (str str-input)
                                   (upper-case)
                                   (re-pattern))
                              (upper-case (:age %)))
               @data)]
    (if-not (empty? firstname)
      firstname
      (if-not (empty? lastname)
        lastname
        (if-not (empty? age)
          age)))))

我认为这对你有用。使用 Clojure 中的事实 :first-name 是一个函数,可用于在哈希图中查找其对应的值。

(defn find-all 
  [field value data] 
  (filter #(= value (field %)) data))

这将 return 向量中匹配哈希图的列表。

user=> (find-all :first-name "John1" @data)
({:id 1, :first-name "John1", :last-name "Dow1", :age "14"})

我建议您将年龄存储为整数而不是字符串,如果您没有充分理由不这样做的话。

关于关键字的更多信息:

(:key map)

  • 当您的地图为零时有效
user=> (:key-word nil)
nil
  • 可以与地图或过滤器一起使用
user=> (map :last-name @data)
("Dow1" "Dow2" "Dow3" "Dow4" "Dow5")
  • :key不能为nil
user=> (nil (first @data))
CompilerException java.lang.IllegalArgumentException: Can't call nil, form: (nil (first (clojure.core/deref data))),

compiling:(/private/var/folders/nr/g50ld9t91c555dzv91n43bg40000gn/T/form-init5403593628725666667.clj:1:1)

(地图:键)

  • 当 :key 为 nil 时更好
user=> ((first @data) nil)
nil

这也可以借助功能组合来实现,例如您可以使用 every-pred 函数,它创建一个函数,检查所有 preds 的参数是否真实,并使用它来过滤数据。例如,如果你想找到所有奇数 :id 值具有 :last-name"Dow1", "Dow2","Dow3":age</code> 开头的所有项目:</p> <pre><code>user> (def data [{:id 1 :first-name "John1" :last-name "Dow1" :age "14"} {:id 2 :first-name "John2" :last-name "Dow2" :age "54"} {:id 3 :first-name "John3" :last-name "Dow3" :age "34"} {:id 4 :first-name "John4" :last-name "Dow4" :age "12"} {:id 5 :first-name "John5" :last-name "Dow5" :age "24"}]) user> (filter (every-pred (comp odd? :id) (comp #{"Dow1" "Dow2" "Dow3"} :last-name) (comp #{} first :age)) data) ;;=> ({:id 3, :first-name "John3", :last-name "Dow3", :age "34"})

另一种方法是使用 transducers:

user> (sequence (comp (filter (comp odd? :id))
                      (filter (comp #{"Dow1" "Dow2" "Dow3"} :last-name)))
                data)

请注意,实际过滤只会对每个项目进行一次,因此不会创建任何中间集合。

更新

根据您的更新,当 任何 谓词为真时,您需要保留该值,因此您可以使用 some 函数而不是 every-pred :

user> (filter #(some (fn [pred] (pred %))
                     [(comp odd? :id)
                      (comp #{"Dow1" "Dow2" "Dow4"} :last-name)
                      (comp (partial = ) first :age)])
              data)
;;=> ({:id 1, :first-name "John1", :last-name "Dow1", :age "14"} {:id 2, :first-name "John2", :last-name "Dow2", :age "54"} {:id 3, :first-name "John3", :last-name "Dow3", :age "34"} {:id 4, :first-name "John4", :last-name "Dow4", :age "12"} {:id 5, :first-name "John5", :last-name "Dow5", :age "24"})