当值具有复合数据时,在 Clojure 中按值过滤哈希图
Filtering a hashmap by value in Clojure when values have compound data
我正在自学 Clojure。
对于与工作相关的项目(显而易见,我不是专业程序员),我正在尝试合并一堆电子表格。电子表格包含与金融交易相关的评论。多个评论(包括跨电子表格)可以引用同一笔交易;每笔交易都有一个唯一的序列号。因此,我使用以下数据结构来表示电子表格:
(def ss { :123 '([ "comment 1" "comment 2" ]
[ "comment 3" "comment 4" ]
[ "comment 5" ]),
:456 '([ "happy days" "are here" ]
[ "again" ])})
这可能是从以下两个电子表格创建的:
+------------+------------+-----------+
| Trans. No. | Cmt. A | Cmt. B |
+------------+------------+-----------+
| 123 | comment 1 | comment 2 |
| 456 | happy days | are here |
| 123 | comment 3 | comment 4 |
+------------+------------+-----------+
+-----------------+------------+
| Analyst Comment | Trans. No. |
+-----------------+------------+
| comment 5 | 123 |
| again | 456 |
+-----------------+------------+
我已经成功编写函数来创建这个数据结构,给定一个充满 CSV 的目录。我想再写两个函数:
;; FUNCTION 1 ==========================================================
;; Regex Spreadsheet -> Spreadsheet ; "Spreadsheet" is like ss above
;; Produces a Spreadsheet with ALL comments per transaction if ANY
;; value matches the regex
; (defn filter-all [regex my-ss] {}) ; stub
(defn filter-all [regex my-ss] ; template
(... my-ss))
(deftest filter-all-tests
(is (= (filter-all #"1" ss)
{ :123 '([ "comment 1" "comment 2" ]
[ "comment 3" "comment 4" ]
[ "comment 5" ]) })))
;; FUNCTION 2 ==========================================================
;; Regex Spreadsheet -> Spreadsheet ; "Spreadsheet" is like ss above
;; Produces a Spreadsheet with each transaction number that has at least
;; one comment that matches the regex, but ONLY those comments that
;; match the regex
; (defn filter-matches [regex my-ss] {}) ; stub
(defn filter-matches [regex my-ss] ; template
(... my-ss))
(deftest filter-matches-tests
(is (= (filter-matches #"1" ss)
{ :123 '([ "comment 1" ]) })))
我不明白的是让每个 key
的正则表达式足够深入到 vals
的最佳方法,因为它们是嵌套在列表中的向量中的字符串。我试过将 filter
与嵌套的 apply
s 或 map
s 一起使用,但我对语法感到困惑,即使它有效,我也不知道如何坚持下去keys
为了建立一个新的 hashmap。
我也尝试过在 filter
函数中使用解构,但我自己也很困惑,我还认为我必须 "lift" 嵌套数据中的函数(我认为这是术语——就像 Haskell 中的应用程序和单子一样。
有人可以建议过滤此数据结构的最佳方法吗?作为一个单独的问题,我很乐意就这是否是一个适合我的目的的合理数据结构提供反馈,但我想学习如何解决目前存在的这个问题,即使只是为了学习目的。
非常感谢。
这是您的数据结构的解决方案。
filter
采用谓词函数。进入那个函数,你实际上可以进入数据结构来测试你需要的任何东西。这里,flatten
有助于删除评论向量列表。
(defn filter-all [regex my-ss]
(into {} (filter (fn [[k v]] ; map entry can be destructured into a vector
; flatten the vectors into one sequence
; some return true if there is a match on the comments
(some #(re-matches regex %) (flatten v)))
my-ss)))
user> (filter-all #".*3.*" ss)
{:123 (["comment 1" "comment 2"] ["comment 3" "comment 4"] ["comment 5"])}
对于 filter-matches
逻辑是不同的:您想要使用某些部分的值构建一个 new 地图。 reduce
可以帮助做到这一点:
(defn filter-matches [regex my-ss]
(reduce (fn [m [k v]] ; m is the result map (accumulator)
(let [matches (filter #(re-matches regex %) (flatten v))]
(when (seq matches)
(assoc m k (vec matches)))))
{}
my-ss))
user> (filter-matches #".*days.*" ss)
{:456 ["happy days"]}
对于数据结构本身,如果将嵌套向量保留在每个条目的列表中没有用,可以使用{:123 ["comment1" "comments 2"] ...}
进行简化,但不会大大简化上述功能。
我认为您的方向是正确的,但也许让生活变得比需要的更艰难。
最令人担忧的是您对正则表达式的使用。虽然正则表达式对于某些事情来说是一个很好的工具,但当其他解决方案更好更快时,它们经常被使用。
在 clojure 中采用的关键思想之一是使用您 assemble 一起使用的小型库以获得更高级别的抽象。例如,有各种库可以处理不同的电子表格格式,例如 excel、google docs 电子表格,并且支持处理 CSV 文件。因此,我的第一步是看看您是否可以找到一个将您的 spreadhseet 解析为标准 clojure 数据结构的库。
例如,clojure 的 data.csv 会将 CSV 电子表格处理成惰性向量序列,其中每个向量都是电子表格中的一行,向量中的每个元素都是该行中的一个列值。一旦您拥有该格式的数据,然后使用 map、filter 等对其进行处理。阿尔。相当琐碎。
下一步是考虑使您的处理尽可能简单的抽象类型。这在很大程度上取决于您打算做什么,但我对此类数据的建议是使用由散列图组成的嵌套结构,该结构在外层由您的交易号索引,然后每个值都是一个散列图,它电子表格中的每一列都有一个条目。
{:123 {:cmnta ["comment 1" "comment 3"]
:cmntb ["comment 2" "comment 4"]
:analstcmt ["comment 5"]}
:456 {:cmnta ["happy days"]
:cmntb ["are here"]
:analystcmt ["again"]}}
有了这个结构,您就可以使用像 get-in 和 update-in 这样的函数来 access/change 结构中的值,即
(get-in m [123 :cmnta]) => ["comment 1" "comment 3"]
(get-in m [123 :cmnta 0]) => "comment 1"
(get-in m [456 :cmnta 1]) => nil
(get-in m [456 :cmnta 1] "nothing to see here - move on") => "nothing to see here - move on"
我正在自学 Clojure。
对于与工作相关的项目(显而易见,我不是专业程序员),我正在尝试合并一堆电子表格。电子表格包含与金融交易相关的评论。多个评论(包括跨电子表格)可以引用同一笔交易;每笔交易都有一个唯一的序列号。因此,我使用以下数据结构来表示电子表格:
(def ss { :123 '([ "comment 1" "comment 2" ]
[ "comment 3" "comment 4" ]
[ "comment 5" ]),
:456 '([ "happy days" "are here" ]
[ "again" ])})
这可能是从以下两个电子表格创建的:
+------------+------------+-----------+
| Trans. No. | Cmt. A | Cmt. B |
+------------+------------+-----------+
| 123 | comment 1 | comment 2 |
| 456 | happy days | are here |
| 123 | comment 3 | comment 4 |
+------------+------------+-----------+
+-----------------+------------+
| Analyst Comment | Trans. No. |
+-----------------+------------+
| comment 5 | 123 |
| again | 456 |
+-----------------+------------+
我已经成功编写函数来创建这个数据结构,给定一个充满 CSV 的目录。我想再写两个函数:
;; FUNCTION 1 ==========================================================
;; Regex Spreadsheet -> Spreadsheet ; "Spreadsheet" is like ss above
;; Produces a Spreadsheet with ALL comments per transaction if ANY
;; value matches the regex
; (defn filter-all [regex my-ss] {}) ; stub
(defn filter-all [regex my-ss] ; template
(... my-ss))
(deftest filter-all-tests
(is (= (filter-all #"1" ss)
{ :123 '([ "comment 1" "comment 2" ]
[ "comment 3" "comment 4" ]
[ "comment 5" ]) })))
;; FUNCTION 2 ==========================================================
;; Regex Spreadsheet -> Spreadsheet ; "Spreadsheet" is like ss above
;; Produces a Spreadsheet with each transaction number that has at least
;; one comment that matches the regex, but ONLY those comments that
;; match the regex
; (defn filter-matches [regex my-ss] {}) ; stub
(defn filter-matches [regex my-ss] ; template
(... my-ss))
(deftest filter-matches-tests
(is (= (filter-matches #"1" ss)
{ :123 '([ "comment 1" ]) })))
我不明白的是让每个 key
的正则表达式足够深入到 vals
的最佳方法,因为它们是嵌套在列表中的向量中的字符串。我试过将 filter
与嵌套的 apply
s 或 map
s 一起使用,但我对语法感到困惑,即使它有效,我也不知道如何坚持下去keys
为了建立一个新的 hashmap。
我也尝试过在 filter
函数中使用解构,但我自己也很困惑,我还认为我必须 "lift" 嵌套数据中的函数(我认为这是术语——就像 Haskell 中的应用程序和单子一样。
有人可以建议过滤此数据结构的最佳方法吗?作为一个单独的问题,我很乐意就这是否是一个适合我的目的的合理数据结构提供反馈,但我想学习如何解决目前存在的这个问题,即使只是为了学习目的。
非常感谢。
这是您的数据结构的解决方案。
filter
采用谓词函数。进入那个函数,你实际上可以进入数据结构来测试你需要的任何东西。这里,flatten
有助于删除评论向量列表。
(defn filter-all [regex my-ss]
(into {} (filter (fn [[k v]] ; map entry can be destructured into a vector
; flatten the vectors into one sequence
; some return true if there is a match on the comments
(some #(re-matches regex %) (flatten v)))
my-ss)))
user> (filter-all #".*3.*" ss)
{:123 (["comment 1" "comment 2"] ["comment 3" "comment 4"] ["comment 5"])}
对于 filter-matches
逻辑是不同的:您想要使用某些部分的值构建一个 new 地图。 reduce
可以帮助做到这一点:
(defn filter-matches [regex my-ss]
(reduce (fn [m [k v]] ; m is the result map (accumulator)
(let [matches (filter #(re-matches regex %) (flatten v))]
(when (seq matches)
(assoc m k (vec matches)))))
{}
my-ss))
user> (filter-matches #".*days.*" ss)
{:456 ["happy days"]}
对于数据结构本身,如果将嵌套向量保留在每个条目的列表中没有用,可以使用{:123 ["comment1" "comments 2"] ...}
进行简化,但不会大大简化上述功能。
我认为您的方向是正确的,但也许让生活变得比需要的更艰难。
最令人担忧的是您对正则表达式的使用。虽然正则表达式对于某些事情来说是一个很好的工具,但当其他解决方案更好更快时,它们经常被使用。
在 clojure 中采用的关键思想之一是使用您 assemble 一起使用的小型库以获得更高级别的抽象。例如,有各种库可以处理不同的电子表格格式,例如 excel、google docs 电子表格,并且支持处理 CSV 文件。因此,我的第一步是看看您是否可以找到一个将您的 spreadhseet 解析为标准 clojure 数据结构的库。
例如,clojure 的 data.csv 会将 CSV 电子表格处理成惰性向量序列,其中每个向量都是电子表格中的一行,向量中的每个元素都是该行中的一个列值。一旦您拥有该格式的数据,然后使用 map、filter 等对其进行处理。阿尔。相当琐碎。
下一步是考虑使您的处理尽可能简单的抽象类型。这在很大程度上取决于您打算做什么,但我对此类数据的建议是使用由散列图组成的嵌套结构,该结构在外层由您的交易号索引,然后每个值都是一个散列图,它电子表格中的每一列都有一个条目。
{:123 {:cmnta ["comment 1" "comment 3"]
:cmntb ["comment 2" "comment 4"]
:analstcmt ["comment 5"]}
:456 {:cmnta ["happy days"]
:cmntb ["are here"]
:analystcmt ["again"]}}
有了这个结构,您就可以使用像 get-in 和 update-in 这样的函数来 access/change 结构中的值,即
(get-in m [123 :cmnta]) => ["comment 1" "comment 3"]
(get-in m [123 :cmnta 0]) => "comment 1"
(get-in m [456 :cmnta 1]) => nil
(get-in m [456 :cmnta 1] "nothing to see here - move on") => "nothing to see here - move on"