テキスト処理プログラムで、表っぽいものを作りたいときに、 printfでの桁揃えが役立つ。
sum = 0 shopping = [150, 2000, 10] shopping.each {|item| printf(" %7d円\n", item) sum += item } printf("合計: %7d円\n", sum)
結果は以下のとおり。
150円 2000円 10円 合計: 2160円
美しい。
では1点ずつ商品名を入れてみよう。
#!/usr/bin/env ruby # -*- coding: utf-8 -*- sum = 0 shopping = {"りんご" => 150, "図鑑" => 2000, "お菓子棒" => 10} shopping.each {|item, price| printf("%-10s%7d円\n", item, price) sum += price } printf("%-10s%7d円\n", "合計:", sum)
さー実行してみようか。はい、残念。ガタガタになる。
(Ruby1.8まで) 図鑑 2000円 りんご 150円 お菓子棒 10円 合計: 2160円 (Ruby1.9以降) りんご 150円 図鑑 2000円 お菓子棒 10円 合計: 2160円
Ruby1.8まではutf-8日本語が3バイトなので3桁で数え、 Ruby1.9以降はどんな文字でも1字は1桁だと数えるため、 fixed width font な仮想端末で漢字==2桁という風習を期待すると 悲しい目に遭う。実はこれ、Ruby1.8で euc-jp か sjis を使えば 2バイト漢字==2桁という風に処理してくれるのでちゃんと揃っていた。
Ruby1.9以降では日本語printfは揃わないのか! utf-8だと絶望的なのか! と悩んでいたが、これは Ruby1.9 以降でも utf-8 でも逃げられる手があった。 一度内部的に euc-jp にして、Ruby1.9 ではさらにバイナリモードに変えてから printfに幅計算をさせるとよい。
簡単な例では以下のようになる。上記プログラムのprintfを以下のように変える。
require 'kconv' format = "%-10s%7d円\n".toeuc print(if RUBY_VERSION < "1.9" # Ruby1.8 sprintf(format, item.toeuc, price) else # Ruby1.9以降 sprintf(format.force_encoding("binary"), item.toeuc.force_encoding("binary"), price) end.toutf8)
つまり、一度euc-jpに変えてsprintfしたものを、出力するときにまた UTF-8に変換し直している。Ruby1.9の場合は、euc-jpにしただけでは 文字数基準の桁幅計算になるので、さらにbinaryにしてバイト数計算で やらせている。
ただ、printfごとにやるのはたいへんなので、以下の定義を読み込ませる。
class String require 'kconv' if defined?("".force_encoding) def toeucbin() self.toeuc.force_encoding("binary") end else def toeucbin() self.toeuc end end end class IO def printf(*args) out = sprintf(*(args.collect{|x| x.is_a?(String) ? x.toeucbin : x})) print out.toutf8 end end class Object def printf(*args) if args[0].is_a?(String) $stdout.printf(*args) else port = args.shift port.printf(*args) end end end
これは、出力コードがutf8と決め打ちした場合。 kconv以外を希望の場合は適宜書き換えのこと。
さて、これをロードした上で以下のプログラムを動かしてみる。
#!/usr/bin/env ruby # -*- coding: utf-8 -*- require './kprintf.rb' sum = 0 shopping = {"りんご" => 150, "図鑑" => 2000, "お菓子棒" => 10} shopping.each {|item, price| printf("%-10s%7d円\n", item, price) sum += price } printf("%-10s%7d円\n", "合計:", sum)
はい実行してみよう。
(Ruby1.8まで) りんご 150円 お菓子棒 10円 図鑑 2000円 合計: 2160円 (Ruby1.9以降) りんご 150円 お菓子棒 10円 図鑑 2000円 合計: 2160円
おめでとう。