はじめに
こんにちは!アメリカの大学で語学を学びながら、独学でソフトウェアエンジニアを目指している者です。
たのしいRubyの中で、ファイルの内容から特定の行数を抽出する問題に取り組みました。
この問題は、Unixのtail
コマンドと同じ動作を再現する機能を実装するものでした。
今回は、私が書いたコードと、答えのコードの2つを比較し、それぞれの特徴や違いを振り返ります。どちらも動作は似ていますが、設計の工夫や効率の違いを学ぶ良い機会になりました。
私が書いたコード
def tail(last_lines, filename)
File.open(filename) do |file|
ary = file.readlines
lines_whole = file.lineno
start_lines = lines_whole - last_lines
puts ary.slice(start_lines, last_lines).join
end
end
tail(3, "data.txt")
コードの特徴
- 全行を一度に読み込む
file.readlines
を使用してファイル全体を配列として読み込むため、小規模なファイルでは問題ありませんが、大きなファイルではメモリ効率が悪くなる可能性があります。 - 行番号の計算
全体の行数を取得し、指定された「抽出する行数 (last_lines
)」から開始位置を計算しています。slice
メソッドを使うことで、配列から必要な部分だけを抽出しています。 - わかりやすい構造
初学者には処理の流れが直感的ですが、無駄な処理が含まれるため改善の余地があります。
rubyの勉強前にSQLとデータベースの勉強をしたのですが、その際にもすべてを読み込むという操作をやるとメモリ効率が落ちると書いてありましたので、実装するコードを書くことを踏まえるとあまりいいコードではないと思いました。
答えのコード
あくまでもこれは模範解答のコードです。
このコードが一番効率的なコードかわからないのでご了承ください
もし、もっと効率的なコードがあれば教えていただけると嬉しいです
def tail(lines, file)
queue = Array.new
open(file) do |io|
while line = io.gets
queue.push(line)
if queue.size > lines
queue.shift
end
end
end
queue.each { |line| print line }
end
puts "==="
tail(10, "data.txt")
puts "==="
tail(3, "data.txt")
コードの特徴
- 行を順次処理する
ファイルを1行ずつ読み込むため、メモリ効率が良い設計です。特に大規模ファイルを扱う場合でも、必要以上のメモリを消費しません。 - キューによる管理
queue
を使い、最新の指定行数だけを保持します。古い行はqueue.shift
によって削除されます。 - 柔軟性
ファイル全体を扱うことなく動作するため、行数の増減やファイルサイズの変化にも対応可能です。
まとめ
観点 | 自分が書いたコード | 答えのコード |
---|---|---|
メモリ効率 | ファイル全体を読み込むため効率が悪い | 1行ずつ処理するため効率が良い |
ファイルサイズの対応 | 小規模ファイル向き | 大規模ファイルにも対応 |
理解のしやすさ | 初学者にとって直感的だが、行番号計算が不要な点に気づきにくい | キューの仕組みがやや複雑 |
処理の柔軟性 | 行番号を計算する必要があり硬直的 | 最新の行数のみを保持する柔軟な設計 |
今回の振り返りを通して、コードの効率や設計を振り返ることの重要性を改めて実感しました。
次回以降は、無駄のない設計を目指して実装を工夫してみたいと思います!