[英]How to filter vector of maps by multiple keys in Clojure
Assume we have a datastructure like this one:假设我们有一个这样的数据结构:
(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"}]))
I have learned how to filter it by one key, for example:我已经学会了如何一键过滤,例如:
(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})
But now I'm a little bit confused on how to filter data by :first-name
, :last-name
and :age
simple way?但是现在我对如何通过
:first-name
、 :last-name
和:age
简单方法过滤数据感到有些困惑?
Update : Sorry for being not too clear enough in explanation of what the problem is... Actually, I want all keys :first-name
, :last-name
and :age
to paticipate in filter function, so that, if str-input
doesn't match :first-name
's val, check if it matches :last-name
's val and so on.更新:抱歉,在解释问题时不够清楚……实际上,我希望所有键
:first-name
、 :last-name
和:age
都能参与过滤功能,这样,如果str-input
不匹配:first-name
的 val,检查它是否匹配:last-name
的 val 等等。
Update 2 : After trying some-fn
, every-pred
and transducers
, I didn't get what I need, eg regex in filter predicates, I guess it's a lack of knowledge for now.更新 2 :在尝试
some-fn
、 every-pred
和transducers
,我没有得到我需要的东西,例如过滤谓词中的正则表达式,我想这是现在缺乏知识。 So, I ended up with this function which works fine, but the code is ugly and duplicated.所以,我最终得到了这个工作正常的函数,但代码很丑陋且重复。 How I can get rid of code duplication?
我怎样才能摆脱代码重复?
(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)))))
I think this would just work for you.我认为这对你有用。 Using the fact that in Clojure :first-name is a function that can be used to look up its corresponding value in a hashmap.
使用 Clojure 中的 :first-name 是一个函数,可用于在哈希图中查找其对应的值。
(defn find-all
[field value data]
(filter #(= value (field %)) data))
This will return a list of the matching hashmaps in your vector.这将返回向量中匹配哈希图的列表。
user=> (find-all :first-name "John1" @data)
({:id 1, :first-name "John1", :last-name "Dow1", :age "14"})
I would suggest you to store age as integer instead of string if you do not have a strong case not to.如果您没有强烈的理由不这样做,我建议您将年龄存储为整数而不是字符串。
More about keywords:有关关键字的更多信息:
(:key map) (:键映射)
user=> (:key-word nil) nil
user=> (map :last-name @data) ("Dow1" "Dow2" "Dow3" "Dow4" "Dow5")
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)
编译:(/private/var/folders/nr/g50ld9t91c555dzv91n43bg40000gn/T/form-init5403593628725666667.clj:1:1)
(map :key) (地图:键)
user=> ((first @data) nil) nil
This can also be achieved with the help of functional composition, eg you can use every-pred
function, which creates a function, checking if all the preds are truthy for its arguments, and use it to filter data.这也可以在函数组合的帮助下实现,例如您可以使用
every-pred
函数,它创建一个函数,检查所有 pred 的参数是否为真,并使用它来过滤数据。 For example if you want to find all items with odd :id
value having :last-name
of "Dow1", "Dow2",
or "Dow3"
and :age
starting with \\3
:例如,如果您想查找所有奇数
:id
值的项目,其:last-name
为"Dow1", "Dow2",
或"Dow3"
并且:age
以\\3
开头:
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 #{\3} first :age))
data)
;;=> ({:id 3, :first-name "John3", :last-name "Dow3", :age "34"})
another way to do it, is to use transducers :另一种方法是使用传感器:
user> (sequence (comp (filter (comp odd? :id))
(filter (comp #{"Dow1" "Dow2" "Dow3"} :last-name)))
data)
notice that the actual filtering would happen just once for every item, so it won't create any intermediate collections.请注意,实际过滤对于每个项目只会发生一次,因此它不会创建任何中间集合。
Update更新
According to your update you need to keep the value when any of the predicates is true, so you can use some
function instead of every-pred
:根据您的更新,当任何谓词为真时,您需要保留该值,因此您可以使用
some
函数而不是every-pred
:
user> (filter #(some (fn [pred] (pred %))
[(comp odd? :id)
(comp #{"Dow1" "Dow2" "Dow4"} :last-name)
(comp (partial = \3) 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"})
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.