Hatena::Grouprubyist

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

2008-12-09練習:たのしいRuby P.244 (1) str2hash を定義する

配列の %w のように、空白で区切られた文字列をハッシュに変換するメソッド str2hash を定義する。

$KCODE = "SJIS"

def str2hash(str)
  # 空白文字類を区切りにして文字列を単語に分ける
  words = str.split(nil)
  # [キー, 値, ・・・] の配列をハッシュにする
  Hash[*words]
end

p str2hash("blue 青 white 白\nred 赤")

実行結果

{"white"=>"白", "blue"=>"青", "red"=>"赤"}

2008-12-08Send + More = Money

krystalさんの日記Send + More = Money を解くものを Array#permutation を使って書いてみる。

# 0 から 9 までの数字から 8 個を順に取り出す組み合わせのリスト
num_permutation = (0..9).to_a.permutation(8).to_a

# Send + More = Money のチェック
def check(s, e, n, d, m, o, r, y)
  send = s * 1000 + e * 100 + n * 10 + d
  more = m * 1000 + o * 100 + r * 10 + e
  money = m * 10000 + o * 1000 + n * 100 + e * 10 + y
  if send + more == money && s * m > 1 
    return true
  else
    return false
  end
end

num_permutation.each do |comb|
  s, e, n, d, m, o, r, y = comb[0, 8]
  if check(s, e, n, d, m, o, r, y) == true
    puts "Send = #{s}#{e}#{n}#{d}, More = #{m}#{o}#{r}#{e}, Money = #{m}#{o}#{n}#{e}#{y}"
    exit
  end
end

実行したら、コマンドラインでカーソルが 20 回近く点滅してた。
4 桁の整数 + 4 桁の整数の最大値は 9,999 + 9,999 = 19,998 で、M = 1 なのはすぐにわかるから、それ以外の数字を求めるようにしてみよう。

# 0 と 2 から 9 までの数字から 7 個を順に取り出す組み合わせのリスト
num_permutation_list = [0, *2..9].permutation(7).to_a

# Send + More = Money のチェック
def check(s, e, n, d, o, r, y)
  send = s * 1000 + e * 100 + n * 10 + d
  more = 1000 + o * 100 + r * 10 + e
  money = 10000 + o * 1000 + n * 100 + e * 10 + y
  if send + more == money && s != 0
    return true
  else
    return false
  end
end

num_permutation_list.each do |comb|
  s, e, n, d, o, r, y = comb[0, 7]
  if check(s, e, n, d, o, r, y) == true
    puts "Send = #{s}#{e}#{n}#{d}, More = 1#{o}#{r}#{e}, Money = 1#{o}#{n}#{e}#{y}"
    exit
  end
end

ほとんど待たずに(カーソルの点滅は 2 回だった)、結果が出た。1 文字減るだけで、こんなに差が出るとは。

holysugarholysugar2008/12/08 16:52大筋はとてもよいんじゃないかと思います。冗長な部分をより簡潔に書けるようになるともっといいかも?

krystalkrystal2008/12/08 19:26Prinyさんすごい! > 4 桁の整数 + 4 桁の整数の最大値は 9,999 + 9,999 = 19,998 で、M = 1 なのはすぐにわかる

cuziccuzic2008/12/08 20:54一文字減ることも大きいですが、10^n か n!/(9-n)! かで大幅な高速化を実現できていますね。

rubikitchrubikitch2008/12/10 07:00ベンチマークを比較してみた。
http://d.hatena.ne.jp/rubikitch/20081210/1228858392

prinypriny2008/12/11 11:15コメントをありがとうございます。
書き方で処理時間がかなり変わるんですね。冗長さに気づく、なくすができるよう、精進します。

2008-12-05Rubyのプログラムの練習 1. 矢印

矢印を表示するプログラムを作る。大きさを指定できるようにする。

http://lecture.ecc.u-tokyo.ac.jp/~yamaguch/johokagaku/2007/ruby-primer.html
def arrow(head_length, body_length)
  head_length.times {|i|
    puts(("*" * (2 * i + 1)).center(50)) 
  }
  body_length.times { 
    puts "*".center(50) 
  }
end

arrow(8, 10)

実行結果

                        *
                       ***
                      *****
                     *******
                    *********
                   ***********
                  *************
                 ***************
                        *
                        *
                        *
                        *
                        *
                        *
                        *
                        *
                        *
                        *

いつも、puts は、puts "hoge" みたいに使っているので、3 行目が見慣れない。
でも、そう書かないと、Fixnumcenter は使えないよとか、引数との間に空白を入れたらダメだよとか言われる。

puts "hoge" みたいに書けるように違う方法で書いてみよう。

def arrow2(head_length, body_length)
  head_length.times {|i|
    puts "*".concat("**"*i).center(50)
  }
  body_length.times { 
    puts "*".center(50) 
  }
end

arrow2(8, 3)

実行結果

                        *
                       ***
                      *****
                     *******
                    *********
                   ***********
                  *************
                 ***************
                        *
                        *
                        *

通りすがりのプログラマさんに、「どっちのほうがいいんでしょう?」と聞いてみた。
「好みの問題かも。個人的には括弧を書いているほうがいい。一人でやっている分にはどっちでもいいと思うけど、プロジェクトでやるなら、ほかの人とあわせる。」とのことでした。

2008-12-03練習:プログラミング入門 - 9. クラス

OrangeTree クラスを作ってみなさい。このクラスには、木の高さを返すheightメソッドと、メソッドを呼ぶことで、1年分時間を進める oneYearPasses メソッドがあります。毎年、この木は成長し大きくなります(オレンジの木が 1 年に伸びる分だけ)。そして、ある年限が来たら(これもメソッド呼び出しによります) その木は死んでしまいます。最初の 2,3 年は実をつけませんが、その後は実がなる様にします。で、成長した木は若い木よりたくさん実をつけます。このあたりはあなたが納得するよう調節してみましょう。そして、もちろん countTheOranges (木になっているオレンジの数を返す)メソッドと、 pichAnOrange (オレンジをひとつ摘むメソッド。このメソッドで @orangeCount が、1だけ小さくなり、いかにおいしいオレンジだったかを告げる文字列かあるいは、もう木にオレンジがなっていないことを告げる文字列かを返します。)を実行できるようにしなければいけません。それと、ある年に取り残したオレンジは次の年には落ちてしまうようにしましょう。

http://www1.tf.chiba-u.jp/~shin/tutorial/index.rb?Chapter=09

いろんな実を収穫できる「ふしぎな木」をつくる。

# 「ふしぎな木」には 5 種類の実がなる
FRUIT_VARIATION = ["モモ", "リンゴ", "ナシ", "みかん", "さくらんぼ"]
# 「ふしぎな木」の寿命は 10 年
MAX_AGE = 10

class WonderfulTree  
  def initialize
    @tree_age = 0       # 樹齢。初期値は 0 (年)
    @tree_height = 0    # 木の高さ。初期値は 0 (cm)
    @fruit_list = []    # 木になっている実のリスト。初期値は空
  end
  
  attr_reader :fruit_list
  
  # 「ふしぎな木」の高さを伝える
  def speak_tree_height
    puts "「ふしぎな木」の高さは #{@tree_height} cm です。"
  end
  
  # 「ふしぎな木」になっている実の数を伝える
  def speak_fruit_num
    puts "「ふしぎな木」になっている実の数は #{@fruit_list.size} 個です。"
  end
  
  # 実を 1 つだけ収穫する。
  def harvest_one_fruit(index)
    harvested_fruit = @fruit_list.delete_at(index)
    # 収穫できたら、収穫した実の種類を伝える。
    if harvested_fruit == nil
      $stderr.puts "その番号の実は存在しません。"
    else
      puts "#{harvested_fruit}」を 1 個収穫しました。"
    end
  end
  
  # 「ふしぎな木」に実がなっているとき、実の種類を伝える
  def speak_fruit_list
    if @fruit_list.size >= 1
      @fruit_list.each_with_index do |fruit, index|
        puts "#{index}:#{fruit}"
      end
      puts "を収穫できます。"
    end
  end
  
  # 経過させる年数を引数にもらって、時間を進める
  def pass_time(year)
    # 寿命がきていなかったら、高さ、樹齢を増やし、実を実らせる。
    # 寿命がくると「ふしぎな木」は枯れてしまう
    if @tree_age + year  < MAX_AGE
      year.times do |i|
        @tree_height += 20
        @tree_age += 1
        bear_seed
      end
      puts "「ふしぎな木」は、樹齢 #{@tree_age} 年になりました。"
    else  
      puts "「ふしぎな木」は寿命がきて、枯れてしまいました。"
      exit  #プログラム終了
    end
  end
  
  private
  
  # 実をつける
  def bear_seed
    #木は 3 年たつと実をつける
    if @tree_age >= 3
      #ある年に取り残した実は次の年には落ちる(去年の実の数を今年の実の数で上書きする)
      @fruit_list = []

      #成長するほどたくさんの実をつける
      fruit_num = (@tree_age-2)*2
      fruit_num.times do |i|
        @fruit_list.push(FRUIT_VARIATION[rand(FRUIT_VARIATION.size)])
      end
    end
  end
end

guide = <<EOS
あなたは、以下の 5 種類のコマンドを使えます。

g1 : 1 年、時間を進めます。「ふしぎな木」が成長します。
g2 : 2 年、時間を進めます。「ふしぎな木」が成長します。
f : 収穫できる実が表示されます。収穫したい実の番号を入力すると、実を収穫できます。
h : 「ふしぎな木」の高さを調べます。
n : 「ふしぎな木」に実っている実の数を調べます。

「ふしぎな木」は成長すると実を実らせます。
寿命がくると枯れてしまいます。

さあ、コマンドを入力して、「ふしぎな木」を育てましょう。
EOS

puts guide

tree = WonderfulTree.new

while true
  puts "コマンドを入力してください。"
  command = gets.chomp
  case command.downcase
  when "g1"
    tree.pass_time(1)
  when "g2"
    tree.pass_time(2)
  when "f"
    if  tree.fruit_list.size >= 1
      tree.speak_fruit_list
      puts "収穫したい実の番号を入力してください。"
      fruit_num = gets.chomp
      if /\d/ =~ fruit_num
        tree.harvest_one_fruit(fruit_num.to_i)
      else
        $stderr.puts "入力が正しくありません。"
      end
    else
      puts "収穫できる実はありません。"
    end
  when "h"
    tree.speak_tree_height
  when "n"
    tree.speak_fruit_num
  else
    $stderr.puts "コマンドが正しくありません。"
  end
end

実行結果

あなたは、以下の 5 種類のコマンドを使えます。

g1 : 1 年、時間を進めます。「ふしぎな木」が成長します。
g2 : 2 年、時間を進めます。「ふしぎな木」が成長します。
f : 収穫できる実が侮ヲされます。収穫したい実の番号を入力すると、実を収穫できます
。
h : 「ふしぎな木」の高さを調べます。
n : 「ふしぎな木」に実っている実の数を調べます。

「ふしぎな木」は成長すると実を実らせます。
寿命がくると枯れてしまいます。

さあ、コマンドを入力して、「ふしぎな木」を育てましょう。
コマンドを入力してください。
g1
「ふしぎな木」は、樹齢 1 年になりました。
コマンドを入力してください。
g2
「ふしぎな木」は、樹齢 3 年になりました。
コマンドを入力してください。
f
0:ナシ
1:みかん
を収穫できます。
収穫したい実の番号を入力してください。
0
「ナシ」を 1 個収穫しました。
コマンドを入力してください。
f
0:みかん
を収穫できます。
収穫したい実の番号を入力してください。
3
その番号の実は存在しません。
コマンドを入力してください。
g2
「ふしぎな木」は、樹齢 5 年になりました。
コマンドを入力してください。
h
「ふしぎな木」の高さは 100 cm です。
コマンドを入力してください。
n
「ふしぎな木」になっている実の数は 6 個です。
コマンドを入力してください。
f
0:モモ
1:モモ
2:ナシ
3:リンゴ
4:モモ
5:リンゴ
を収穫できます。
収穫したい実の番号を入力してください。
2
「ナシ」を 1 個収穫しました。
コマンドを入力してください。
f
0:モモ
1:モモ
2:リンゴ
3:モモ
4:リンゴ
を収穫できます。
収穫したい実の番号を入力してください。
2
「リンゴ」を 1 個収穫しました。
コマンドを入力してください。
g2
「ふしぎな木」は、樹齢 7 年になりました。
コマンドを入力してください。
g2
「ふしぎな木」は、樹齢 9 年になりました。
コマンドを入力してください。
g2
「ふしぎな木」は寿命がきて、枯れてしまいました。

気になること

入力が正しくない系のエラー出力に $stderr を使ってみた。こういうエラーは $stderr を使うので正しい?

実を収穫するについて、クラスの中と外に分けて書いている。もっとよい方法がある?

コマンドのところで、「while true」と書いたけど、お作法的にあり?こういう場合は、どんな条件にするのが適切?

2008-12-02練習:たのしいRuby P.230 (3) StringIO クラスを定義する

http://rubyist.g.hatena.ne.jp/krystal/20081201/1228121621

一緒の練習問題を解いていったら、勉強になりそうに思った。
ので、『たのしいRuby―Rubyではじめる気軽なプログラミング』の同じ問題をやってみる。

もともとある StringIO クラスとは別物として作成したいので、別のクラス名にしよう。
文字列を取り出すのがメインっぽいので、StringPicker で。

$KCODE = 'SJIS'

class StringPicker
  def initialize(string)
    @string = string
    rewind
  end
  
  def rewind
    @pos = 0
  end
  
  def gets
    line_end = @string.index("\n", @pos)
    if line_end
      read(line_end-@pos+1)
    else
      read(@string.length-@pos+1)
    end
  end

  def read(letter_num)
     result = @string[@pos, letter_num]
     @pos += letter_num
     return result
  end
end


sio = StringPicker.new("密林\n\n赤と黒\n\n")
p sio.gets  
p sio.gets  
p sio.gets  
p sio.gets
p sio.gets
p sio.gets
p sio.gets
sio.rewind  
p sio.gets
p sio.read(3)
p sio.read(5)

実行結果

"密林\n"
"嘘\n"
"赤と黒\n"
"罠\n"
"罪"
nil
nil
"密林\n"
"嘘\n"
"赤と\215"

例示されている使い方はできているみたい。

気になること

rewind (巻き戻す) という名前のメソッドを initialize の中で呼ぶのは、お作法としてあり?

引数を省略できるようにする方法がわからなくて、StringPicker#read は引数を省略できない。(IO#read は引数を省略できる。)

あと、日本語の文字単位になっていないから、read の引数によっては文字の途中で切れる。むむむ。。。

知ったこと

  • String#to_a
    • 行ごとの文字列を要素に持つ配列にしてくれる
  • String#index(pattern, pos)
    • pos の位置から pattern にマッチする文字列を右方向に探索し、見つかった部分文字列の左端の位置を返す
    • 見つからなければ nil を返す
  • Array#shift
    • 配列の先頭の要素を取り除く
    • 取り除いたオブジェクトを返す
  • IO#gets
    • 1 行読み込んで、読み込みに成功したときはその文字列を返す
    • 1 行返したら終わる
    • ファイルの終わりまできたら nil を返す
  • IO#rewind
    • ファイルポインタを先頭に移動する
    • 0(ポインタの番号?)を返す
  • IO#read(length)
    • length が指定された場合、length バイト読み込んで、それを返す
    • length が省略された場合は、すべてのデータを読み込んで、それを返す

prinypriny2008/12/04 13:17仮引数のデフォルト値を指定すると、その引数を省略できることを知りました。
StringPicker#read の引数を letter_num から letter_num = @string.length に変更したら、StringPicker#read の引数を省略できるようになりました。

prinypriny2008/12/05 01:46StringIO#read は引数なしで呼んだ場合、文字列を全部読んだあとは、空文字列を返す。
StringPicker#read のデフォルト引数を @string.length - @pos にしたら同じ動作になった。