如何在 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-fn
、every-pred
和 transducers
之后,我没有得到我需要的东西,例如过滤谓词中的正则表达式,我想现在是缺乏知识。
所以,我最终得到了这个工作正常的函数,但代码很丑陋且重复。我怎样才能摆脱代码重复?
(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"})
假设我们有这样一个数据结构:
(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-fn
、every-pred
和 transducers
之后,我没有得到我需要的东西,例如过滤谓词中的正则表达式,我想现在是缺乏知识。
所以,我最终得到了这个工作正常的函数,但代码很丑陋且重复。我怎样才能摆脱代码重复?
(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"})