LoginSignup
15
16

More than 5 years have passed since last update.

ファイルから指定した行数ずつ読み込む

Last updated at Posted at 2015-11-01

Ruby でファイルから n 行ずつ読み込むIO#batch_line(batch_size: n)みたいなメソッドがあれば良いなと思ったので、それっぽいものを書いてみました。

きっかけ

バッチ内に、メモリリークを考慮して、ログファイルを2000行ずつ処理している以下のようなコードがありました。

process_log.rb
File.open("./log.txt") do |file|
  result = {}
  file.readline # skip header
  file.each_line do |line|
    key, value = process(line)
    result[key] = value
    next if result.size < 2000
    output(results)
    result = {}
  end
  output(result)
end

つ、辛え……。

理想

あまりにも辛い。読む気が起きない。
こんな感じで書いて欲しい。書けて欲しい。

process_log.rb
File.open("./log.txt", skip_header: true) do |file|
  file.batch_line(batch_size: 2000) do |lines|
    result = process(lines)
    output(result)
  end
end

テキストファイルをbatch_lineというメソッドを持ったオブジェクトでラップしても良い気がします。

process_log.rb
file = TextFilePager.new("./log.txt", skip_header: true)
file.batch_line(batch_size: 2000) do |lines|
  result = process(lines)
  output(result) 
end

どちらも辛くない。

早速書いてみる

ファイルをオブジェクトでラップする方を書いてみました。

text_file_pager.rb
class TextFilePager
  DEFAULT_BATCH_SIZE = 1000

  def initialize(file_path, skip_header: false, delete_line_break: false)
    @file_path = file_path
    @skip_header = skip_header
    @delete_line_break = delete_line_break
  end

  def batch_line(batch_size: DEFAULT_BATCH_SIZE)
    File.open(@file_path) do |file|
      file.gets if skip_header?
      loop do
        line, lines = "", []
        batch_size.times do
          break if (line = file.gets).nil?
          lines << (delete_line_break? ? line.chomp : line)
        end
        yield lines
        break if line.nil?
      end
    end
  end

  def skip_header?
    @skip_header
  end

  def delete_line_break?
    @delete_line_break
  end
end

使い方

先ほど書いたコードがまんま動きます。
試しに動く例を書いてみます。

まずは適当なテキストファイルを準備します。

sample.txt
01 Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
02 Aenean commodo ligula eget dolor.
03 Aenean massa.
04 Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
05 Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.
06 Nulla consequat massa quis enim.
07 Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu.
08 In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo.
09 Nullam dictum felis eu pede mollis pretium.
10 Integer tincidunt.
11 Cras dapibus.
12 Vivamus elementum semper nisi.
13 Aenean vulputate eleifend tellus.
14 Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim.
15 Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus.

単なる15行の Lorem ipsum です。ピリオドごとに改行しており、行頭に行番号を降っています。

ここで誰かに「行番号を外して3センテンスごとの改行に直して欲しい」と頼まれたとします。その処理にprocessという曖昧過ぎてコードレビューで殺される名前をつけて実装してみます。

process_lorem_ipsum.rb
def process(lines)
  lines.map do |line|
    _, *content = line.split
    content.join("\s")
  end.join("\s")
end

# Main
sample_text = TextFilePager.new("sample.txt", delete_line_break: true)
sample_text.batch_line(batch_size: 3) do |lines|
  puts process(lines)
end

理想として挙げたコードとほぼ一緒です。違いはoutputの代わりにputsを使っているだけです。

出力を確認してみます。

processed_sample.txt
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.
Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim.
Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium.
Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi.
Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus.

問題なく動きました。

感想

ググり力が低いのか、探しても全然見つからなかったので自分で書いてみました。
複数行ずつ読み込むって結構需要がありそうな気もするので、良かったら参考にしてみてください。

IOFileを直接拡張して#batch_lineを生やすのもありかなって思ったんですが、標準クラスの拡張はあまり好きじゃないのでオブジェクトでラップしてみました。

ブロックを使いこなして関数型力あげてきたいです(´∀`∩)↑age↑

追記

素敵なコメントを元に、つたない英語で自分のブログにまとめ直してみました。

15
16
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
16