Hatena::Grouprubyist

Ruby初心者prinyの学習帳 RSSフィード

2009-01-08練習:たのしいRuby P.285 (1) (2)

練習問題 (1)
あるファイルの内容を別のファイルにそのままコピーするメソッドをを定義しましょう。
このメソッドは、2 つの引数(コピー元のファイル名, コピー先のファイル名)を取ります。

書いたもの

def copy_file(from, to)
  src = open(from)  # コピー元のファイルを開く
  begin
    # コピー先のファイルを開く
    dest = open(to, "w")
    # コピー元のファイルを全部読んで、それをコピー先のファイルに書き込む
    dest.write(src.read)
    dest.close
  rescue  SystemCallError
    $stderr.puts "SystemCallError: #$!"
  rescue
    $stderr.puts "Error: #$!"
  else
    puts "#{from}#{to} にコピーしました。"
  ensure
    src.close
  end
end

copy_file("hello.txt", "copy.txt")

例外処理の項に答えになるサンプルが。

そのままでは何なので、エラーが発生しなかったら、メッセージが表示されるようにしてみた。


練習問題 (2)
Unix で使われる tail コマンドと似たことができるメソッド tail を定義しましょう。
tail メソッドは、2 つの引数(行数, ファイル名)を取ります。

これに、krystalさんの日記へのentottoさんのコメント にある、tail コマンドの -f オプションみたいなものも追加してみよう。
「終了しないで、ファイルが更新されたら、追加された行を表示する」を -f オプションを付けたときだけおこなうようにする方法がわからないので、「指定した行数表示した後、終了しないで、ファイルが更新されたら、追加された行を表示する」という仕様にする。

書いたもの

def tail(line_num, file)
  queue = []
  open(file){|io|
    while line = io.gets
      queue.push(line)
      if queue.size > line_num
        queue.shift
      end
    end
    queue.each{|line| print line }
  
    while true
      io.seek(0, IO::SEEK_CUR)
      if add_line = io.read
        print add_line
      end
      sleep(0.5)
    end
  }
end

tail(3, "hello.txt")

先達の知恵をお借りして、それっぽく動くものができたように思うけど、実際はどうなんだろう。

「ファイルの末尾までデータを読み取ると、それ以上読み込みができなくなるプラットフォームがあって、seek すると(副作用で)EOF フラグが解除される」みたいな記述があったのだけど、「io.seek(0, IO::SEEK_CUR)」をコメントアウトしても、同じ動きをしているように見えるんだよなぁ。

終わらせるときは、[Ctrl]+[C]キーを押す。tail コマンドで -f オプションを付けたときと同じ。

別の話だけど、リアルタイムにログを表示するには、tailf コマンドのほうが tail コマンドより負荷が低いのでよいそうだ。

2009-01-06練習:たのしいRuby P.150 Unix の wc コマンドみたいなもの

例外処理の書き方の練習。

ファイル名を引数に指定すると、各ファイルごとの行数、単語数、バイト数と、全ファイルの合計を出力する。

書いたもの (wc.rb)

lines = 0        # ファイル内の行数
words = 0        # ファイル内の単語数
bytes = 0        # ファイル内のバイト数

lines_total = 0  # 行数の合計
words_total = 0  # 単語数の合計
bytes_total = 0  # 文字数の合計

ARGV.each{|file|
  begin
    open(file){|input|
      while line = input.gets
        lines += 1
        bytes += line.size
        words += line.split(nil).size  # 行ごとの単語数を得る。空白類文字だけが単語の区切りとする
      end
    }
    printf("%8d %8d %8d %s\n", lines, words, bytes, file)  # 出力を整形する
    lines_total += lines
    words_total += words
    bytes_total += bytes
    hoge += momo  # エラーが発生するはずのものを入れてみる
  rescue  SystemCallError
    $stderr.puts "SystemCallError: #$!"
  rescue
    $stderr.puts "Error: #$!"
  end
}
printf("%8d %8d %8d %s\n", lines_total, words_total, bytes_total, "total")

サンプルをちょこちょこいじった。

  • 変数名を変えた。
    • サンプルの c、l、w などでもわかりそうだけれど、省略形ではないものにしてみた。
  • ファイルのオープンでブロックを使うようにしてみた。
    • ファイルのオープンにはブロックを使うといいよ。ブロックの実行が終了すると、ファイルは自動的にクローズされるから、ファイルを開きっぱなしにしてしまうことを防げるし、ブロックの閉じ忘れは構文エラーになるから、間違いに気づきやすい。と、以前、教えてもらったので。
  • 文字列を単語に分ける方法を変えた。
    • split の引数に nil を指定すれば、先頭と末尾の空白類文字を取り除いてから、残りを空白類文字で分けてくれる。サンプルの行頭の空白類文字を取り除いて、正規表現を使って空白類文字で文字列を分ける、と同じことができる。
  • rescue 節で SystemCallError を使ってみた。
  • エラー出力は $stderr を使うようにした。
    • これも、以前、教えてもらったこと。
  • 例外オブジェクトが代入される変数名を指定しないでみた。
    • 指定しなくても、自動的にセットされるので。
    • 明示的に指定したほうがいいのかなあ。よいエラー処理って難しい。

コマンドラインで入力したもの

ruby wc.rb bbs.data access.txt testes.rb hoge.rb  

「bbs.data」「access.txt」「testes.rb」は、作成した wc.rb と同じフォルダに存在するファイル。「hoge.rb」は存在しないファイル。

実行結果

       3        6       84 bbs.data
Error: undefined local variable or method `momo' for main:Object
       9       60      450 access.txt
Error: undefined local variable or method `momo' for main:Object
      48      126     1341 testes.rb
Error: undefined local variable or method `momo' for main:Object
SystemCallError: No such file or directory - hoge.rb
      60      192     1875 total

エラーが発生するように意図的に仕込んだ「hoge += momo」のせいで見づらくなっているけど、思ったとおりの動作はしてるっぽい。

ブロックを使っていても、ファイルが閉じられなくなることはあるのかな。開いたファイルは必ず閉じるようにしたかったら、ensure 節に「ファイルを閉じます」と書くべきなんだろうか。

2009-01-01練習:たのしいRuby P.266 (2) (3)

練習問題 (2)
「"オブジェクト指向は難しい! なんて難しいんだ!"」という文字列を
gsub メソッドを使って「"オブジェクト指向は簡単だ! なんて簡単なんだ!"」
という文字列に直しましょう。

コード

$KCODE = "SJIS"

msg = "オブジェクト指向は難しい! なんて難しいんだ!"
p msg.gsub(/難しい/, "簡単だ").sub(/簡単だんだ/, "簡単なんだ")

実行結果

"オブジェクト指向は簡単だ! なんて簡単なんだ!"

gsub メソッドではなく、sub でいいような気がする。


練習問題 (3)
アルファベットとハイフンからなる文字列を与えられると、ハイフンで
区切られた部分を Capitalize するようなメソッド word_capitalize を
定義しましょう。

コード

def word_capitalize(str)
  str.capitalize.gsub(/-([a-zA-Z]+)/) { "-" + $1.capitalize }
end

p word_capitalize("in-reply-to")
p word_capitalize("X-MAILER")

実行結果

"In-Reply-To"
"X-Mailer"

FogeFoge2011/09/08 19:06It's really great that peploe are sharing this information.

mmdtkfuujmmmdtkfuujm2011/09/09 00:08QpryQZ <a href="http://fneoedmihpas.com/">fneoedmihpas</a>

wfujwwoooiwfujwwoooi2011/09/09 20:51bBWNok , [url=http://glrlbpehckjk.com/]glrlbpehckjk[/url], [link=http://zvfrfwxcnxni.com/]zvfrfwxcnxni[/link], http://fbnsbdrvvxhn.com/

bdsuijwxhbdsuijwxh2011/09/12 19:29ufCxhC , [url=http://oiyacprreiqg.com/]oiyacprreiqg[/url], [link=http://dqjiwxjsltcj.com/]dqjiwxjsltcj[/link], http://kzgaqgbjrhvg.com/

2008-12-31練習:たのしいRuby P.266 (1)

練習問題 (1)
メールアドレスのローカルパートを $1 として、ドメイン名を $2 として取得する正規表現を作りましょう。

アットマークが 1 つだけ含まれる文字列を対象と考えて、/\A([^@]+)@([^@]+)\z/ でいいのかな。

フリー百科事典『ウィキペディア(Wikipedia)』- メールアドレス を参考に、もう少しメールアドレスっぽいものが対象になるようにしよう。

実際のメールアドレスでは、ローカルパートで、quoted-string の形式だとさらに使える文字が増えるとか、ピリオドが連続しているのはダメとか、ドメイン名の先頭の文字はアルファベットの大文字、小文字か数字である必要があるとか、ほかにも、いろいろあるみたいだけど、深みにはまって抜けられなくなりそうなので、quoted-string の形式で使える文字のことは置いといて、さらに、条件つき(最後には使えないよ、とか)で使える文字は、単純に「使える文字」として扱うことにする。

コード

def get_localpart_and_domain(str)
  /\A([a-zA-Z0-9!#$%&'*+\/=?^_`{|}~\.-]+)@([a-zA-Z0-9\.-]+)\z/ =~ str
  [$1, $2]
end

p get_localpart_and_domain("Abc@example.com")
p get_localpart_and_domain("hoge+123/Y=A*X@example.com")
p get_localpart_and_domain("!#$%&'*+-/=?^_`.{|}~@example.com")

実行結果

あらま。エラーになった。

get_localpart_and_domain.rb:2: syntax error, unexpected $undefined
  /\A([a-zA-Z0-9!#$%&'*+\/=?^_`{|}~\.-]+)@([a-zA-Z0-9\.-]+)\z/ =~ str
                   ^
get_localpart_and_domain.rb:8: unterminated string meets end of file
get_localpart_and_domain.rb:8: syntax error, unexpected tSTRING_END, expecting tSTRING_CON
TENT or tREGEXP_END or tSTRING_DBEG or tSTRING_DVAR

閉じ忘れがあるって言われているのかな。正規表現のオプションに x を指定していないのに、# の後ろがコメントとして扱われている?よくわからないまま、なんとなく、# と $ の場所を入れ替えてみた。

コード 2

def get_localpart_and_domain(str)
  /\A([a-zA-Z0-9!$#%&'*+\/=?^_`{|}~\.-]+)@([a-zA-Z0-9\.-]+)\z/ =~ str
  [$1, $2]
end

p get_localpart_and_domain("Abc@example.com")
p get_localpart_and_domain("hoge+123/Y=A*X@example.com")
p get_localpart_and_domain("!$#%&'*+-/=?^_`.{|}~@example.com")

実行結果 2

["Abc", "example.com"]
["hoge+123/Y=A*X", "example.com"]
["!$#%&'*+-/=?^_`.{|}~", "example.com"]

エラーが出なくなった。「#$」という順番がダメだったってこと?今度は「#!$」にしてみた。

コード 3

def get_localpart_and_domain(str)
  /\A([a-zA-Z0-9#!$%&'*+\/=?^_`{|}~\.-]+)@([a-zA-Z0-9\.-]+)\z/ =~ str
  [$1, $2]
end

p get_localpart_and_domain("Abc@example.com")
p get_localpart_and_domain("hoge+123/Y=A*X@example.com")
p get_localpart_and_domain("#!$%&'*+-/=?^_`.{|}~@example.com")

実行結果 3

["Abc", "example.com"]
["hoge+123/Y=A*X", "example.com"]
["#!$%&'*+-/=?^_`.{|}~", "example.com"]

これもエラーにはならなかった。

なぞな動き。練習問題の本質ではなさそうなとこで、はまってしまった。検索しようにも「#$」では検索できないし。こまった・・・。

holysugarholysugar2009/01/02 02:38#による式展開をしようとしてますね。

http://www.ruby-lang.org/ja/man/html/_A5EAA5C6A5E9A5EB.html#a.bc.b0.c5.b8.b3.ab
を見てください。
正規表現中で特殊な働きをする文字については常にエスケープしておくと不安が無くなるかもしれません。

prinypriny2009/01/02 21:13なるほど。ありがとうございます。勉強になりました。

2008-12-24練習:たのしいRuby P.244 (2) OrderedHash クラスを定義する

順番を保存し、以下のような動きをするハッシュ、OrderedHash クラスを定義する。

oh = OrderedHash.new
oh["one"] = 1
oh["two"] = 2
oh["three"] = 3
oh["two"] = 4
p oh.keys #=> ["one", "two", "three"]
p oh.values #=> [1, 4, 3]

書いたもの

class OrderedHash
  def initialize
    # キーの順番を保持する配列をつくる
    @index = []
    # キーとバリューを結びつけて保存するハッシュをつくる
    @element = {}
  end
  
  # キーを指定してバリューを取得する
  def [](key)
    @element[key]
  end
  
  # ハッシュに要素を追加する
  def []=(key, value)
    @element[key] = value
    unless @index.include?(key)
      @index << key
    end
  end
  
  # キーに対する関連を取り除く。取り除かれた値を返す
  def delete(key)
    @index.delete(key)
    @element.delete(key)
  end
  
  # すべてのキーの配列を返す
  def keys
    @index.dup
  end
  
  # すべてのバリューの配列を返す
  def values
    @index.map{|key| @element[key]}
  end
  
  # each でハッシュに入れた順番に処理する
  def each
    @index.each do |key|
      yield([key, self[key]])
    end
    self
  end
end

animals = OrderedHash.new
animals["cat"] = "meow"
animals["dog"] = "bowwow"
animals["owl"] = "tu-whit, tu-whoo"
animals["dog"] = "yap yap"
animals["sheep"] = "maa"

p animals["sheep"]
puts

animals.each do |i|
  p i
end
puts

p animals.delete("owl")
p animals.delete("rabbit")
puts

p animals.keys
p animals.values

実行結果

"maa"

["cat", "meow"]
["dog", "yap yap"]
["owl", "tu-whit, tu-whoo"]
["sheep", "maa"]

"tu-whit, tu-whoo"
nil

["cat", "dog", "sheep"]
["meow", "yap yap", "maa"]

順番が保存されていることがわかりやすいのは、OrderedHash#each したときかなと思ったのでつくってみた。
あと、追加できるなら、取り除くのもできたほうがいいと思ったので OrderedHash#delete も。

メソッド名にアンダーバー以外の記号([] と =)を使うのは初めてで、ふしぎなかんじがした。

JaylinJaylin2011/09/07 20:59So true. Hoentsy and everything recognized.

kjsmlrsbkjsmlrsb2011/09/07 22:37UfxjO0 <a href="http://ispuayidiexg.com/">ispuayidiexg</a>

gtqprkibgtqprkib2011/09/08 20:48Y3FXr6 , [url=http://bpuhojptmeya.com/]bpuhojptmeya[/url], [link=http://vbrqtkfusrcx.com/]vbrqtkfusrcx[/link], http://qkqkyhuwvpwf.com/

hokmzccxhokmzccx2011/09/09 17:55HdeVtz <a href="http://mkwgswniuwhi.com/">mkwgswniuwhi</a>

lqkncbgphlqkncbgph2011/09/10 22:57vrgfkT , [url=http://tclogszrvoeg.com/]tclogszrvoeg[/url], [link=http://ltaccopoogfv.com/]ltaccopoogfv[/link], http://seyoxmubqjzt.com/