Ruby TIPS

Ruby TIPS

桁区切り数値の記述と出力/文字列を逆順に/文字列の分割/文字列の各文字の利用 ― コーディングミスを防ぐには?(4)

2016年3月3日

初心者向けにRubyプログラミングの落とし穴を紹介。桁区切り指定で数値リテラルを記述する方法や、桁区切りの数値文字列を出力する方法、文字列を逆順に並べ替える方法、文字列を文字列で分割する方法、文字列を1文字ずつ扱う方法を説明する。

ローグ・インターナショナル 羽山 博
  • このエントリーをはてなブックマークに追加

 Rubyには他のプログラミング言語にはない便利な機能が数多く備わっている。一方で意外な落とし穴もある。今回は数値リテラルを見やすくする方法や文字列をうまく扱う方法を見ていく。最後にそれらを組み合わせた例として、数値に桁区切りスタイルを適用するためのコードも紹介する。

数値リテラルには桁区切りが指定できる

 一般的なプログラムでは、大きな数値リテラルを扱うことは少ないかもしれないが、桁数が増えると位取りが分かりにくくなる。例えば、1000000という値を見ても、百万なのか一千万なのか一瞬で見分けるのは難しいし、0の数を間違って入力してしまうこともある。Rubyでは、_(アンダーバー)を使って桁区切りを指定できるので、そういう面倒さを避けることができる。

sample001.rb
limit = 1_000_000
rest = limit - ARGV[0].to_i
puts rest
リスト1.1 1,000,000から、引数で指定した数値を引いた残りの数値を出力する

プログラムの中で桁数の多い数値リテラルを使う例。一目見ただけで百万であることが分かる。

 桁区切りの記号として使う_は単に無視されるだけなので、特に3桁ごとに指定する必要はない。ただし、2つ連続させたり、末尾に付けたりすることはできない。

 ところで、3桁ごとに区切るのは「thousand(千)」「million(百万)」「billion(十億)」といった英語の位取りに合わせたものなので、日本語の「万」「億」「兆」という位取りに合わせたい場合には4桁ごとに区切った方が適切といわれている。当然、そういった表し方もできる。例えばリスト1.1の変数limitに代入する値は、100_0000と書いてもよい。いずれの書き方でもいいが、実行してみると以下のようになる。

コンソール
$ ruby sample001.rb 123456
876544
$ 
実行例1.1 大きな桁数の数値を取り扱うプログラムの実行例

桁区切りの_は無視されるので、普通の数値として扱われる。当然、加算や減算などの演算子も使えるので、正しく結果が求められる。

 桁区切りの_は10進整数だけでなく、どんな数値リテラルでも使える。例えば以下のように、桁数の多い2進数や16進数を_で区切ればかなり見やすくなる。

  • 10進実数の例: 1_234.56
  • 2進数の例: 0xE7_84_A1
  • 16進数の例: 0b1110_0111_1000_0100_1010_0001

 なお、数値に書式を指定して出力するためにはprintfメソッドなどを使うが、桁区切りのための書式指定子がないので、3桁ごとにカンマ(,)で区切って出力するといった場合にはちょっとした工夫が必要になる。その方法については、文字列についてのTIPSを紹介した後(=本稿の最後)で、コードの例を示すこととする。

文字列を逆順にするには

 文字列を自由に取り扱うには、文字列の性質やStringクラスのメソッドを知っておく必要がある。これまでに公開されたTIPSでも文字列の取り扱い方法をいくつか見てきたが、今回は、

  • 文字列を逆順に並べ替えるメソッド
  • 文字列を分割するメソッド
  • 文字列と配列との関係

を紹介する。まずは、文字列を逆順に並べ替えるreverseメソッドとreverse!メソッドの使い方を簡単な例で見てみよう。

sample002.rb
str1 = "today"

str2 = str1.reverse  # str1を逆順にしたものをstr2に代入
puts "非破壊的メソッドの実行結果"

puts str1, str2
puts "破壊的メソッドの実行結果"

str1.reverse!  # str1を破壊的に逆順にする
puts str1
リスト1.2 reverseメソッドやreverse!メソッドを使って文字列を逆順にする

Stringクラスのreverseメソッドは非破壊的なメソッドなので文字列そのものの内容は変更されない。一方、Stringクラスのreverse!メソッドは破壊的なメソッドなので文字列そのものが変更される。

 非破壊的なメソッドでは元の文字列は変更されず、変更された新しい文字列が作られる。一方、破壊的なメソッドでは元の文字列そのものが変更される。それを踏まえた上で、結果がどうなるか予想してみてほしい。予想ができたら、実行例で結果を確認してみよう。

コンソール
$ ruby sample002.rb
非破壊的メソッドの実行結果
today
yadot
破壊的メソッドの実行結果
yadot
$
実行例1.2 大きな桁数の数値を取り扱うプログラムの実行例

前半では変数str1が参照する文字列"today"は変更されず、逆順にされた"yadot"が新しく作られ、その参照が変数str2に代入された。後半では変数str1が参照する文字列そのものが"yadot"に変更された。

文字列を分割するには

 "abc.txt""."の前後で"abc""txt"に分割する、というように、文字列を特定の文字列で分割したいときにはStringクラスのpartitionメソッドが使える。簡単な例で見てみよう。

sample003.rb
filename = "abc.txt"
fname, sp, ext = filename.partition(".")
puts fname, sp, ext
リスト1.3 partitionメソッドを使って文字列を3つの部分に分割する

Stringクラスのpartitionメソッドは、引数に指定した文字列を使って、元の文字列を3分割し、「引数より前の部分」「引数の文字列」「引数より後ろの部分の文字列」をそれぞれ返す。

 partitionメソッドには、セパレーターとして使う文字列を引数に指定する。このメソッドが返す値は、下記の3つがあることに注意しよう。

  1. 分割した文字列の前の部分
  2. 引数として指定した文字列(セパレーター)
  3. 分割した文字列の後ろの部分

 元の文字列にセパレーターが含まれない場合は、2つ目と3つ目の返り値は空文字列となる。Rubyでは複数の変数に複数の値を代入できることを思い出そう。実行例は以下の通り。

コンソール
$ ruby sample003.rb
abc
.
txt
実行例1.3 ファイル名と拡張子を分割する

セパレーターに"."を指定すれば、ファイル名を名前の部分と拡張子とに分割できる。

 ただし、「abc.def.txt」のように文字列に複数の「.」が含まれる場合、ファイル名の拡張子を得るという目的に、この方法は使えない。その場合は「abc」「.」「def.txt」の3つの部分に分割される。なお、Fileクラスのextnameメソッドを使えば、「.」を含めたファイル名の拡張子が返されるので、例えば、File.extname("abc.def.txt")とすれば、「.txt」が取り出せる。したがって、File.extname("abc.def.txt").partition(".")とすれば、3つ目の返り値として拡張子の「txt」が返せる。

文字列は配列と同じように扱える

 文字列に含まれる各文字は、配列と同じようにインデックスを指定すれば利用できる。この性質は文字列を取り扱うのにとても便利だ。こちらも簡単な例で見てみよう。

sample004.rb
str1 = "ruby"
for i in 0..str1.length - 1
  puts str1[i]
end
str1[3] = "l"
str1[4] = "e"
puts str1
リスト1.4 インデックスを指定して文字列に含まれる各文字を取り扱う

文字列の各文字は0から始まるインデックスを指定して参照したり、代入したりできる。元の文字列の長さを超えるようなインデックスを指定すれば文字を追加することもできる。

 実行例は以下の通り。for文により1文字ずつ出力した後、文字列の一部を変更して出力する。

コンソール
$ ruby sample004.rb
r
u
b
y
ruble
$
実行例1.4 1文字ずつ出力した後、文字列を変更して出力する

"ruby"という文字列の0文字目から3文字目を出力した後、3文字目を"l"に、4文字目を"e"に変更して出力した。

文字列に含まれる文字を順に利用するには

 リスト1.4の前半のように文字列の全ての文字を1つずつ処理する場合は、for文の代わりに、Stringクラスのeach_charメソッドを使うと便利だ。インデックスを使わないので、コードが簡潔になり、変数名のスペルミスなどの誤りも防げる。

sample005.rb
str1 = "ruby"
str1.each_char{|c|
  puts c
}
リスト1.5 each_charメソッドを使って文字列の文字を順に扱う

each_charメソッドでは、文字列を| |で囲まれた変数に1文字ずつ代入ながらその後の{ }の中の文を繰り返し実行する。

 実行結果は実行例1.4の前半と同じなので省略する。Stringクラスの性質やメソッドには他にも取り上げるべきことが数多くあるが、今回はこれぐらいにとどめておく。最後に、桁数の多い10進整数を3桁ごとにカンマで区切って出力するための関数の例を示しておこう。

数値に桁区切りスタイルを適用して文字列に変換するには

 数値を何桁かずつに区切るにはさまざまな方法(例えば正規表現を使って1行で記述する方法など)があるが、今回はRuby初心者向けに分かりやすいシンプルな方法でやってみよう。

 まず、数値を文字列に変換する。続いて、末尾から1文字ずつ取り出し、3文字ごとにカンマを挿入する。ここでは繰り返し処理の流れが見やすくなるように、末尾から1文字ずつ取り出さず、文字列を逆順に変換してから、先頭から1文字ずつ取り出そう。ここまでに説明してきたメソッドや、文字列を配列として取り扱う方法を応用すれば実現できる。

sample006.rb
def addcomma(num, sep)
  temp = num.to_s.reverse
  result = ""
  for i in 0..temp.length - 1
    if i % sep == 0 and i != 0
      result = "," + result
    end
    result = temp[i] + result
  end
  return result
end
puts(addcomma(1_000_000,3))
リスト1.6 指定した桁数ごとにカンマを挿入した文字列を作成する関数

ここで作成したaddcomma関数には数値を第1引数に、カンマを挿入する桁数を第2引数に指定する。addcomma関数の中では、reverseメソッドを使って文字列を逆順にしてから1文字ずつ取り出す。3文字ごとにカンマを挿入して、その文字を前につないでいけばよい。

 実行例は以下の通り。

コンソール
$ ruby sample006.rb
1,000,000
$ 
実行例1.6 数値に桁区切りスタイルを適用した例

ここでは3桁ごとにカンマを入れてみた。addcomma関数の第2引数に4を指定すれば4桁ごとに区切ることができる。ぜひ試してみてほしい。

 リスト1.6のaddcomma関数では骨格となる部分を作っただけなので、整数しか取り扱えない。そこで、すでに見たpartitionメソッドを使って、整数部と小数部に分けて処理を行い、小数点のある数値にも桁区切りスタイルを適用できるようにしよう。なお、文字列を逆順にしなくても、文字列を後ろから取り出していけば同じことができる。そのような処理を行う例を示しておく。

sample007.rb
def addcomma(num, sep)
  temp = num.to_s # 逆順にしない。単に文字列に変換するだけ

  # "."で3分割する
  intpart, p, fracpart = temp.partition(".")

  # 整数部にカンマを挿入する
  result = ""
  for i in 0..intpart.length - 1
    idx = intpart.length - i - 1 # iが増えればidxは減る
    if i % sep == 0 and i != 0
      result = "," + result
    end
    result = intpart[idx] + result
  end

  # 整数部と小数部を連結して返す
  if p == "."
    return result + p + fracpart
  else
    return result
  end

end
puts(addcomma(1_000_000.12,3))
リスト1.7 小数点がある数値に対応した桁区切りの関数例

Stringクラスのpartitionメソッドを使って、文字列を整数部と小数部に分ける。整数部の処理はリスト1.6と同じだが、インデックスの指定方法に注意。整数部の桁数(=intpart.length)が7であるとすると、変数iの値は「0, 1, 2, ..., 6」と増えていくが、idxの値は「6, 5, 4, ..., 0」と減っていく。このようにして文字列を後ろから処理する。最後に、小数部がある場合は、整数部に小数点と小数点以下を連結して返す。

 実行例は以下の通り。

コンソール
$ ruby sample007.rb
1,000,000.12
$ 
実行例1.7 小数点のある数値に桁区切りスタイルを適用した例

小数点以下があっても正しく桁区切りができるようになった。リスト1.7のaddcommaメソッドにさまざまな数値を指定して試してみるといい。

 今回は比較的小さな技をいくつか取り上げた後、それらを組み合わせることによって実用的なプログラムを作成する例を示した。次回もいくつかの小技とそれらの合わせ技を紹介したいと思う。

まとめ

 Rubyでは、数値リテラルの桁区切り記号として_が使える。_は実行時には無視されるので、桁数の大きな数値を見やすくするのに役に立つ。文字列を逆順に変換するメソッドには、非破壊的なreverseメソッドと破壊的なreverse!メソッドがある。partitionメソッドを使えば、文字列を「前半」「セパレーター」「後半」といった3つの部分に分割できる。また文字列は、配列と同じようにインデックスを指定することにより各文字を取り扱える。

処理対象:リテラル|数値リテラル|浮動小数点数 カテゴリ:文法 > リテラル
API:reverseメソッド|reverse!メソッド|partitionメソッド|each_charメソッド カテゴリ:Stringクラス
API:printf カテゴリ:組み込みライブラリ > Kernelモジュール

※以下では、本稿の前後を合わせて5回分(第4回~第8回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

4. Rubyをインストール/アップデートするには?(Windows編)

Windows上でのRubyプログラミングを始める入門者向けに、Ruby環境の構築方法、複数バージョンのインストール方法、バージョンのアップデート方法を説明する。

5. 代入により決まる変数のデータ型/丸め誤差が発生する浮動小数点数の比較― コーディングミスを防ぐには?(3)

初心者向けにRubyプログラミングの落とし穴を紹介。代入する値により変数のデータ型が決まることに関する注意点と、浮動小数点数の比較における丸め誤差の問題と回避方法について説明する。

6. 【現在、表示中】≫ 桁区切り数値の記述と出力/文字列を逆順に/文字列の分割/文字列の各文字の利用 ― コーディングミスを防ぐには?(4)

初心者向けにRubyプログラミングの落とし穴を紹介。桁区切り指定で数値リテラルを記述する方法や、桁区切りの数値文字列を出力する方法、文字列を逆順に並べ替える方法、文字列を文字列で分割する方法、文字列を1文字ずつ扱う方法を説明する。

7. 複素数(Complexクラス)を活用するには?

組み込みライブラリに含まれるComplexクラスによる基本的な複素数の取り扱い方、複素数の四則演算、平面上の点(ベクトル)の操作方法を説明する。

8. if修飾子/unless文/case文 ― ちょっと便利な条件分岐の構文とは?

Rubyには、if文のような一般的なもの以外にも、if修飾子/unless文/unless修飾子/case文といった便利な条件分岐の構文が用意されている。その基本的な使い方を解説。

イベント情報(メディアスポンサーです)

Azure Central の記事内容の紹介

Twitterでつぶやこう!


Build Insider賛同企業・団体

Build Insiderは、以下の企業・団体の支援を受けて活動しています(募集概要)。

ゴールドレベル

  • 日本マイクロソフト株式会社
  • グレープシティ株式会社