【问题标题】:How to read a file by paragraphs or chunks into arrays如何按段落或块将文件读入数组
【发布时间】:2016-01-02 12:53:22
【问题描述】:

我有一个文件,其中包含由空行分隔的文本块,如下所示:

block 1
some text
some text

block 2
some text
some text

如何将其读入数组?

【问题讨论】:

    标签: arrays ruby file io


    【解决方案1】:

    这个问题经常被问到,我认为解释该怎么做会很有用,但首先我需要说一下:

    不要试图一口气读完一个文件。这就是所谓的“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"]
    

    然后还是要找空行,把数组变成子数组。

    相反,有两种简单的方法可以加载文件。

    1. 记住之前关于 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
      
    2. 如果它是一个大文件,如果文件尚未打开,我们可以通过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,因此请适当编辑和贡献。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-07-16
      • 2010-10-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-18
      • 2019-08-31
      相关资源
      最近更新 更多