【问题标题】:How to read data from a file into a hash-map (or other data structure) in Clojure?如何将文件中的数据读入 Clojure 中的哈希映射(或其他数据结构)?
【发布时间】:2021-01-02 15:26:55
【问题描述】:

不太清楚从哪里开始。 我有一个大数据文件,其中包含与特定事物相关的不同值(即第 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 相关联。

【问题讨论】:

  • 这个特定论坛的问题有点模糊。 StackOverflow 最适合解决专注于完整、独立的代码示例和特定期望的问题。
  • 一个示例行会有所帮助,但请查看github.com/clojure/data.csv,它似乎可能会为您的用例提供一些实用程序。
  • 您对数据的描述过于模糊,我们无法真正对其进行任何处理,您能举个例子吗?最好尝试解决方案?
  • 添加了目前的文件结构和代码,希望对您有所帮助。 TIA。
  • @digit23 好吧,你的问题现在有两个重新打开投票只需要再重新打开一个投票,所以是的:)

标签: file clojure


【解决方案1】:

您遇到的问题是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

【讨论】:

    【解决方案2】:

    这里有一个函数可以用来做你正在寻找的事情:

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

    您必须提供三个参数:

    1. 用于分隔每行中的字段的正则表达式。对于您显示的数据,这可以是#"[^ ]+",或者基本上任何不是空白的都是该字段的一部分。如果您有简单的逗号分隔值且没有复杂性,例如在数据或引用字段中嵌入逗号,则 #"[^,]+" 之类的将起作用。或者,如果您只想提取数字字符,则可以使用更复杂的方法,例如 `#"[-0-9]+"。

    2. 要分配的列名集合。

    3. 文件名。

    因此,如果您在问题中显示的数据存储为 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", :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 会将整个文件作为字符串加载到内存中。如果您有一个特别大的文件,这不是一个好主意。在这种情况下,最好使用 line-seq 逐行读取文件
    【解决方案3】:

    主要答案

    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, :num2 23, :num3 25, :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, :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}]
    

    如果您希望解析更复杂的文件或真的想杀死/过度设计它,您可以使用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})
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-09-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-12
      • 2011-04-14
      • 2019-03-19
      相关资源
      最近更新 更多