使用 clojure 将多行读入记录
Reading multiple line into a record with clojure
我正在学习 clojure,想将跨越多行的记录读取到一组映射中。实际上文件的内容是从 ami image/snapshot/volume 和实例列表上的 AWS 控制台复制和粘贴的。
生成的文本文件的内容如下:-
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*
我的想法是将第一行读取为字段计数,然后将这些行分成两组,一组为 header,然后其余为数据:-
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") ("My own instance 1" "Ins-123456" "Vol-234567" "My own instance 2" "Ins-123457" "Vol-234568")]
我有办法把这两个列表变成一组地图吗?
有人能帮忙吗?
下面是一个简单的尝试,没有首先尝试检查文件是否具有预期的结构:
(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-seq
的用法与 readit
fn 的用法大致相同。从 line-seq 开始有几个基本步骤:
- 获取字段数。
- 取那么多行并将它们存储为字段名称。如果需要,您可以在此处映射
keyword
并将它们的数据类型从 String 更改为。
- 删除字段规范,然后将 seq 行划分为 "blocks" 对应于各个记录。我加 1 是为了拿走你的
*Blank line*
,但这些都没有用。
- 使用
zipmap
构建我们想要的地图。这是一个非常有用的函数,它接受一个键序列和一个值序列并将它们粘合在一起形成一个映射。我们总是希望使用相同的键(我们的 fields
),因此我们可以在将其映射到值的序列之前部分地应用 zipmap 并将它们作为参数。它不会使用没有相应键的值,这就是我们摆脱空行的方式。
- 使用
into
将地图收集成一组。
使用初始 readfile()
函数构建记录序列。我选择 keyword
-ise 字段名称和记录 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")))
我正在学习 clojure,想将跨越多行的记录读取到一组映射中。实际上文件的内容是从 ami image/snapshot/volume 和实例列表上的 AWS 控制台复制和粘贴的。
生成的文本文件的内容如下:-
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*
我的想法是将第一行读取为字段计数,然后将这些行分成两组,一组为 header,然后其余为数据:-
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") ("My own instance 1" "Ins-123456" "Vol-234567" "My own instance 2" "Ins-123457" "Vol-234568")]
我有办法把这两个列表变成一组地图吗?
有人能帮忙吗?
下面是一个简单的尝试,没有首先尝试检查文件是否具有预期的结构:
(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-seq
的用法与 readit
fn 的用法大致相同。从 line-seq 开始有几个基本步骤:
- 获取字段数。
- 取那么多行并将它们存储为字段名称。如果需要,您可以在此处映射
keyword
并将它们的数据类型从 String 更改为。 - 删除字段规范,然后将 seq 行划分为 "blocks" 对应于各个记录。我加 1 是为了拿走你的
*Blank line*
,但这些都没有用。 - 使用
zipmap
构建我们想要的地图。这是一个非常有用的函数,它接受一个键序列和一个值序列并将它们粘合在一起形成一个映射。我们总是希望使用相同的键(我们的fields
),因此我们可以在将其映射到值的序列之前部分地应用 zipmap 并将它们作为参数。它不会使用没有相应键的值,这就是我们摆脱空行的方式。 - 使用
into
将地图收集成一组。
使用初始 readfile()
函数构建记录序列。我选择 keyword
-ise 字段名称和记录 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")))