如何在 Clojure 中将文件中的数据读入 hash-map(或其他数据结构)?
How to read data from a file into a hash-map (or other data structure) in 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 ,
:num4 \space,
:num5 ,
:num6 ,
:num7 \space}
最简单的方法是逐行遍历文件,将其拆分为所需的值。请注意此处的 vec
,它强制执行(惰性)for
的结果,确保我们在 with-open
关闭 reader.
之前处理整个文件
(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")
它会 return
({:c1 "1", :c2 "23", :c3 "25", :c4 "-9", :c5 "-0", :c6 "1", :c7 "1"} {:c1 "2", :c2 "23", :c3 "25", :c4 "10", :c5 "1", :c6 "2", :c7 "3"})
或者换句话说,您会得到一系列映射,这些映射按您提供的列名映射值。如果您更喜欢将数据放在向量中,您可以使用
(into [] (map-from-file #"[^ ]+" [:c1 :c2 :c3 :c4 :c5 :c6 :c7] "/some-path/test3.dat"))
主要回答
Slurp 将 return 文件内容作为文本字符串,但您的代码似乎假定该文件已被解析为数字数组。事实并非如此。您仍然可以使用 slurp
但您必须自己解析文件。您可以通过首先使用 split-lines. Each line is valid Clojure syntax for a vector if we surround it by square brackets, and if we do so, we can then parse it into a vector using edn/read-string. We use map
to parse each line of the file. The following code will do the job, and uses the ->> 宏按行分隔符拆分文件字符串来解析它,以保持代码的可读性:
(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, :num2 23, :num3 25, :num4 10, :num5 1, :num6 2, :num7 3})
Extensions/variations
如果有包含其他元素数量的行,您可能希望仅保留包含七个元素的行,使用 filter
。映射和过滤可以组成一个transducer that we pass as argument to 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, :num2 23, :num3 25, :num4 -9, :num5 0, :num6 1, :num7 1} {:num1 2, :num2 23, :num3 25, :num4 10, :num5 1, :num6 2, :num7 3}]
如果您希望解析更复杂的文件或真的想 kill/overengineer 它,您可以使用 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, :num2 23, :num3 25, :num4 -9, :num5 0, :num6 1, :num7 1} {:num1 2, :num2 23, :num3 25, :num4 10, :num5 1, :num6 2, :num7 3})
不太确定从哪里开始。 我有一个大数据文件,其中包含不同的值,这些值都与某个事物相关(即第 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 ,
:num4 \space,
:num5 ,
:num6 ,
:num7 \space}
最简单的方法是逐行遍历文件,将其拆分为所需的值。请注意此处的 vec
,它强制执行(惰性)for
的结果,确保我们在 with-open
关闭 reader.
(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")
它会 return
({:c1 "1", :c2 "23", :c3 "25", :c4 "-9", :c5 "-0", :c6 "1", :c7 "1"} {:c1 "2", :c2 "23", :c3 "25", :c4 "10", :c5 "1", :c6 "2", :c7 "3"})
或者换句话说,您会得到一系列映射,这些映射按您提供的列名映射值。如果您更喜欢将数据放在向量中,您可以使用
(into [] (map-from-file #"[^ ]+" [:c1 :c2 :c3 :c4 :c5 :c6 :c7] "/some-path/test3.dat"))
主要回答
Slurp 将 return 文件内容作为文本字符串,但您的代码似乎假定该文件已被解析为数字数组。事实并非如此。您仍然可以使用 slurp
但您必须自己解析文件。您可以通过首先使用 split-lines. Each line is valid Clojure syntax for a vector if we surround it by square brackets, and if we do so, we can then parse it into a vector using edn/read-string. We use map
to parse each line of the file. The following code will do the job, and uses the ->> 宏按行分隔符拆分文件字符串来解析它,以保持代码的可读性:
(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, :num2 23, :num3 25, :num4 10, :num5 1, :num6 2, :num7 3})
Extensions/variations
如果有包含其他元素数量的行,您可能希望仅保留包含七个元素的行,使用 filter
。映射和过滤可以组成一个transducer that we pass as argument to 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, :num2 23, :num3 25, :num4 -9, :num5 0, :num6 1, :num7 1} {:num1 2, :num2 23, :num3 25, :num4 10, :num5 1, :num6 2, :num7 3}]
如果您希望解析更复杂的文件或真的想 kill/overengineer 它,您可以使用 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, :num2 23, :num3 25, :num4 -9, :num5 0, :num6 1, :num7 1} {:num1 2, :num2 23, :num3 25, :num4 10, :num5 1, :num6 2, :num7 3})