【问题标题】:How to read contents from a file and store it in a hash-map in clojure?如何从文件中读取内容并将其存储在 clojure 的哈希映射中?
【发布时间】:2020-09-20 11:22:35
【问题描述】:

我在 clojure 中遇到了一个小问题。我是这个编程环境的新手,需要一点帮助。我有一个这种格式的文本文件:

1|John Smith|123 Here Street|456-4567
2|Sue Jones|43 Rose Court Street|345-7867
3|Fan Yuhong|165 Happy Lane|345-4533

我想解析文件中的每一行,并希望以这种方式将数据存储在 hashmap 中:

{ 1 : [John Smith, 123 here street, 456-4567], 2 : ... }

这是我到目前为止的代码,但我还没有成功。

(defn readcustfile[]
  (def cust-file (clojure.string/split-lines (slurp "cust.txt"))) 

  (doseq [line cust-file]
    (def cust-id (nth (clojure.string/split line #"\|") 0))
    (def cust-name (str (nth (clojure.string/split line #"\|") 1)))
    (def cust-add (str (nth (clojure.string/split line #"\|") 2)))
    (def cust-phone (str (nth (clojure.string/split line #"\|") 3)))
    (def cust-data (str cust-name "," cust-add "," cust-phone))
    (def cust (map (fn [[id name add phone]]
                     (list (Integer/parseInt cust-id)
                           (hash-map :name cust-name
                                     :add cust-add
                                     :phone cust-phone))
    )
  )
(readcustfile)

【问题讨论】:

  • def 在顶层之外通常是禁止的。请改用let。此外,仅通过分隔符拆分来读取 CSV 可能很危险 - 通常最好使用库进行正确解析(例如,那些库可以处理引用/转义)

标签: functional-programming clojure


【解决方案1】:

我的建议是逐行读取文件,一次将其转换为结果映射:

(require '[clojure.java.io :refer [reader]]
         '[clojure.string :as cs]
         '[clojure.edn :as edn])

(with-open [rdr (reader "data.csv")]
  (into {}
        (map (comp (juxt (comp edn/read-string first) rest)
                   #(cs/split % #"\|")))
        (line-seq rdr)))

;;=> {1 ("John Smith" "123 Here Street" "456-4567"),
;;    2 ("Sue Jones" "43 Rose Court Street" "345-7867"),
;;    3 ("Fan Yuhong" "165 Happy Lane" "345-4533")}

注意,您不必将整个文件读入内存,而是使用with-open reader + line seq

还要注意,into + map(转换器)的形式被用来代替简单的map,然后是into {}for,以避免创建中间集合。

【讨论】:

    【解决方案2】:

    您应该use a library 来读取 CSV 数据。这里是another one

    这是一个例子:

    (ns tst.demo.core
      (:use tupelo.core tupelo.test)
      (:require
        [tupelo.csv :as csv]
        ))
    
    ; use (slurp <filename>) to read data into a string
    (def data-str
      "id|name|address|phone
       1|John Smith|123 Here Street|456-4567
       2|Sue Jones|43 Rose Court Street|345-7867
       3|Fan Yuhong|165 Happy Lane|345-4533 ")
    

    请注意,我添加了一个标题行来描述每个 CSV 记录的字段。 单元测试显示结果:

    (dotest
      (let [entity-maps (csv/parse->entities data-str :delimiter \|)
            grouped     (group-by :id entity-maps)]
    
        (is= entity-maps
          [{:id      "1",
            :name    "John Smith",
            :address "123 Here Street",
            :phone   "456-4567"}
           {:id      "2",
            :name    "Sue Jones",
            :address "43 Rose Court Street",
            :phone   "345-7867"}
           {:id      "3",
            :name    "Fan Yuhong",
            :address "165 Happy Lane",
            :phone   "345-4533"}])
    
        (is= grouped
          {"1"
           [{:id      "1",
             :name    "John Smith",
             :address "123 Here Street",
             :phone   "456-4567"}],
           "2"
           [{:id      "2",
             :name    "Sue Jones",
             :address "43 Rose Court Street",
             :phone   "345-7867"}],
           "3"
           [{:id      "3",
             :name    "Fan Yuhong",
             :address "165 Happy Lane",
             :phone   "345-4533"}]})))
    

    请务必查看 this list of documentation,尤其是 Clojure CheatSheet。

    【讨论】:

      【解决方案3】:
      (def cust-file ["1|John Smith|123 Here Street|456-4567"
                      "2|Sue Jones|43 Rose Court Street|345-7867"
                      "3|Fan Yuhong|165 Happy Lane|345-4533"])
      
      (into {}                      ;; construc new hash map from coll
                                    ;; (into {} ["key" "data"]) => {"key" "data"}
            (for [line cust-file    ;; `for` instead of `doseq`
                                    ;; (`doseq` will return nil, but `for` - results of iteration)
                  :let [[id & data] ;; shortend `let` alias, use desctructjion to get `id` and rest as `data`
                        (clojure.string/split line #"\|")]]
              [(Integer/parseInt id) data])) ;; return pair [id [name add phone]] for every line
      
      ;; => {1 ("John Smith" "123 Here Street" "456-4567"),
      ;;     2 ("Sue Jones" "43 Rose Court Street" "345-7867"),
      ;;     3 ("Fan Yuhong" "165 Happy Lane" "345-4533")}
      

      【讨论】:

        猜你喜欢
        • 2021-03-02
        • 2013-08-27
        • 1970-01-01
        • 2023-04-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多