当值具有复合数据时,在 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 与嵌套的 applys 或 maps 一起使用,但我对语法感到困惑,即使它有效,我也不知道如何坚持下去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"