当值具有复合数据时,在Clojure中按值过滤散列映射

时间:2015-02-13 20:22:15

标签: clojure hashmap

我试图自学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" ]) })))

我不理解的是将正则表达式放到每个vals的{​​{1}}中的最佳方法,因为它们是嵌套在嵌套在列表内的向量内的字符串。我尝试将key与嵌套filterapply一起使用,但我对语法感到困惑,即使它有效,我也不知道如何使用挂起map以构建新的hashmap。

我也试过在keys函数中使用解构,但我也在混淆自己,我也认为我必须解除"嵌套数据中的函数(我认为Haskell中的术语类似的应用程序和monad)。

有人可以建议过滤此数据结构的最佳方法吗?作为一个单独的问题,我很乐意得到关于这是否是一个合理的数据结构的反馈,但是我想学习如何解决当前存在的问题,如果仅用于学习目的。

非常感谢。

2 个答案:

答案 0 :(得分:2)

这是一个包含数据结构的解决方案。 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"] ...}进行简化,但不会大大简化上述函数。

答案 1 :(得分:0)

我认为你走的是正确的道路,但也许会让生活变得更加艰难。

最令人担忧的是你使用正则表达式。虽然regexp对于某些事情来说是一个很好的工具,但是当其他解决方案更好,速度更快时,它们经常被使用。

在clojure中采用的一个关键思想是使用小型库,您可以将它们组合在一起以获得更高级别的抽象。例如,有各种用于处理不同电子表格格式的库,例如excel,google docs电子表格,并且支持处理CSV文件。因此,我的第一步是查看是否可以找到一个库,它将您的spreadhseet解析为标准的clojure数据结构。

例如,clojure的data.csv会将CSV电子表格处理为一个惰性的向量序列,其中每个向量都是电子表格中的一行,向量中的每个元素都是该行的列值。获得该格式的数据后,使用map,filter et处理它。人。是相当微不足道的。

下一步是考虑抽象的类型,这将使​​您的处理尽可能简单。这在很大程度上取决于你打算做什么,但我对这类数据的建议是使用一个由哈希映射组成的嵌套结构,在外层使用你的事务编号索引,然后每个值都是一个哈希映射。为电子表格中的每一列都有一个条目。

{: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等函数来访问/更改结构中的值,即

(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"