問題で与えられた式をそのまま使ってメソッドを定義します。
def cels2fahr(cels) return cels.to_f * 9 / 5 + 32 end
与えられた摂氏温度cels
を、to_f
を使ってFloatに変換しています(「10.4 数値型の変換」参照)。この変換を行わないと、cels
がIntegerだった場合、正しく計算されません(5で割るところの結果がIntegerになってしまいます)。
(1)の逆の計算を定義します。 「+」と「*」の優先順位の違いから括弧が必要なことに注意してください。
def fahr2cels(fahr) return (fahr.to_f - 32) * 5 / 9 end
さらに、華氏を1度から100度まで変えるには、upto
メソッドを使います。
1.upto(100) do |i| print i, " ", fahr2cels(i), "\n" end
「10.6 乱数」で説明したrand
メソッドを使います。単に「rand(6)
」とした場合は、0から5の値が返るので結果に1を足します。
def dice return rand(6) + 1 end
単純にdice+dice+...
と書いても可能ではありますが、やはりここは繰り返しを使うべきでしょう。
def dice10 ret = 0 10.times do ret += dice end ret end
まず、2より小さい数は素数でないとします。それ以上の数は、2からその数の平方根まで割り算を行って、すべての剰余が0でないことを確認します。
def prime?(num) return false if num < 2 2.upto(Math.sqrt(num)) do |i| if num % i == 0 return false end end return true end
二つの方法を挙げます。もっと小さな配列なら「ary = [1, 2, 3, ...]
」とリテラルで定義してもいいでしょう。
# 空の配列を作って、1から100までの値を格納する a = [] 100.times{|i| a[i] = i + 1 } # 91ページで少しだけ触れた範囲オブジェクトのto_aメソッドを使う a = (1..100).to_a
普通にArray#collect
を使えば、新しい配列が作れます。同じ配列をそのまま100倍したいときは、「!」がついたArray#collect!
を使います。
# 配列を作成する a = (1..100).to_a # 配列の全ての要素を100倍した値を含む新しい配列を作る a2 = a.collect{|i| i * 100 } p a2 # 配列の全ての要素を100倍する a.collect!{|i| i * 100 } p a
条件に当てはまるものをとりのぞくには、Array#reject
を使います。
# 配列を作成する a = (1..100).to_a # aryから3の倍数だけを取り出す a3 = a.reject{|i| i % 3 != 0 } p a3 # なお、条件に当てはまるものだけ返す、!が不要なselectというメソッドもあります a4 = a.select{|i| i % 3 == 0 } p a4 # aryから3の倍数以外の数を削除する a.reject!{|i| i % 3 != 0 } p a
Array#sort
とArray#sort_by
を使う方法では、ブロックの結果を-1倍することによって、逆にソートされるようにしています。
# 配列を作成する a = (1..100).to_a # (a) Array#reverseを使う a2 = a.reverse p a2 # (b) Array#sortを使う a2 = ary.sort{|n1, n2| -(n1 <=> n2) } p a2 # (c) Array#sort_byを使う a2 = a.sort_by{|i| -i } p a2
別解として、Array#inject
を使う方法があります。Array#each
を使う場合は値を蓄えておくための変数(例中のresult
)を用意する必要がありますが、Array#inject
を使う場合は必要ありません。
# 配列を作成する a = (1..100).to_a # (a) Array#eachで和を求める result = 0 a.each{|i| result += i } p result # (b) Array#injectで和を求める p a.inject(0){|memo, i| memo += i }
取り出す要素の先頭のインデックスと必要な要素の数を指定します。
# 配列を作成する ary = (1..100).to_a result = Array.new 10.times do |i| result << ary[i*10, 10] end p result
Array#eachでary1の各要素にループさせるのと同時に、インデックスを使ってary2の各要素にアクセスするのがポイントです。また、別解として、Array#zip
というメソッドを使うと、2つの配列に対して同時に各要素を参照することができます。
def sum_array(ary1, ary2) result = Array.new i = 0 ary1.each do |elem1| result << elem1 + ary2[i] i+=1 end return result end # Array#zipを使った別解 def sum_array_zip(ary1, ary2) result = Array.new ary1.zip(ary2){|a, b| result << a + b } return result end p sum_array([1, 2, 3], [4, 6, 8])
単純にsplit
で分割すればだいじょうぶです。
str = "Ruby is an object oriented programming language" ary = str.split p ary
アルファベットだけの場合、引数なしのArray#sort
でソートされます。この辺りは配列の復習にもなっています。
str = "Ruby is an object oriented programming language" ary = str.split p ary.sort
こちらではArray#sort_by
を使って、引数の比較をしています。その際、String#downcase
で強制的に小文字にしてから比較するため、大文字小文字の区別がなくなります。
str = "Ruby is an object oriented programming language" ary = str.split p ary.sort_by{|s| s.downcase }
文字列の先頭を大文字にするにはString#capitalize
を使います。これを配列の各要素に行うため、Array#collect
と組み合わせています。
str = "Ruby is an object oriented programming language" ary = str.split cap_ary = ary.collect{|word| word.capitalize } str = "" cap_ary.each do |s| str << s+" " end p str ## 別解 p cap_ary.join(" ")
なお、文字列の連結には別解で示したArray#join
を使うと簡単にできです。
このメソッドは配列に含まれる文字列を連結するもので、
要素と要素の間に挿入する文字列を引数で指定することもできます。
p ["a", "b", "c"].join #=> "abc" p ["a", "b", "c"].join("-") #=> "a-b-c"
ハッシュを作成して、文字をキー、出現した回数を値として記録します。最後に文字をソートして回数個数のアスタリスクとともに出力します。
str = "Ruby is an object oriented programming language" count = Hash.new str.each_char do |c| count[c] = 0 unless count[c] count[c] += 1 end count.keys.sort.each do |c| printf("'%s': %s\n", c, "*" * count[c]) end
ハッシュを初期化する際にデフォルト値として0を返すようにすると次のようになります。
str = "Ruby is an object oriented programming language" count = Hash.new(0) str.each_char do |c| count[c] += 1 end count.keys.sort.each do |c| printf("'%s': %s\n", c, "*" * count[c]) end
この問題は応用問題でかなり難しいです。
各桁の数字をString#gsub!
で置き換えていきます。桁の切り出しには正規表現を駆使しています。
def kan2num(string) digit4 = digit3 = digit2 = digit1 = "0" nstring = string.dup nstring.gsub!(/一/, "1") nstring.gsub!(/二/, "2") nstring.gsub!(/三/, "3") nstring.gsub!(/四/, "4") nstring.gsub!(/五/, "5") nstring.gsub!(/六/, "6") nstring.gsub!(/七/, "7") nstring.gsub!(/八/, "8") nstring.gsub!(/九/, "9") if nstring =~ /((\d)?千)?((\d)?百)?((\d)?十)?(\d)?$/ if $1 digit4 = $2 || "1" end if $3 digit3 = $4 || "1" end if $5 digit2 = $6 || "1" end digit1 = $7 || "0" end return (digit4+digit3+digit2+digit1).to_i end p kan2num("七千八百二十三") p kan2num("千八百二十三") p kan2num("八百二十三") p kan2num("百二十三") p kan2num("百三") p kan2num("二十三") p kan2num("十三") p kan2num("三")
1つずつ個別に定義してもよいのですが、まとめて定義してみます。
wday = { "sunday" => "日曜日", "monday" => "月曜日", "tuesday" => "火曜日", "wedensday" => "水曜日", "thursday" => "木曜日", "friday" => "金曜日", "saturday" => "土曜日", }
普通にHash#size
を使えば求まります。
wday = { "sunday" => "日曜日", "monday" => "月曜日", "tuesday" => "火曜日", "wedensday" => "水曜日", "thursday" => "木曜日", "friday" => "金曜日", "saturday" => "土曜日", } p wday.size #=> 7
普通に配列でキーを与えてもいいのですが、簡単のため%w
を使ってみます。
wday = { "sunday" => "日曜日", "monday" => "月曜日", "tuesday" => "火曜日", "wedensday" => "水曜日", "thursday" => "木曜日", "friday" => "金曜日", "saturday" => "土曜日", } %w(sunday monday tuesday wedensday thursday friday saturday).each do |day| puts "「#{day}」は#{wday[day]}のことです。" end
文字列をString#split
で分割したあと、Array#shift
で一つずつ取り出して、ハッシュを作っていきます。
def str2hash(str) hash = Hash.new() array = str.split(/\s+/) while key = array.shift value = array.shift hash[key] = value end return hash end p str2hash("bule 青 white 白\nred 赤");
メールアドレスは複雑なルールがあったり、さらにはルールに適合しないのに実際には使われているアドレスもあったりするなど、ややこしい事情もあるのですが、ここではわりきった形で解析してみました。
def get_local_and_domain(str) str =~ /^([^@]+)@(.*)$/ localpart = $1 domain = $2 return [localpart, domain] end p get_local_and_domain("info@example.com")
「難しい」という部分が2回出現しますが、1回の置換で置き換えるのは難しいので2回に分けています。「難しい」を「簡単だ」に置き換えると、「難しいんだ」の部分が「簡単だんだ」となってしまうため、先に「難しいんだ」を「簡単なんだ」に置き換えています。
s = "オブジェクト指向は難しい! なんて難しいんだ!" puts s.gsub(/難しいんだ/, "簡単なんだ").gsub(/難しい/, "簡単だ")
基本的には文字列クラスの練習問題(4)と同様ですが、「-」の正規表現を作るときにはエスケープします。回答例ではメソッドチェインを使って1行で書いてみました。
def word_capitalize(str) return str.split(/\-/).collect{|w| w.capitalize}.join('-') end p word_capitalize("in-reply-to") #=> "In-Reply-To" p word_capitalize("X-MAILER") #=> "X-Mailer"
行数、単語数、文字数を出力するwc
メソッドを作成しました。この例ではString#split
を使って行を単語に分割していますが、行頭に空白を含む場合はString#split
の結果に空白の文字列が含まれるため、これを削除していることに注意してください。
def wc(file) nline = nword = nchar = 0 File.open(file){|io| io.each{|line| words = line.split(/\s+/).reject{|w| w.empty? } nline += 1 nword += words.length nchar += line.length } } puts "lines=#{nline} words=#{nword} chars=#{nchar}" end wc(__FILE__)
これは個別にスクリプトを書いてみます。まず、ファイルの逆順です。
IO#readlines
で行ごとに読み込んだ後、IO#rewind
で先頭に戻し、IO#truncate
で空にしてから、IO#reverse
で逆順にしたものを書きこみます。
def reverse(input) open(input, "r+") do |f| lines = f.readlines f.rewind f.truncate(0) f.write lines.reverse.join() end end reverse(ARGV[0])
次に、1行先頭出力です。これは、実は先ほどのスクリプトを少しいじるだけで実現できます。
def reverse(input) open(input, "r+") do |f| lines = f.readlines f.rewind f.truncate(0) f.write lines[0] end end reverse(ARGV[0])
そして、1行末尾出力です。こちらはちょうど(a)(b)を組み合わせた形です。
def reverse(input) open(input, "r+") do |f| lines = f.readlines f.rewind f.truncate(0) f.write lines.reverse[0] end end reverse(ARGV[0])
いったんqueue
という変数に読み込んだ行を保存させるのがポイントです。
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, __FILE__) puts "===" tail(3, __FILE__)
FileTest.directory?
を使ってディレクトリではないものを排除した後、Dir.open
を使ってディレクトリ内のファイルのファイル名を調べていきます。
def print_libraries $:.each do |path| next unless FileTest.directory?(path) Dir.open(path) do |dir| dir.each do |name| if name =~ /\.rb$/i puts name end end end end end print_libraries
なお、本文中では詳しく取り上げていませんが、RubyのライブラリにはRubyで記述されたものの他に、C言語などで記述された拡張ライブラリがあります。拡張ライブラリは「.rb
」ではなく、「.dll
」や「.so
」といったプラットフォームによって異なる拡張子を持ったファイル名になっています。
rbconfig
ライブラリから、この拡張子を取得して拡張ライブラリにも対応させたバージョンを以下に示します。
require "rbconfig" def print_libraries $:.each do |path| next unless FileTest.directory?(path) dlext = RbConfig::CONFIG["DLEXT"] Dir.open(path) do |dir| dir.each do |name| if name =~ /\.rb$/i || name =~ /\.#{dlext}$/i puts name end end end end end print_libraries
findライブラリの応用です。
require "find" def du(path) result = 0 Find.find(path){|f| if File.file?(f) result += File.size(f) end } printf("%d %s\n", result, path) return result end du(ARGV[0] || ".")
日時を表す日本語文字列の解析は正規表現でがんばります。現在の時刻はTime.now
で取得し、与えられた文字列から取得できなかった項目を補います。最後に、Time.mktime
で時刻を生成します。
def jparsedate(str) now = Time.now year = now.year month = now.month day = now.day hour = now.hour min = now.min sec = now.sec str.scan(/(午前|午後)?(\d+)(年|月|日|時|分|秒)/) do case $3 when "年" year = $2.to_i when "月" month = $2.to_i when "日" day = $2.to_i when "時" hour = $2.to_i hour += 12 if $1 == "午後" when "分" min = $2.to_i when "秒" sec = $2.to_i end end return Time.mktime(year, month, day, hour, min, sec) end p jparsedate("2010年12月23日午後8時17分50秒") p jparsedate("12月23日午後8時17分50秒") p jparsedate("午前8時17分50秒") p jparsedate("8時17分50秒")
「.」で始まるファイルを削除してから、File.mtimeで取得できる日時の順にソートします。最後にファイル名と日付を出力します。
def ls_t(path) entries = Dir.entries(path) # エントリを取得 entries.reject!{|name| /^\./ =~ name } # "."で始まるファイルを削除 mtimes = Hash.new # mtimeを収集しながらソート entries = entries.sort_by do |name| mtimes[name] = File.mtime(File.join(path, name)) end entries.each do |name| printf("%-40s %s\n", name, mtimes[name]) # ファイル名とmtimeを表示 end rescue => ex puts ex.message end ls_t(ARGV[0] || ".")
文房具の万年カレンダーの要領でカレンダーを整形する例を紹介します。2月30日などの存在しない日付は、月末の日付と比較することで弾いています。また、テーブル中の日付が存在しない部分も同じ条件で弾くために「99」で初期化しています。
require "date" module Calendar WEEK_TABLE = [ [99, 99, 99, 99, 99, 99, 1, 2, 3, 4, 5, 6, 7], [ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], [ 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21], [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28], [23, 24, 25, 26, 27, 28, 29, 30, 31, 99, 99, 99, 99], [30, 31, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99], ] module_function def cal(year, month) first = Date.new(year, month, 1) # 指定された月の1日 end_of_month = ((first >> 1) - 1).day # 翌月の1日の前日 start = 6 - first.wday # テーブルの何処から表示するか puts first.strftime("%B %Y").center(21) puts " Su Mo Tu We Th Fr St" WEEK_TABLE.each do |week| buf = "" week[start, 7].each do |day| if day > end_of_month buf << " " else buf << sprintf("%3d", day) end end puts buf end end end t = Date.today Calendar.cal(t.year, t.month)