使用Clojure解析数据,间隔问题
为了学习,我正在用clojure编写一个小解析器。 基本上是一个TSV文件解析器,需要放在数据库中,但我增加了一个复杂的问题。 复杂的是在同一个文件中有更多的间隔。 该文件如下所示:使用Clojure解析数据,间隔问题,clojure,Clojure,为了学习,我正在用clojure编写一个小解析器。 基本上是一个TSV文件解析器,需要放在数据库中,但我增加了一个复杂的问题。 复杂的是在同一个文件中有更多的间隔。 该文件如下所示: ###andreadipersio 2010-03-19 16:10:00### USER COMM PID PPID
###andreadipersio 2010-03-19 16:10:00###
USER COMM PID PPID %CPU %MEM TIME
root launchd 1 0 0.0 0.0 2:46.97
root DirectoryService 11 1 0.0 0.2 0:34.59
root notifyd 12 1 0.0 0.0 0:20.83
root diskarbitrationd 13 1 0.0 0.0 0:02.84`
....
###andreadipersio 2010-03-19 16:20:00###
USER COMM PID PPID %CPU %MEM TIME
root launchd 1 0 0.0 0.0 2:46.97
root DirectoryService 11 1 0.0 0.2 0:34.59
root notifyd 12 1 0.0 0.0 0:20.83
root diskarbitrationd 13 1 0.0 0.0 0:02.84
('andreadipersio', '2010-03-19', '16:10:00', 'root', 'launchd', 1, 0, 0.0, 0.0, '2:46.97')
(def process-line
[line]
(if is-header? line)
(def header-data (extract-fields line header-pattern)))
(cons header-data (extract-fields line data-pattern)))
;; just a reminder, normally you'd put this in an ns form:
(use '[clojure.contrib.seq :only (partition-by)])
(->> (line-seq (java.io.BufferedReader. (java.io.StringReader. test-data)))
(remove #(re-find discard-pattern %)) ; throw out "USER COMM ..."
(partition-by is-header?)
(partition 2)
;; mapcat performs a map, then concatenates results
(mapcat extract-fields-add-headers))
我最终得到了以下代码:
(defn is-header?
"Return true if a line is header"
[line]
(> (count (re-find #"^\#{3}" line)) 0))
(defn extract-fields
"Return regex matches"
[line pattern]
(rest (re-find pattern line)))
(defn process-lines
[lines]
(map process-line lines))
(defn process-line
[line]
(if (is-header? line)
(extract-fields line header-pattern))
(extract-fields line data-pattern))
我的想法是,“流程线”中的间隔需要与数据合并,所以我有这样的想法:
###andreadipersio 2010-03-19 16:10:00###
USER COMM PID PPID %CPU %MEM TIME
root launchd 1 0 0.0 0.0 2:46.97
root DirectoryService 11 1 0.0 0.2 0:34.59
root notifyd 12 1 0.0 0.0 0:20.83
root diskarbitrationd 13 1 0.0 0.0 0:02.84`
....
###andreadipersio 2010-03-19 16:20:00###
USER COMM PID PPID %CPU %MEM TIME
root launchd 1 0 0.0 0.0 2:46.97
root DirectoryService 11 1 0.0 0.2 0:34.59
root notifyd 12 1 0.0 0.0 0:20.83
root diskarbitrationd 13 1 0.0 0.0 0:02.84
('andreadipersio', '2010-03-19', '16:10:00', 'root', 'launchd', 1, 0, 0.0, 0.0, '2:46.97')
(def process-line
[line]
(if is-header? line)
(def header-data (extract-fields line header-pattern)))
(cons header-data (extract-fields line data-pattern)))
;; just a reminder, normally you'd put this in an ns form:
(use '[clojure.contrib.seq :only (partition-by)])
(->> (line-seq (java.io.BufferedReader. (java.io.StringReader. test-data)))
(remove #(re-find discard-pattern %)) ; throw out "USER COMM ..."
(partition-by is-header?)
(partition 2)
;; mapcat performs a map, then concatenates results
(mapcat extract-fields-add-headers))
每一行直到下一个间隔,但我不知道如何做到这一点
我试过这样的方法:
###andreadipersio 2010-03-19 16:10:00###
USER COMM PID PPID %CPU %MEM TIME
root launchd 1 0 0.0 0.0 2:46.97
root DirectoryService 11 1 0.0 0.2 0:34.59
root notifyd 12 1 0.0 0.0 0:20.83
root diskarbitrationd 13 1 0.0 0.0 0:02.84`
....
###andreadipersio 2010-03-19 16:20:00###
USER COMM PID PPID %CPU %MEM TIME
root launchd 1 0 0.0 0.0 2:46.97
root DirectoryService 11 1 0.0 0.2 0:34.59
root notifyd 12 1 0.0 0.0 0:20.83
root diskarbitrationd 13 1 0.0 0.0 0:02.84
('andreadipersio', '2010-03-19', '16:10:00', 'root', 'launchd', 1, 0, 0.0, 0.0, '2:46.97')
(def process-line
[line]
(if is-header? line)
(def header-data (extract-fields line header-pattern)))
(cons header-data (extract-fields line data-pattern)))
;; just a reminder, normally you'd put this in an ns form:
(use '[clojure.contrib.seq :only (partition-by)])
(->> (line-seq (java.io.BufferedReader. (java.io.StringReader. test-data)))
(remove #(re-find discard-pattern %)) ; throw out "USER COMM ..."
(partition-by is-header?)
(partition 2)
;; mapcat performs a map, then concatenates results
(mapcat extract-fields-add-headers))
但这并不例外
有什么提示吗
谢谢 根据你的描述,我不能完全确定,但也许你只是在语法上犯了错误。这就是你想做的吗
(def process-line [line]
(if (is-header? line) ; extra parens here over your version
(extract-fields line header-pattern) ; returning this result
(extract-fields line data-pattern))) ; implicit "else"
如果“cons
”的目的是将标题与其关联的详细信息数据组合在一起,则需要更多的代码来实现这一点,但如果只是尝试“合并”并根据标题或详细信息行返回标题或详细信息行,则这应该是正确的。一种可能的方法:
行顺序
将输入拆分为行。(如果您想在字符串上测试这一点,您可以通过执行(line-seq(java.io.BufferedReader.(java.io.StringReader.test-string)))在字符串上获得行seq
)
(clojure.contrib.seq/Partition-by is header?您的行序列)
(分区2*2)
(其中*2
是在上述步骤2中获得的序列)将返回类似以下形式的序列:((标题-1)(流程-1流程-line-2))((标题-2)(流程-line-3流程-line-4))
。如果输入可能包含一些标题行,后面没有任何数据行,则上述内容可能类似于((header-1a header-1b)(process-line-1 process-line-2))
*3
)的输出:(解释
(最后一个标题)
位:我们在这里得到多个标题的唯一情况是,其中一些标题没有自己的数据行;实际附加到数据行的是最后一个。)
使用以下示例模式:
(def data-pattern #"(\w+)\s+(\w+)\s+(\d+)\s+(\d+)\s+([0-9.]+)\s+([0-9.]+)\s+([0-9:.]+)")
(def header-pattern #"###(\w+)\s+([0-9-]+)\s+([0-9:]+)###")
;; we'll need to throw out the "USER COMM ..." lines,
;; empty lines and the "..." line which I haven't bothered
;; to remove from your sample input
(def discard-pattern #"^USER\s+COMM|^$|^\.\.\.")
整个“管道”可能如下所示:
###andreadipersio 2010-03-19 16:10:00###
USER COMM PID PPID %CPU %MEM TIME
root launchd 1 0 0.0 0.0 2:46.97
root DirectoryService 11 1 0.0 0.2 0:34.59
root notifyd 12 1 0.0 0.0 0:20.83
root diskarbitrationd 13 1 0.0 0.0 0:02.84`
....
###andreadipersio 2010-03-19 16:20:00###
USER COMM PID PPID %CPU %MEM TIME
root launchd 1 0 0.0 0.0 2:46.97
root DirectoryService 11 1 0.0 0.2 0:34.59
root notifyd 12 1 0.0 0.0 0:20.83
root diskarbitrationd 13 1 0.0 0.0 0:02.84
('andreadipersio', '2010-03-19', '16:10:00', 'root', 'launchd', 1, 0, 0.0, 0.0, '2:46.97')
(def process-line
[line]
(if is-header? line)
(def header-data (extract-fields line header-pattern)))
(cons header-data (extract-fields line data-pattern)))
;; just a reminder, normally you'd put this in an ns form:
(use '[clojure.contrib.seq :only (partition-by)])
(->> (line-seq (java.io.BufferedReader. (java.io.StringReader. test-data)))
(remove #(re-find discard-pattern %)) ; throw out "USER COMM ..."
(partition-by is-header?)
(partition 2)
;; mapcat performs a map, then concatenates results
(mapcat extract-fields-add-headers))
(使用行seq
在您的最终程序中可能从不同的来源获取输入。)
对于您的示例输入,上面生成的输出如下(为清晰起见添加了换行符):
您正在执行(>(计数(重新查找#“^\{3}”行))0
,但您可以执行(重新查找#“^\{3}”行)
并将结果用作布尔值<如果匹配失败,code>re-find返回nil
如果迭代集合中的项目,并且希望跳过某些项目或将原始集合中的两个或多个项目合并为结果中的一个项目,则减少99%的时间。这通常是非常简单的
;; These two libs are called "io" and "string" in bleeding-edge clojure-contrib
;; and some of the function names are different.
(require '(clojure.contrib [str-utils :as s]
[duck-streams :as io])) ; SO's syntax-highlighter still sucks
(defn clean [line]
(s/re-gsub #"^###|###\s*$" "" line))
(defn interval? [line]
(re-find #"^#{3}" line))
(defn skip? [line]
(or (empty? line)
(re-find #"^USER" line)))
(defn parse-line [line]
(s/re-split #"\s+" (clean line)))
(defn parse [file]
(first
(reduce
(fn [[data interval] line]
(cond
(interval? line) [data (parse-line line)]
(skip? line) [data interval]
:else [(conj data (concat interval (parse-line line))) interval]))
[[] nil]
(io/read-lines file))))
感谢您的回复,修复了if表单上的语法问题,清理了输出,但是我仍然需要找到合并两个序列的正确方法(这是您描述的第一种情况)。ps对不起我的描述,我是一个clojure和函数式编程的初学者,所以我可能使用了错误的术语。顺便说一句,除非你真的知道自己在做什么,否则不要使用def
!而且绝对不要将其用于可变存储。使用Ref或Atom代替。谢谢,这是一个宝贵的提示!我希望你同意这个问题成为rubylearning.org(Clojure 101课程)练习的基础?我发现这是一个很好的问题。当然,一点问题也没有!非常感谢你。它就像一个符咒,我学到了两个有用的函数:mapcat和partition。再次感谢,不客气!请注意,我做了另一次编辑,使其能够正确处理某些标题后面可能没有数据行的情况。这非常好。目前,这是我能想到的最好的解决方案,也是最短的。非常感谢。这可能与手头的示例有任何关系,也可能没有任何关系,但我不同意关于reduce
适用于此类任务的说法。在Clojure中,reduce
总是很严格,因为它总是在内存中实现整个结果,然后再将其任何部分用于处理(因为Clojure的reduce
是一个左折叠)。这与惰性转换相互层叠(输入序列位于堆栈底部)的方法形成了对比,在这种方法中,结果可以分块生成。此外,我发现,与管道式方法相比,编写用于reduce
的复杂函数会降低代码的可读性和可调整性,虽然这可能是一个品味的问题,当然取决于任何特定案件的细节。为了避免给人留下错误的印象,我并不以任何方式反对reduce
,只是对于将序列转换为其他序列的特殊情况,我发现它是最后的工具,而不是第一选择。(这是最后的手段,因为几乎任何事情都可以用它来做,而管道式方法有局限性……不过,在这种特殊情况下,后者不起作用。)如果你想要完全的懒惰,reduce
不起作用,是的。clojure contrib中的reduces
可以。但是,我不确定你是否需要懒惰来解决这个问题。
和->
如果包含多个表单,我很快就会感到尴尬,尤其是当你因为ord而不得不从一个表单切换到另一个表单时呃,争论的变化。YMMV,这是一个s的问题