2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【初心者向け】Ruby のまずいコード 25 本Advent Calendar 2021

Day 21

【Ruby のまずいコード】文字列連結

Last updated at Posted at 2021-12-22

お題

何かのデータファイルから情報を抜き出してテキストファイルに書き出すというお仕事です。
データファイルはテキストファイルで,どの行も先頭が A から Z までのいずれかの文字になっています。
こんなふうに:

data.txt
H 9032,4232
G PXPP
A 123:A-G3
L FO,WE
C 1..9X
G LOPX

この中から,A の行だけを(順序を変えずに)抽出して結合し,また B の行だけを同様に抽出して結合し,この二つを A→B の順に結合したものを result.txt に書き出してください。

データファイル data.txt は何百万行もあるので,File.read で一気にまるごと読み込むということはせず,File.foreach で 1 行ずつ読み込んで処理するようにしましょう。

なお,データファイルの末尾には改行が入っているものとします。よって,File.foreach で取り出した「行」は,最終行まですべて改行が入っていると仮定できます。

コード

text_a = ""
text_b = ""

File.foreach("data.txt") do |line|
  case line
  when /^A/
    text_a += line
  when /^B/
    text_b += line
  end
end

File.write "result.txt", text_a + text_b

講評

速度面では,A の行,B の行の判定に case/when と正規表現を用いるのをやめて,ifString#start_with? を用いるのがよさそうですが,この記事で伝えたい点はそこではないので,この点に関しては上記コードのままでよいことにします1

このコードのまずい点は,見出された A の行,B の行を次々と結合していくのに

text_a += line

のようなやり方をとっていることです。
これは,自己代入式の形で書かれていますが,要するに

text_a = text_a + line

のことですね。
ローカル変数 text_a に代入されている String オブジェクトと,ローカル変数 line に代入されている String オブジェクトとを結合した String オブジェクトを新たに生成し,それを text_a に代入しています。

この式が実行(評価)される前に text_a に代入されていた String オブジェクトは,実行後にどうなるのでしょうか?
これは,もうどこからも参照されないオブジェクトになります。永遠に使われることはないのです。こういったオブジェクトをガーベジ(garbage)といいます。

もし,A で始まる行が百万行あったとすると,最終的に百万個のガーベジが出来ます。
ガーベジは際限なく溜まり続けるわけではなく,Ruby の処理系が適当なタイミングで消していきます。そうしないとメモリーが足りなくなりますから。この処理をガーベジコレクションといいますが,場合によってはこれがプログラムの効率に無視できない影響を与えることもあります。

ところで,

text_a = text_a + line

のような処理を繰り返していくと,生成する文字列はだんだん長くなりますね。
あとになればなるほど大きなガーベジができるわけです。

以上から,連続する文字列結合で,上記のような書き方は避けたほうがよいことが分かります。

改善

文字列結合に String#<< を使って

text_a << line

とすれば解決します。
String#<< は破壊的に文字列を結合するメソッドです。
この場合,text_a に代入されている文字列自身がずんずん伸びていきます(line に代入されているオブジェクトは変化しません)。
この操作では新たな String オブジェクトが出来たりはしません。

なお,スクリプト先頭に

# frozen_string_literal: true

と書いた場合は,

text_a = ""
text_b = ""

で得られる String オブジェクトが凍結されているので,<< による破壊的操作ができません。
この場合は

text_a = +""
text_b = +""

のようにして,凍結されていない空の String オブジェクトを作ればいいでしょう。

  1. お題をシンプルにするために「A で始まる行」「B で始まる行」としましたが,正規表現を使わないと判定できないようなケースを想定してください。

2
0
4

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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?