【发布时间】:2016-01-02 12:53:22
【问题描述】:
我有一个文件,其中包含由空行分隔的文本块,如下所示:
block 1
some text
some text
block 2
some text
some text
如何将其读入数组?
【问题讨论】:
我有一个文件,其中包含由空行分隔的文本块,如下所示:
block 1
some text
some text
block 2
some text
some text
如何将其读入数组?
【问题讨论】:
这个问题经常被问到,我认为解释该怎么做会很有用,但首先我需要说一下:
不要试图一口气读完一个文件。这就是所谓的“slurping”,这是一个坏主意,除非你能保证你得到的文件总是显着小于 1MB尺寸。有关详细信息,请参阅“Why is "slurping" a file not a good practice?”。
如果我有一个看起来像这样的文件:
block 1
some text
some text
block 2
some text
some text
我试着正常阅读它,我会得到类似的东西:
File.read('foo.txt')
#=> "block 1\nsome text\nsome text\n\nblock 2\nsome text\nsome text\n"
这会让我不得不将它分成单独的行,试图找到空白行,然后将其分成块。而且,天真的解决方案总是使用正则表达式,这种方法可行,但不是最优的。
或者我们可以试试:
File.readlines('foo.txt')
#=> ["block 1\n", "some text\n", "some text\n", "\n", "block 2\n", "some text\n", "some text\n"]
然后还是要找空行,把数组变成子数组。
相反,有两种简单的方法可以加载文件。
记住之前关于 slurping 文件的警告,如果它是一个小文件,我们可以使用:
File.readlines('foo.txt', "\n\n")
#=> ["block 1\nsome text\nsome text\n\n", "block 2\nsome text\nsome text\n"]
注意在第二个参数中使用了"\n\n"。这就是“行分隔符”,对于 *nix 类型的操作系统,通常定义为“\n”,对于 Windows,通常定义为“\r\n”。它实际上基于操作系统派生的全局值 Ruby 集,被亲切地称为 $/、$RS 或 $INPUT_RECORD_SEPARATOR。它们记录在English 模块中。记录分隔符是文本文件中用于分隔两行的字符,或者,对于我们的目的,是由两个行尾字符分隔的一组行,或者换句话说,一个段落。
一旦阅读,很容易清理内容以删除尾随行尾:
File.readlines('foo.txt', "\n\n").map(&:rstrip)
#=> ["block 1\nsome text\nsome text", "block 2\nsome text\nsome text"]
或者将它们分解成子数组:
File.readlines('foo.txt', "\n\n").map{ |s| s.rstrip.split("\n") }
#=> [["block 1", "some text", "some text"], ["block 2", "some text", "some text"]]
所有示例都可以与类似以下的段落一起使用:
File.readlines('foo.txt', "\n\n").map(&:rstrip).each do |line|
# do something with line
end
或:
File.readlines('foo.txt', "\n\n").map{ |s| s.rstrip.split("\n") }.each do |paragraph|
# do something with the sub-array `paragraph`
end
如果它是一个大文件,如果文件尚未打开,我们可以通过foreach 使用Ruby 的逐行IO,如果文件已经打开,我们可以通过each_line 使用。而且,由于您阅读了上面的链接,您已经知道我们为什么要使用逐行 IO。
File.foreach('foo.txt', "\n\n") #=> #<Enumerator: File:foreach("foo.txt", "\n\n")>
foreach 返回一个枚举器,因此我们需要附加 to_a 来读取数组,以便我们可以看到结果,但通常我们不必这样做:
File.foreach('foo.txt', "\n\n").to_a
#=> ["block 1\nsome text\nsome text\n\n", "block 2\nsome text\nsome text\n"]
很容易使用foreach,就像上面一样:
File.foreach('foo.txt', "\n\n").map(&:rstrip)
#=> ["block 1\nsome text\nsome text", "block 2\nsome text\nsome text"]
File.foreach('foo.txt', "\n\n").map(&:rstrip).map{ |s| s.rstrip.split("\n") }
#=> [["block 1", "some text", "some text"], ["block 2", "some text", "some text"]]
注意:我强烈怀疑像这样使用map 会导致类似的问题,因为Ruby 会在将foreach 的输出传递给map 之前对其进行缓冲。相反,我们需要对 do 块内读取的每个段落进行操作:
File.foreach('foo.txt', "\n\n") do |ary|
ary.rstrip.split("\n").each do |line|
# do something with the individual line
end
end
这样做对性能的影响很小,但是因为目标是按段落或块进行处理,所以可以接受。
另请注意,这是一个社区 Wiki,因此请适当编辑和贡献。
【讨论】: