レコード
class Point attr_accessor("x", "y") end … point.rb インスタンス変数の宣言irb(main):004:0> load ("point .rb") => true irb(main):005:0> p = Point.new() => #<Point:0x40332080> irb(main):006:0> p.x = 3 => 3 irb(main):007:0> p.y = 4 => 7 irb(main):008:0> p => #<Point:0 x40332080 @y=4, @x=3> オブジェクト (Pointクラスの インスタンス) インスタンス変数への代入 インスタンス変数への代入
irb(main):013:0> p.y => 4 irb(main):014:0> sqrt(p.x**2 + p.y**2) => 5.0 (もちろん include(Math) が必要…)
• 実はRubyでは,レコードもオブジェクトの一種
(オブジェクトについては後で説明する)
インスタンス変数の参照
… def point_make(u,v) p = Point.new() p.x = u p.y = v p end … レコード/オブジェクト(への参照)を返す point.rb
… def point_scale(p,s) point_make(p.x*s, p.y*s) end def point_add(p,q) point_make(p.x+q.x, p.y+q.y) end … point.rb 点の座標値のスカラ倍 点の座標値の加算
…
def point_interpolate(p,q,t)
point_add(point_scale(p,1-t), point_scale(q,t)) end
def point_draw(p,a)
if 0 <= p.y+0.5 && p.y+0.5 < a.length() && 0 <= p.x+0.5 && p.x+0.5 < a[0].length() a[p.y+0.5][p.x+0.5]=1 end end point.rb 配列aの縦の長さ 配列aの横の長さ Rubyの配列の添え字が実数でも いいことを利用している 0.5は四捨五入をするため
load("./max.rb") load("./abs.rb")
def line_draw(p0,p1,a)
n=max(abs(p1.x - p0.x), abs(p1.y - p0.y)) for i in 0..n point_draw(point_interpolate(p0,p1,i*1.0/n), a) end end line.rb 2点間の差分ベクトルの x,yの大きい数
def bezier_draw(p0,c,p1,a) n = 10 prev = p0 for i in 1..n t = i*1.0/n q0 = point_interpolate(p0, c, t) q1 = point_interpolate(c, p1, t) r = point_interpolate(q0, q1, t) line_draw(prev, r, a) prev = r end end bezier.rb 曲線をn等分する prev から r まで線分を描く
練習
• 授業のWebページから point.rb,line.rb,
bezier.rb, kana.rb をダウンロードして、練習
8.1 a) の図形 kana() を描画してみなさい。
(abs.rb, max.rb, make1d.rb, make2d.rb などは、 すでに作成したものである)
進捗状況の確認
1. kana()が表示できた(できた時点で投票して
ください)
できた人は、教科書168ページの練習 8.2 に
挑戦してみてください。
Line や Bezier もレコードにできる
class Line attr_accessor("p0", "p1") end class Bezier attr_accessor("p0", "c", "p1") endload("./max.rb") load("./abs.rb")
def line_draw(l, a)
n=max(abs(l.p1.x – l.p0.x), abs(l.p1.y – l.p0.y)) for i in 0..n point_draw(point_interpolate(l.p0,l.p1,i*1.0/n), a) end end line.rb 2点間の差分ベクトルの x,yの大きい数
レコードの意義
• 一まとまりの複数のデータを表す変数が1個で
すむ
– Pointの場合,x座標とy座標をまとめて扱える – Lineの場合,始点と終点をまとめて扱える • 4つの座標をまとめて扱えている – Bezierの場合,始点と終点,第3点をまとめて扱える • 6つの座標をまとめて扱えている• このようなデータのまとまりを値として扱える
– 関数の値として返すことができる • 4つの座標を一度に返すことは通常の関数では難しいしかし,レコードのままだと
…
• クラス(レコードの種類)ごとに操作を定義
Pointクラス Lineクラス Bezierクラス point_draw line_draw bezier_draw
• これらが混在している場合,
if figureがPointクラスのオブジェクト point_draw(figure,a) else if figureがLineクラスのオブジェクト line_draw(figure,a) else if figureがBezierクラスのオブジェクト bezier_draw(figure,a)オブジェクト指向では
• オブジェクト(ここではレコードのこと)に自分に
対する操作の仕方を覚えさせる
– Pointのレコードに,スカラー倍,ベクトル和などの 操作を覚えさせる – クラス定義の中で操作法(メソッド)を定義する• 「カプセル化(encapsulation)」
• 「継承(inheritance)」
• 「多相性(polymorphism)」
class Point attr_accessor("x", "y") def initialize(u,v) self.x = u self.y = v end def scale(s) Point.new(self.x * s, self.y * s) end def add(q)
Point.new(self.x + q.x, self.y + q.y) end end
クラス
scaleメソッド 初期化メソッド 自分自身の x @x でもよい oo-point.rb 自分の操作法を クラス定義の一部とし メソッドと呼ぶ ↓ 「カプセル化」irb(main):005:0> p = Point .new(3,4) => #<Point:0x7ffa3ed8 @x=3, @y=4> irb(main):006:0> p.x => 3 irb(main):007:0> q = p.scale(2) => #<Point:0x7ffa3e84 @x=6, @y=8> irb(main):008:0> p.add(q) => #<Point:0x7ff9b0f8 @x=9, @y=12> irb(main):009:0> p.add(q).scale(0.5) => #<Point:0x7ff94ca8 @x=4.5, @y=6.0> 初期化メソッドの引数 新しいオブジェクト 新しいオブジェクト 新しい オブジェクト
load("./max.rb") load("./abs.rb")
load("./oo-point.rb”)
def line_draw(p0, p1, a)
n=max(abs(p1.x – p0.x), abs(p1.y – p0.y)) for i in 0..n p0.interpolate(p1,i*1.0/n).draw(a) end end line_draw.rb 2点間の差分ベクトルの x,yの大きい数
isrb(main):001:0> load("line_draw.rb") true isrb(main):002:0> load("make2d.rb") true isrb(main):003:0> a=make2d(200,200) : (省略) isrb(main):004:0> show(a) : (省略) isrb(main):005:0> line_draw(Point.new(50,50),Point.new(100,170),a) 0..120 isrb(main):006:0> line_draw(Point.new(40,150),Point.new(180,70),a) 0..140
練習
• 授業のページから oo-point.rb をダウンロー
ドして、練習
8.5 b), c) を追加したら、
line_draw.rb で直線を描画してみなさい。
進捗状況の確認
1. 直線が描画できた時点で投票してください。
その後、練習 8.5 a), d) も定義してください。
進捗状況の確認
1. 直線が描画できた時点で投票してください。
2. drawメソッドはできた。
3. interpolateメソッドはできた。
4. どちらもできていない。
load("oo-point.rb") load("line_draw.rb") class Figure def draw(a) pnts = self.points() for i in 0..(pnts.length()-2) line_draw(pnts[i], pnts[i+1], a) end end end oo-figure.rb drawメソッドの定義 Figureの種類によらず pointsメソッドで 点列が得られる ↓ 「カプセル化」 「多相性」
(抽象的な) クラス Figure
load("oo-figure.rb") class Line < Figure
attr_accessor("p0", "p1") def initialize(q,r) self.p0 = q self.p1 = r end def points() [self.p0, self.p1] end end oo-line.rb Figureオブジェクトの継承 drawメソッドを使える ↓ 「継承」 pointsメソッドは 両端点を返す ↓ 「カプセル化」
load("oo-line.rb") class Bezier < Line
attr_accessor("p0", "c", "p1") def initialize(q,r,s) super(q,s) self.c = r end def points() n = 16 pnts = Array.new(n+1) pnts[0] = self.p0 pnts[n] = self.p1 for i in 1..(n-1) t = i*1.0/n q0 = self.p0.interpolate(c, t) q1 = self.c. interpolate(p1, t) pnts[i] = q0.interpolate(q1, t) end pnts end end oo-bezier.rb Lineオブジェクトの継承 drawメソッド (iniAalizeメソッド) pointsメソッドは t=0〜1の点列 (16+1個) ↓ 「カプセル化」 最初と最後の点は self.p0 と self.p1 線形補間を2段階して 曲線上の点を求める 点列を返す LineクラスのiniAalizeメソッドを呼び出す
練習
• 授業のページから oo-figure.rb, oo-line.rb,
oo-bezier.rb, drawall.rb をダウンロードして、
drawline() と drawground() を実行してみな
さい。
進捗状況の確認
1. drawground() ができたら投票してください。
その後は、oo-circle.rbを完成させて、
drawmoon() を実行してください。ただし、練
習 8.5 d) の rotate メソッドが必要です。
回転
(rotate)と円の描画
• 点の回転 (θ)
• 円の描画(正多角形)
x 軸上の点 original を θ 回転してから平行移動 ! x ! y " # $ % &' ="#$ cossinθθ −sincosθθ %&'" xy # $ % & ' x y self.c original pnts[0]=pnts[1] θ pnts[1] self.r
load("oo-figure.rb") include(Math)
class Circle < Figure attr_accessor("c", ”r") def initialize(q,r) self.c = q self.r = r end def points() n = 32 pnts = Array.new(n+1) original = Point.new(self.r, 0) pnts[0] = original.add(self.c) pnts[n] = pnts[0] for i in 1..(n-1) pnts[i] = # 自分で書く end pnts end end oo-circle.rb Figureオブジェクトの継承 drawメソッドを使える pointsメソッドは t=0〜1の点列 (32+1個) ↓ 「カプセル化」 originalを平行移動する originalの回転と平行移動 で円周上の点列を作る 点列を返す 原点を中心とした円の 始点=終点(original)を作る
多相性の実現
def drawall(elements,a)
for i in 0..elements.length()-1
elements[i].draw(a)
end
end
…
elements[i] のクラスが何であれ, 共通したdrawメソッドが使える ↓ 「多相性」 drawall.rb… def drawmoon() p0=Point.new(0,85) p2=Point.new(99,85) f=[Line.new(p0,p2), Bezier.new(p0,Point.new(50,60),p2), Circle.new(Point.new(66,20),20)] a=make2d(100,100) drawall(f,a) show(a) end drawall.rb
def drawall(elements,a)
for i in 0..elements.length()-1
figure_draw(elements[i], a)
end
end
def figure_draw(f,a)
pnts = f.points()
for i in 0..(pnts.length()-2)
line_draw(pnts[i], pnts[i+1], a)
end
end
f のクラスが何であれ, 共通したpointsメソッドが使える ↓ 「多相性」多相性
drawall_fig.rb 必ずしもdrawメソッド でなくても構わない たとえば関数でも良いが…レコードのままでは
…
• クラス(レコードの種類)ごとに操作を定義
Pointクラス Lineクラス Bezierクラス point_draw line_draw bezier_draw
• これらが混在している場合,
if figureがPointクラスのオブジェクト point_draw(figure,a) else if figureがLineクラスのオブジェクト line_draw(figure,a) else if figureがBezierクラスのオブジェクト bezier_draw(figure,a)カプセル化,継承,多相性
• カプセル化 – 「もの」を表すデータとメソッドをまとめ,決められたインス タンスメソッドを通してのみインスタンス変数にアクセスを させ,インスタンス変数に直接触らせないこと • 継承 – 他のクラスのインスタンス変数やメソッドを受け継いで新 しいクラスを作ること新しいクラスをサブクラス,元のクラ スをスーパークラスと呼ぶ • 多相性 – 同じ名前のインスタンスメソッドを呼び出しても,適用され たオブジェクトによって異なるメソッドを呼び出すこと実行 時にメソッドを探す動的結合で実現するベクトルの計算など
…
• ベクトルの演算 – 「カプセル化」
加減算、内積、正規化、直交ベクトルなど Pointクラス内のメソッド定義 … def dotProduct(v) self.x*v.x + self.y*v.y end def normalize() self.scale(1.0 / sqrt(self.dotProduct(self))) end def normal() Point.new(self.y, -self.x).normalize() end oo-point.rb ベクトルの操作法を クラス内で メソッドとして定義する ↓ 「カプセル化」直線
(線分)の交点計算
• 交差の条件
線分の両端点が他方の直線の両側(左右)に分かれる = 符号付距離が異符号になる• 交点の位置
線分の両端点を他方の直線からの距離で内分(線形補間)def line_intersect(p0,p1,q0,q1) pNormal = p1.sub(p0).normal() qStart = pNormal.dotProduct(q0.sub(p0)) qEnd = pNormal.dotProduct(q1.sub(p0)) qNormal = pStart = pEnd =
if (qStart * qEnd > 0) || (pStart * pEnd > 0) nil
else
p0.interpolate(p1, ((pStart * 1.0) / (pStart - pEnd))) end
end
練習
• line_intersect.rb の line_intersect メソッドを
定義しなさい。
• 授業のページから drawintersect.rb をダウン
ロードして,
drawintersect() を実行しなさい。
(
Circle クラスが必要)
進捗状況の確認
1. drawintersect()が表示できた(できた時点で
投票してください)
: def intersect(other) spnts = self.points() opnts = other.points() results = Array.new() for i in 0..(spnts.length()-2) for j in 0..(opnts.length()-2) point = line_intersect(spnts[i],spnts[i+1],opnts[j],opnts[j+1]) if point != nil results[results.length()] = point end end end results end : oo-figure.rb Figureオブジェクト間の intersectメソッドの定義 線分列同士の交点 pointsメソッドで 点列が得られる 大きさ0の配列 (でも中に入る!) 交点があれば 配列の最後に加える (配列は自動的に 1つ大きくなる) 交点列(配列)を返す 隣接2点からなる線分 の間で交点を計算する
def drawintersect() a = make2d(400, 400) f = [Line.new(Point.new(50, 50), Point.new(300, 350)), Circle.new(Point.new(199, 199), 70), Bezier.new(Point.new(50, 200), Point.new(150, 50), Point.new(350, 300)), Bezier.new(Point.new(250, 50), Point.new(100, 150), Point.new(200, 350))] for i in 0..(f.length()-1) f[i].draw(a) for j in (i+1)..(f.length()-1) results = f[i].intersect(f[j]) for k in 0..(results.length()-1) Circle.new(results[k], 4).draw(a) end end end show(a) end drawintersect.rb i番目のFigureを描く i番目とj番目のFigureの 交点を配列で受け取る 各交点の位置に 半径4の小円を描く