我试图自学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
与嵌套filter
或apply
一起使用,但我对语法感到困惑,即使它有效,我也不知道如何使用挂起map
以构建新的hashmap。
我也试过在keys
函数中使用解构,但我也在混淆自己,我也认为我必须解除"嵌套数据中的函数(我认为Haskell中的术语类似的应用程序和monad)。
有人可以建议过滤此数据结构的最佳方法吗?作为一个单独的问题,我很乐意得到关于这是否是一个合理的数据结构的反馈,但是我想学习如何解决当前存在的问题,如果仅用于学习目的。
非常感谢。
答案 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"