2007/12/12 ema
Agenda オブジェクト指向 ブロック(イテレータ) クラス irb 文法 変数 三目並べ : Tick-tac-toe
オブジェクト 指向って?
!!! WARNING !!!
!!! WARNING !!! プログラミングの経験を前提にしていますが 頭をまっさらにして聞いてください 「C++ / Java ではこうだよね?」 というのは邪魔になりかねません 後、一般的な入門書とは毛色を変えています 詳細は入門書を読み直してください
3つのオブジェクト指向 「メッセージング」 by アラン・ケイ 影響を受けている言語 : Smalltalk、Ruby など 影響を受けている言語 : C++ など 「手続きによる抽象化手法」 by ウィリアム・クック オブジェクト指向の概念の発明者は誰ですか? http://d.hatena.ne.jp/sumim/20040525/p1
登 場 人 物
すべてがオブジェクト 「メッセージング」の考え方 インスタンス メッセージ リテラル すべてがオブジェクト オブジェクト同士はメッセージをやりとりする インスタンス メッセージ リテラル
インスタンスありきと神は語りき 計算してください
インスタンス / オブジェクト 計算してください コンピュータ上にある何か
メッセージ 計算してください オブジェクト同士のやりとり
実 例
1 . to_s : 数字の「1」を文字列に ソースコード オブジェクト空間 1 . to_s Rubyインタプリタ
リテラルという魔法 1 「1」の インスタンスを生成 1 . to_s
メッセージング(メソッド呼び出し) 1 to_s して下さい 1 . to_s
結果が返ってくる "1" を生成 1 "1" "1" だよ 1 . to_s
評価終了 : 1.to_s => "1" 1 "1" "1" 評価の結果 "1" でした
式と評価 式 オブジェクト 1 . to_s “1” 評価 先ほどの 一連の流れのこと Ruby のプログラムは 式 と 評価 の繰り返し Evaluate : 値にする,推し測る,実行する 式 オブジェクト 1 . to_s “1” 評価 先ほどの 一連の流れのこと
メソッド オブジェクトに対する操作 「Ruby 用語集」より 先ほどの例は「to_s」メソッドを呼び出していた 厳密には関数とは違うけど、似たようなもの
オブジェクト と メソッド呼び出し 1 to_s して下さい 1 . to_s
ブ ロ ッ ク (イテレータ)
= 例: 10 回繰り返し 10.times { |i| puts i } ブロック 10.times do |i| puts i end ブロック内が実行される
例: each - ループする 1 each do |i| puts i end 2 3
配列 から 配列 を作る A 変換 B
map : 関数型言語のノリ [ 1, 2, 3 ].map do |i| i * 2 end 僕が map の意義を理解したのは割と最近 だから、無茶をしようとしているのかも? でも、map そのものは自然なはず [ 1, 2, 3 ].map do |i| i * 2 end
配列 : map / collect - 変換 1 2 map do |i| i * 2 end 2 4 3 6 各要素を二倍する
配列 : map / collect - 変換 [1, 2, 3] を元に新しい配列を作る 要素を二倍 [ 1, 2, 3 ].map { |i| i * 2 } [ 1, 2, 3 ] ×2 [ 2, 4, 6 ]
[1, 2, 3] . map { |i| I * 2 } ブロック (イテレータ) [1,2,3].map { |i| i*2 }
[1, 2, 3] の生成 - リテラルらるる [1, 2, 3] の インスタンスを生成 [1,2,3].map { |i| i*2 }
メッセージング 結果格納用の 配列を生成 { |i| i*2 } を使って map して下さい 1 2 { |i| i*2 } を使って map して下さい 3 [1,2,3].map { |i| i*2 }
ブロックの評価 : 「1」その 1 「i = 1」で 「i * 2」を 評価して下さい [1,2,3].map { |i| i*2 } 1
ブロックの評価 : 「1」その 2 2 1 2 結果を格納 3 2 でした [1,2,3].map { |i| i*2 }
ブロックの評価 : 「2」その 1 「i = 2」で 「i * 2」を 評価して下さい [1,2,3].map { |i| i*2 } 2
ブロックの評価 : 「2」その 2 2 4 1 2 結果を格納 3 4 でした [1,2,3].map { |i| i*2 }
ブロックの評価 : 「3」その 1 「i = 3」で 「i * 2」を 評価して下さい [1,2,3].map { |i| i*2 } 2 4 1 2 「i = 3」で 「i * 2」を 評価して下さい 3 [1,2,3].map { |i| i*2 }
ブロックの評価 : 「3」その 2 2 4 1 6 2 結果を格納 3 6 でした [1,2,3].map { |i| i*2 }
ブロックの評価 : 「3」その 2 2 4 1 6 2 3 [2, 4, 6] だよ [1,2,3].map { |i| i*2 }
評価終了 2 4 1 6 2 3 評価の結果 [2, 4, 6] でした [2, 4, 6]
ガ ベ ー ジ コレクション
ガベージコレクション (GC) "1" 1 役目を終えたね
ク ラ ス
1.to_s ふたたび 1 to_s 1 . to_s "1" だよ
実際には、Fixnum クラスが処理 to_s 1 Fixnum to_s 1 . to_s "1" だよ
実際には、Fixnum クラスが処理 to_s 1 2 Fixnum to_s 2 . to_s "2" だよ
インスタンス毎は非効率 インスタンス毎にメソッドを用意するのは非効率 共通の処理/特徴をまとめたのがクラス String(文字列), Array(配列)など
継承 : クラスの属性を受け継ぐ sub class super class 基本的にはメッセージを super class に丸投げ Class A Class B super class 基本的にはメッセージを super class に丸投げ
オーバーライド / 再定義 Class A Class B 特定メッセージのみ 反応を変える
i r b
Interactive RuBy irb コマンド Ruby の式を簡単に入力/実行するためのツール Ubuntu などだと標準で入ってないので sudo aptitude install irb などとしてインストール
Interactive RuBy 文 評価結果 irb(main):001:0> a = "1" => "1" irb(main):002:0> b = a irb(main):003:0> a += "b" => "1b" irb(main):004:0> b 文 評価結果
休 憩 質問無い?
Agenda オブジェクト指向 ブロック(イテレータ) クラス irb 文法 変数 三目並べ : Tick-tac-toe
文 法
特徴 すべてがオブジェクト ほぼすべての文が値を返す 改行が文の切り替わり。; は書かない慣習 メソッド呼び出しの ( ) は必須ではない { } と do end はほぼ等価
if - もし○○なら if month == 1 "睦月" elsif month == 2 "如月" # ... 中略 ... else # エラー end
偽になるのは false と nil だけ if 0 puts "hoge" hoge が表示される end 残りは全て 真 になる(0 も!) if 0 puts "hoge" end hoge が表示される
nil って? C でいうところの null 未初期化値
case - when case month when 1 then "睦月" when 2 then "如月" # ... 中略 ... when 12 then "師走" else # エラー end
unless - もし、○○じゃなかったら unless line.empty? # 空でなければ end
while / until - 繰り返し while line = gets puts line end
break / next while line = gets # # で始まる行はコメント next if line[0] =~ /^#/ # __END__ だけの行がきたら終わる break if line == '__END__' puts line end
修飾子 return if str.empty? retry unless retry_count >= 20 この2つはよく使う。後ろから条件を指定 ぶっちゃけ好みの問題
メソッド定義 def japanese_month_name( month ) # ... 中略 ... end メソッド名 引数
制御構造も値を返す def japanese_month_name( month ) case month when 1 then "睦月" # ... 中略 ... when 12 then "師走" else # エラー end
メソッド呼び出し a = japanese_month_name( 1 ) b = japanese_month_name 2 ( ) は、あっても無くても良い 付けるかどうかは気分次第 読みやすいと感じる方で
Array
配列って? オブジェクトを複数格納できるもの 何でもいれれる、混在できる a[0] a[1] a[2] a[n-1] 1 "foo" /bar/ n-2 n-1 n … a[-n] a[-3] a[-2] a[-1]
サイズ n の配列への添字アクセス a.length / a.size a[0] a[1] a[2] a[n-1] 1 2 3 n-2 … a[-n] a[-3] a[-2] a[-1] a.first a.last a.length / a.size
配列式 [ 1, 2, 3 ] # 1, 2, 3 の配列を作る [ [ 1, 2 ], [ 3, 4 ] ] # ネストも可能 [ 1, 2, 3 ] # 1, 2, 3 の配列を作る [ [ 1, 2 ], [ 3, 4 ] ] # ネストも可能 %w(one two three) # ["one","two","three"]
Hash
ハッシュって? オブジェクトを複数格納できるもの 配列の添え字は数字だけ。 ハッシュの添字はオブジェクトなら何でも "foo" h["name"] 20 h["age"] "male" h["sex"]
ハッシュ式 { "name" => "foo", "age" => 20, "sex" => "male" }
配列とハッシュの組み合わせ わざわざクラスや構造体を用意せずに 配列とハッシュの組み合わせだけで データ構造を実現することが多い
String
文字列 "hoge" 'hoge' #{式} で文字列中に値を埋め込める ex. "hoge #{1}" # => "hoge1" 文字列中に値を埋め込むことは出来ない
%記法による文字列 %q|"hoge"| # => "hoge" %Q["hoge#{i}"] # => "hoge1" " や ' などを文字列に入れたいときに重宝 %q|"hoge"| # => "hoge" %Q["hoge#{i}"] # => "hoge1"
リテラル
その他のリテラル - 魔法の呪文 Symbol : :hoge Fixnum : 1 や -1 や 0xff や ?a Float : 1.0 や 1.2e-3 Bignum : 100_000_000_000_000_000 Range : 1..10 や 1...10 Regexp : /hoge/
リテラルの評価 → オブジェクト 配列式 オブジェクト [1, 2, 3] [1, 2, 3] 評価 文字列リテラル オブジェクト %Q|no: #{i}| "no 1" 評価
変 数
変数の種類と、名前の規則 ローカル変数 : 小文字 or _ で始まる 定数 : 大文字 で始まる 定数 : 大文字 で始まる 擬似変数 : self, nil, true, false, ... インスタンス変数 : @ で始まる クラス変数 : @@ で始まる グローバル変数 : $ で始まる
変数のイメージ : 矢印 [1, 2] a a = [1, 2] b = a a << 3
変数のイメージ [1, 2] a b a = [1, 2] b = a a << 3
変数のイメージ [1, 2, 3] a b a = [1, 2] b = a a << 3
数値や文字列はコピーされる 1 a a = 1 b = a a += 1
数値や文字列はコピーされる 1 a 1 b a = 1 b = a a += 1
数値や文字列はコピーされる 2 a 1 b a = 1 b = a a += 1
三目並べ作り Tick-Tac-Toe
実際のコードを読む 実際のコードを読む 出てくる文法的要素を随時解説 三目並べ(Tick-tac-toe)を作ってみた
設計 : 入力と出力を決める 入出力は、コマンドライン すなわち、標準入出力を使う
実行時のイメージ o のターンです x> 1 y> 3 y x:1 2 3 +-+-+-+ 1 |o|o|x| 2 |x|x| | 3 |o| | |
登場人物を洗い出す - クラス候補 ○マス ×マス プレーヤ プレーヤ 空白マス ゲームルール 盤面
今回は盤面以外全部クラスにします Maru Batu Player Player Free TickTacToe
以下,部分部分をみていきます ソースコードは全部で 160 行ほど 以下,部分部分にわけて読んでいきます
クラスの書き方 class クラス名(大文字で始める) # メソッド定義 def hoge end クラス名は大文字で始める
duck typing – アヒル風ならアヒル Batu maru? Free Maru メッセージに 答えてくれれば 何でも良い If it walks like a duck and quacks like a duck, it must be a duck. (アヒルのように歩き, アヒルのように鳴くものはアヒルに違いない) まつもと直伝 プログラミングのオキテ 第4回(3) http://itpro.nikkeibp.co.jp/article/COLUMN/20050926/221633/
Maru, Batu, Free - マス目クラス 末尾に「?」が ついてるのは true / false を返す慣習 class Maru def maru?; true ; end def batu?; false; end def free?; false; end def to_s ; "o" ; end end class Batu def maru?; false; end def batu?; true ; end def free?; false; end def to_s ; "x" ; end end class Free def maru?; false; end def batu?; false; end def free?; true ; end def to_s ; " " ; end end 末尾に「?」が ついてるのは true / false を返す慣習
フロー
main def main game = TickTacToe.new # 初期化 loop do puts game.to_s # 盤面表示 game.main # 置いて、プレーヤ交代 break if game.end? # 終了? end game.display_result # 結果表示 # ... 中略 ... main # main 呼び出し
TickTacToe#main def main x, y = current_player.input # 入力 put( x, y ) # 置く change_player # プレーヤ交代 end
TickTacToe#initialize - 初期化 class TickTacToe def initialize @players = [ Player.new('x'), Player.new('o') ] @current_player_index = 0 @field = Array.new( 3 ) do Array.new( 3 ) { Free.new } end @result = NOT_END
TickTacToe#initialize オブジェクトが初期化されるときに呼ばれる 例えば「 TickTacToe.new 」を呼ぶと, 「 TickTacToe オブジェクト 」 が生成されて, 「 TickTacToe#initialize 」 が実行される TickTacToe.new TickTacToe#initialize
インスタンス変数 TickTacToe TickTacToe @ から始まる変数 インスタンスごとに変数を持つ players field result TickTacToe field players result
TickTacToe#initialize - 初期化 class TickTacToe def initialize @players = [ Player.new('x'), Player.new('o') ] @current_player_index = 0 @field = Array.new( 3 ) do Array.new( 3 ) { Free.new } end @result = NOT_END
@field の中身 3 x 3 の配列 配列の中に配列 Free Free Free @field[0] Free Free Free
なぜ次のコードでは駄目か? Free.new は 1 回しか評価されず 同じオブジェクトを 参照してしまう Free @field = Array.new( 3 ) do Array.new( 3, Free.new ) end Free.new は 1 回しか評価されず 同じオブジェクトを 参照してしまう Free
TickTacToe - フィールド管理 return なくても 値が返る 名前は _ で区切る慣習 def cell( x, y ) @field[y][x] end def set_cell( x, y, obj ) @field[y][x] = obj return なくても 値が返る 名前は _ で区切る慣習
TickTacToe#put def put( x, y ) if @current_player_index == 0 set_cell( x, y, Batu.new ) else set_cell( x, y, Maru.new ) end
TickTacToe - プレーヤ管理 def current_player @players[ @current_player_index ] end def change_player if @current_player_index == 0 @current_player_index = 1 else @current_player_index = 0
TickTacToe#end? その1 - 終了? BATU_WIN = 0; MARU_WIN = 1 DRAW = 2; NOT_END = 3 def end? # 三目並ぶ可能性のある座標組合せの一覧を作る candidates = [ ] candidates << [ [0,0], [1,1], [2,2] ] # 斜め candidates << [ [2,0], [1,1], [0,2] ] # 斜め 3.times do |i| candidates << [ [i,0], [i,1], [i,2] ] # 縦 candidates << [ [0,i], [1,i], [2,i] ] # 横 end
配列 : push - 末尾に追加 a.push n a << n push 配列の大きさは 勝手に拡張される 1 2 3 … n 配列の大きさは 勝手に拡張される
TickTacToe#end? その2 - 終了? candidates.each do |candidate| if candidate.all? { |x,y| cell(x,y).batu? } @result = BATU_WIN; return true end if candidate.all? { |x,y| cell(x,y).maru? } @result = MARU_WIN; return true # 決着がついて無くて,全部埋まれば引き分け @result = DRAW if @result==NOT_END && filled? return @result != NOT_END
TickTacToe#end? その3 - filled? candidates.each do |candidate| if candidate.all? { |x,y| cell(x,y).batu? } @result = BATU_WIN; return true end if candidate.all? { |x,y| cell(x,y).maru? } @result = MARU_WIN; return true # 決着がついて無くて,全部埋まれば引き分け @result = DRAW if @result==NOT_END && filled? return @result != NOT_END
TickTacToe#filled? - 埋まった? def filled? @field.all? do |row| row.all? do |cell| ! cell.free? end class Maru def free?; false; end end class Batu def free?; false; end end class Free def free?; true ; end end
all? - 全部が true? Batu Maru Batu Batu Maru Maru Maru Batu Batu @field.all? do |row| row.all? do |cell| ! cell.free? end Batu Maru Batu Batu Maru Maru Maru Batu Batu
all? - 全部が true? 順番に評価される true Batu true true Maru Batu true Batu Maru @field.all? do |row| row.all? do |cell| ! cell.free? end Batu true true Maru Batu true Batu Maru Maru 順番に評価される Maru Batu Batu
all? - 全部が true? 1行ずつ true true true true true true Batu Maru Maru @field.all? do |row| row.all? do |cell| ! cell.free? end true true true true true Batu Maru Maru 1行ずつ Maru Batu Batu
all? - 全部が true? true true true true true true true true true true @field.all? do |row| row.all? do |cell| ! cell.free? end true true true true true true true true true Maru Batu Batu
all? - 全部が true? true @field.all? do |row| row.all? do |cell| ! cell.free? end
TickTacToe#end? その4 - all? candidates.each do |candidate| if candidate.all? { |x,y| cell(x,y).batu? } @result = BATU_WIN; break end if candidate.all? { |x,y| cell(x,y).maru? } @result = MARU_WIN; break # 決着がついて無くて,全部埋まれば引き分け @result = DRAW if @result==NOT_END && filled? return @result != NOT_END 1列全部×? [ [0, 0], [1, 1], [2, 2] ] 1 2 1 2
TickTacToe#end? その4 - all? candidates.each do |candidate| if candidate.all? { |x,y| cell(x,y).batu? } @result = BATU_WIN; return true end if candidate.all? { |x,y| cell(x,y).maru? } @result = MARU_WIN; return true # 決着がついて無くて,全部埋まれば引き分け @result = DRAW if @result==NOT_END && filled? return @result != NOT_END 1列全部×? [ [2, 0], [1, 1], [0, 2] ] 1 2 1 2
TickTacToe#display_result def display_result puts self.to_s # 終局図を表示 puts case @result when BATU_WIN then "x WIN!" when MARU_WIN then "o WIN!" when DRAW then "DRAW" end
TickTacToe – to_s : 文字列化 WAKU = " +-+-+-+\n" def to_s str = "y x:0 1 2 \n" str += WAKU @field.each_with_index do |row, i| str += "#{i} |" + row.join("|") + "|\n" end return str
配列 : join - 連結して文字列化 1 2 join("|") "1|2|3" 3
文字列への式の埋め込み "#{i}: #{i*2}" "%d: %d" % [ i, i*2 ] 変数の埋め込み #{} で囲む シングルクオート ' だと #{} の効果がない "#{i}: #{i*2}" "%d: %d" % [ i, i*2 ]
Player クラス アクセサ定義 一行読み込んで,数値化 class Player attr_accessor :name def initialize( name ) @name = name end def input puts current_player.name + " のターン" print "x> "; x = gets.to_i print "y> "; y = gets.to_i return x, y アクセサ定義 一行読み込んで,数値化
attr_XXXX - syntactic sugar attr_reader 読み取り専用アクセサ - attr_reader :hoge def hoge; @hoge; end attr_write 書き込み専用アクセサ - attr_writer :hoge def hoge=(val); @hoge = val; end attr_accessor 読み書きアクセサ attr_reader + attr_write
Q. CPU を実装するには??
Q. Player と CPU の違いは? 入力するのが,人 か コンピュータ かだけ name などは共通なので 継承して再定義する
A. CPU クラスを作る 継承 class CPU < Player def input # CPU の思考ルーチンを組み込む end 継承 class TickTacToe def initialize @players = [ Player.new('x'), CPU.new('o') ] # ... 後略 ... end
デバッグ
バグがある! 置かれている場所に置けてしまう 盤の内側かどうかをチェックしていない
例外処理 例外が throw されると メソッド呼び出しを逆にたどって rescue されたら逆戻りが終わる
TickTacToe#put def put( x, y ) unless cell( x, y ).free? throw "そこには置けません" end if @current_player_index == 0 set_cell( x, y, Batu.new ) else set_cell( x, y, Maru.new )
エラーの例 def main puts current_player.name + " のターン" def put( x, y ) rescue => e warn e.message retry end def put( x, y ) unless cell( x, y ).free? throw "そこには置けません" end
TickTacToe#main def main puts current_player.name + " のターン" x, y = current_player.input # 入力 put( x, y ) # 置く change_player # プレーヤ交代 rescue => e # 置けなかった warn e.message retry # やり直す end
Q. 範囲外を指定したら? 実際にやってみて下さい
より情報を 得るには?
量と質をこなすことが大事 まずは入門書 文法を押さえる CodeGolf / どう書く.org 練習問題として書いてみる 他人のコードを読んでみる リファレンスマニュアルとの格闘
プログラミング Ruby これが定番っぽい? あんまり良書を知らない 書いて覚えた
Rubyist Magazine 出張版 正しいRubyコードの書き方講座 Web でも読めます Rubyist Magazine でググる ただ、平易ではない
まとめ
まとめ オブジェクト と メッセージング 式 と 評価 ブロック
ご静聴 ありがとう ございました
Enumerable
配列をチェックする/要素を探す A チェック B
配列 : max / min - 最大最小 1 3 2 max 3
配列 : find / detect find do |s| s =~ /py/ end py を含む 文字列を探す “ruby” “python” “perl” “python” py を含む 文字列を探す
配列 : any? / all? - チェック all? do |s| s =~ /^p/ end 全部 p から 始まってるか? “ruby” all? do |s| s =~ /^p/ end false “perl” “python” 全部 p から 始まってるか?
配列 : sort - 並び替え 3 1 2 sort 2 1 3
配列 : sort_by - 条件を指定する sort_by do |i| - i end0 条件を指定して ソートする 1 3 2 2 3
配列 : select / find_all - 抜粋 1 1 select do |i| i % 2 == 1 end 2 3 3 奇数を全て取り出す
Array
配列 : データの追加 a.push n a << n a.insert 1, 3 insert push unshift 2 insert n-2 n-1 push … unshift 3 n a.unshift 1
配列 : unshift - 先頭に追加 1 2 3 n-2 n-1 n … unshift a.unshift 1
配列 : insert - 任意の場所に追加 a.insert 2, 3 1 2 n-2 n-1 n insert … 3
配列 : 範囲外に代入したら? a = [ 1, 2, 3 ] a[3] = 4 1 2 3
配列 : 範囲外に代入したら? a = [ 1, 2, 3 ] a[3] = 4 配列の大きさは 勝手に拡張 1 2 3 4
配列 : データの削除 pop a.pop 1 2 3 n-2 n-1 n … shift a.shift
配列 : pop - 末尾から削除 pop a.pop 1 2 3 n-2 n-1 n …
配列 : shift - 末尾から削除 1 2 3 n-2 n-1 n … shift a.shift
配列 : delete - ものを指定して削除 1 2 "foo" n-2 n-1 n … delete a.delete "foo"
ホワイの(感動的)Rubyガイド 僕にはソリが合いませんでした http://www.aoky.net/articles/why_poignant_guid e_to_ruby/