如何解决如何在 Clojure 中将文件中的数据读取到哈希映射或其他数据结构中?
不太确定从哪里开始。 我有一个大数据文件,其中包含与某个事物相关的不同值(即第 1 列中的数据是小时),该文件有 15 列宽。该文件不包含任何列标题,只是数字数据。
我需要将这些数据读入一种数据类型,例如哈希映射,这样我就可以对它进行排序并使用诸如包含之类的东西来查询数据?以及执行计算。
我不确定如何执行此操作,因为我是 Clojure 的新手,我们将不胜感激。
我的文件是一个 txt 文件(另存为 mydata.txt),结构如下:
1 23 25 -9 -0 1 1
2 23 25 10 1 2 3
到目前为止我的代码是:
(def filetoanalyse (slurp "mydata.txt"))
(zipmap [:num1 :num2 :num3 :num4 :num5 :num6 :num7] filetoanalyse)
它似乎将整个文件与当前的 :num1 相关联。
解决方法
您遇到的问题是 slurp
将文件作为字符串读入。当你在它上面使用 zipmap
时,它使用字符串中的字符作为映射中的值,导致这种混乱:
(zipmap [:num1 :num2 :num3 :num4 :num5 :num6 :num7] (slurp "mydata.txt"))
;;=> {:num1 \space,:num2 \space,:num3 \1,:num4 \space,:num5 \2,:num6 \3,:num7 \space}
最简单的方法是逐行遍历文件,将其拆分为您想要的值。注意这里的 vec
,它强制(惰性)for
的结果,确保我们在 with-open
关闭读取器之前处理整个文件。
(with-open [reader (clojure.java.io/reader "mydata.txt")]
(vec (for [line (line-seq reader)] ; iterate over each line
(->> (clojure.string/split line #"\s+") ; split it by whitespace
(remove empty?) ; remove any empty entries
(map #(Long/parseLong %)) ; convert into Longs (change if another format is more suitable)
(zipmap [:num1 :num2 :num3 :num4 :num5 :num6 :num7]))))) ; turn into a map
,
这里有一个函数,你可以用它来做你想要的:
(defn map-from-file [field-re column-names filename]
(let [ lines (re-seq #"[^\r\n]+" (slurp filename)) ]
(map #(zipmap column-names (re-seq field-re %)) lines)))
您必须提供三个参数:
-
用于分隔每行中的字段的正则表达式。对于您显示的数据,这可以是
#"[^ ]+"
,或者基本上任何不是空白的东西都是该字段的一部分。如果您有简单的逗号分隔值,没有复杂性,例如数据或引用字段中的嵌入逗号,则#"[^,]+"
之类的东西会起作用。或者,如果您只想提取数字字符,则可以使用更复杂的内容,例如 `#"[-0-9]+"。 -
要分配的列名集合。
-
文件名。
因此,如果您在问题中显示的数据存储为 test3.dat
,则您可以将上述函数调用为
(map-from-file #"[^ ]+" [:c1 :c2 :c3 :c4 :c5 :c6 :c7] "/some-path/test3.dat")
它会返回
({:c1 "1",:c2 "23",:c3 "25",:c4 "-9",:c5 "-0",:c6 "1",:c7 "1"} {:c1 "2",:c4 "10",:c5 "1",:c6 "2",:c7 "3"})
或者换句话说,您会返回一系列映射,这些映射按您提供的列名称映射值。如果您更喜欢将数据放在向量中,可以使用
(into [] (map-from-file #"[^ ]+" [:c1 :c2 :c3 :c4 :c5 :c6 :c7] "/some-path/test3.dat"))
,
主要答案
Slurp 会将文件内容作为文本字符串返回,但您的代码似乎假定此文件已被解析为数字数组。事实并非如此。您仍然可以使用 slurp
,但您必须自己解析文件。您可以通过首先使用 split-lines 按行分隔符拆分文件字符串来解析它。如果我们用方括号将向量包围起来,每一行都是有效的 Clojure 语法,如果我们这样做,我们可以使用 edn/read-string 将其解析为向量。我们使用 map
来解析文件的每一行。以下代码将完成这项工作,并使用 ->> 宏来保持代码可读性:
(require '[clojure.string :as cljstr])
(require '[clojure.edn :as edn])
(->> "/tmp/mydata.txt"
slurp
cljstr/split-lines
(map #(zipmap
[:num1 :num2 :num3 :num4 :num5 :num6 :num7]
(edn/read-string (str "[" % "]")))))
;; => ({:num1 1,:num2 23,:num3 25,:num4 -9,:num5 0,:num6 1,:num7 1} {:num1 2,:num4 10,:num5 1,:num6 2,:num7 3})
扩展/变体
如果有包含其他元素数量的行,您可能只想保留那些包含七个元素的行,使用 filter
。映射和过滤可以组成一个 transducer,我们将其作为参数传递给 into:
(let [columns [:num1 :num2 :num3 :num4 :num5 :num6 :num7]
n (count columns)]
(->> "/tmp/mydata.txt"
slurp
cljstr/split-lines
(into [] (comp (map #(zipmap
columns
(edn/read-string (str "[" % "]"))))
(filter #(= n (count %)))))))
;; => [{:num1 1,:num7 3}]
如果您希望解析更复杂的文件或真的想终止/过度设计它,您可以使用 spec:
(require '[clojure.spec.alpha :as spec])
(->> "/tmp/mydata.txt"
slurp
cljstr/split-lines
(map #(edn/read-string (str "[" % "]")))
(spec/conform (spec/coll-of (spec/cat :num1 number?
:num2 number?
:num3 number?
:num4 number?
:num5 number?
:num6 number?
:num7 number?))))
;; => ({:num1 1,:num7 3})
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。