使用clojure将多行读入记录

时间:2015-02-23 12:34:12

标签: dictionary clojure set

我正在学习clojure,并希望将多行记录读入一组地图。实际上,该文件的内容是来自AWS控制台的ami image / snapshot / volume和实例列表的复制和粘贴。

生成的文本文件的内容如下所示: -

Record 1 Field Value 1
Record 1 Field Value 2
Record 1 Field Value 3
Record 2 Field Value 1
Record 2 Field Value 2
Record 2 Field Value 3

我写的是

(defn read-file [file]
    (letfn [(readit [rdr]
        (lazy-seq
            (if-let [ami-name (.readLine rdr) ]
                (cons ami-name (readit rdr ))
                (do (.close rdr) nil))))]
        (filter #(not (clojure.string/blank? %)) (readit (clojure.java.io/reader file)))))

效果很好,它将所有内容都附加到列表中。但我的最终目标是将三个相似的文件读入三组地图,然后将它们连接在一起以创建有意义的内容,找出具有设置差异的过时记录。我想我可以设法加入基于公共密钥字段的三组记录。问题是我无法弄清楚如何将文本文件读入一组地图。这三个文件的格式类似,如下所示: -

档案1

*Field Count (N)*
Field Label 1
Field Label 2
..
Field Label N
Record 1 Field Value 1
Record 1 Field Value 2
Record 1 Field Value N
Record 2 Field Value 1
Record 2 Field Value 2
..
Record 2 Field Value N

地图的结果列表如下所示: -

(def instance-list
    #{{Field Label 1: Record 1 Field Value 1 Field Label 2: Record 1 Field Value 2 Record 1 Field Label N: Field Value N}
    {Field Label 1: Record 2 Field Value 1 Field Label 2: Record 2 Field Value 2 Record 2 Field Label N: Field Value N}
    {Field Label 1: Record N Field Value 1 Field Label 2: Record N Field Value 2 Record N Field Label N: Field Value N}})

示例数据如下: -

3
Name
Instance id
volume id
My own instance 1
Ins-123456
Vol-234567
*Blank line*
My own instance 2
Ins-123457
Vol-234568
*Blank line*

我的想法是将第一行读作字段计数,然后将这些行分成两组,一组作为标题,然后保留为数据: -

user=> (defn parse-int [s]
  #_=> (Integer. (re-find  #"\d+" s )))

#'user/parse-int
user=> (split-at (parse-int (first (read-file "test.txt"))) (rest (read-file "test.txt")))

[(" Name"" Instance id""" volume id")("我自己的实例1"&# 34; Ins-123456"" Vol-234567""我自己的实例2"" Ins-123457"" Vol-234568") ]

我有没有把这两个列表变成一组地图?

有人可以帮忙吗?

2 个答案:

答案 0 :(得分:1)

这是一个只有幸福案例的尝试,没有尝试先检查文件是否具有预期的结构:

(defn read-file [file]
  (with-open[rdr (clojure.java.io/reader file)]
    (let[lines (line-seq rdr)
         num-fields (Long/valueOf (first lines))
         fields (->> lines (drop 1) (take num-fields))
         block-size (inc num-fields)
         records (->> lines
                      (drop block-size) 
                      (partition block-size) 
                      (map (partial zipmap fields)))]
      (into #{} records))))

;;Returns #{{"volume id" "Vol-2345", "Instance id" "Ins-123457", "Name" "My own instance 2"} 
;;          {"volume id" "Vol-23456", "Instance id" "Ins-12345", "Name" "My own instance 1"}} 

请注意,使用line-seqreadit fn完全相同。从line-seq开始,有几个基本步骤:

  1. 获取字段数。
  2. 取这么多行并将它们存储为字段名称。如果需要,您可以在此处将keyword映射到它们上,并从String中更改其数据类型。
  3. 删除字段规范,然后将行seq划分为与各个记录对应的“块”。我添加1以获取*Blank line*,但这些都没有使用。
  4. 使用zipmap构建我们想要的地图。这是一个非常有用的函数,它接受一系列键和一系列值并将它们粘合在一起形成一个映射。我们总是希望使用相同的键(我们的fields),因此在将它们映射到值的seq之前,我们可以将zipmap作为参数部分应用。如果没有相应的键,它将不会使用值,这就是我们摆脱空白行的方式。
  5. 使用into将地图收集到一个集合中。

答案 1 :(得分:1)

使用您的初始readfile()函数构建记录序列。我选择keyword - 是字段名称和记录ID:

(defn record-seq [file]
  (let [data      (read-file file)
        nb-fields (Integer/parseInt (first data))
        fields    (map #(keyword (str/replace % #"\s+" "-"))
                       (take nb-fields (rest data)))
        values    (filter (complement str/blank?)
                          (drop (inc nb-fields) data))
        rec-ids   (map #(keyword (str "rec-" %))
                       (range))]
    (map #(vector %1 (zipmap fields %2))
         rec-ids
         (partition nb-fields values))))

user> (pprint (record-seq "./ami.log"))
([:rec-0
  {:volume-id "Vol-23456",
   :Instance-id "Ins-12345",
   :Name "My own instance 1"}]
 [:rec-1
  {:volume-id "Vol-2345",
   :Instance-id "Ins-123457",
   :Name "My own instance 2"}]
 [:rec-2
  {:volume-id "Vol-9876",
   :Instance-id "Ins-123987",
   :Name "My own instance 3"}])

构建set条记录只是

的问题
(into #{} (map second (record-seq "./ami.log")))