この章の描画について。
元資料は Excel の図形(オートシェイプ)を使う GSolidLine という
手続きを用意して線を描いています。
この教材の処理系は図形オブジェクトを扱えないので、
同じ名前・同じ引数の GSolidLine x1, y1, x2, y2 を用意し、
右側の描画面に線を引くようにしました。
プログラムの書き方は元資料と同じです。
本物の Excel で動かすには GSolidLine の中身
(ActiveSheet.Shapes.AddLine を使うもの)が別途必要です。
いちばん手軽な描画は、セルに色を塗ることです(第 1 章 §6)。 セルを小さな正方形=画素だと思えば、絵が描けます。
Sub Macro1()
Dim x As Integer, y As Integer
Dim cx As Integer, cy As Integer
Dim r As Double, d As Double
cx = 11: cy = 11 ' 中心
r = 9 ' 半径
For y = 1 To 21
For x = 1 To 21
d = Sqr((x - cx) ^ 2 + (y - cy) ^ 2)
If d <= r Then
' 中心からの距離で色を変える(濃淡をつける)
Cells(y, x).Interior.Color = RGB(255 - d * 20, 190 - d * 10, 60)
End If
Next x
Next y
End Sub
「円の中かどうか」を距離で判定しています。
第 3 章の 2 点間の距離の式がそのまま使えます。
If d <= r Then を If Abs(d - r) < 0.6 Then に変えると
輪郭だけになります。試してみてください。
なお Sqr を使わずに (x-cx)^2 + (y-cy)^2 <= r^2 と
書くほうが速いのは、第 8 章の素因数分解で d * d <= n と書いたのと同じ理由です。
GSolidLine(ByVal x As Double, ByVal y As Double, ByVal xx As Double, ByVal yy As Double)
点 (x, y) から点 (xx, yy) へ線分を引く(元資料 195・200 の 4 枚目)。
座標の向きに注意してください。 数学のグラフとは違い、y は下向きです(画面の座標系)。 元資料の図もそうなっています。
Sub Macro2()
' 壁(四角)
GSolidLine 140, 180, 340, 180
GSolidLine 340, 180, 340, 300
GSolidLine 340, 300, 140, 300
GSolidLine 140, 300, 140, 180
' 屋根(三角)
GSolidLine 140, 180, 240, 100
GSolidLine 240, 100, 340, 180
' ドア
GColor 240, 131, 0
GSolidLine 210, 300, 210, 240
GSolidLine 210, 240, 270, 240
GSolidLine 270, 240, 270, 300
' 窓
GColor 0, 176, 80
GSolidLine 165, 200, 205, 200
GSolidLine 205, 200, 205, 230
GSolidLine 205, 230, 165, 230
GSolidLine 165, 230, 165, 200
End Sub
この教材で使える描画の命令(元資料の GSolidLine に加えて用意したもの)
| 命令 | 働き |
|---|---|
GSolidLine x1, y1, x2, y2 | 線分を引く(元資料と同じ) |
GColor r, g, b | これから引く線の色を決める(0〜255) |
GWidth w | 線の太さ |
GCircle cx, cy, r | 円(内部では 48 本の線分) |
GClear | 描いたものを消す |
2 次元座標平面上の点の座標 (x1, y1) を α ラジアン回転した座標を (x2, y2) とすると、
回転の中心は原点 (0, 0) です。
だから「画面の真ん中で回したい」ときは、
いったん原点に寄せて → 回して → 元の位置へ戻すという 3 段が必要です。
プログラムでは GSolidLine 240 + x, 180 + y, … のように
描くときに中心の分を足すやり方が簡単です。
また Sin・Cos の引数はラジアンです
(度ではありません)。360 度 = 2π ラジアンなので、
度から直すには 度 * pi / 180 とします。
Sub Macro3()
Dim i As Integer
Dim n As Integer
Dim pi As Double, al As Double
Dim x1 As Double, y1 As Double
Dim x2 As Double, y2 As Double
pi = Application.WorksheetFunction.Pi
n = 12 ' 何角形にするか
x1 = 140: y1 = 0 ' 最初の点(原点から右へ 140)
al = 2 * pi / n ' 1 回に回す角度
For i = 1 To n
x2 = x1 * Cos(al) - y1 * Sin(al)
y2 = x1 * Sin(al) + y1 * Cos(al)
GSolidLine 240 + x1, 180 + y1, 240 + x2, 180 + y2
x1 = x2 ' 次はいまの点から
y1 = y2
Next i
End Sub
n を大きくすると円に近づきます(36 で見分けがつかなくなります)。
これは GCircle が内部でやっていることと同じです ──
曲線は、細かい線分の集まりで描くのがコンピュータグラフィックスの基本です。
Sub Macro4()
Dim i As Integer, k As Integer
Dim pi As Double, al As Double
Dim x(4) As Double, y(4) As Double
Dim nx As Double, ny As Double
Dim x0(4) As Double, y0(4) As Double
pi = Application.WorksheetFunction.Pi
' もとの図形(長方形)を原点まわりに置く
x0(1) = -120: y0(1) = -40
x0(2) = 120: y0(2) = -40
x0(3) = 120: y0(3) = 40
x0(4) = -120: y0(4) = 40
' 15 度ずつ、6 回描く
For k = 0 To 5
al = k * 15 * pi / 180
GColor 30 + k * 40, 63, 200 - k * 30
For i = 1 To 4
x(i) = x0(i) * Cos(al) - y0(i) * Sin(al)
y(i) = x0(i) * Sin(al) + y0(i) * Cos(al)
Next i
For i = 1 To 4
nx = x(i Mod 4 + 1)
ny = y(i Mod 4 + 1)
GSolidLine 240 + x(i), 180 + y(i), 240 + nx, 180 + ny
Next i
Next k
End Sub
i Mod 4 + 1 で「次の頂点」を求めています。
i が 1,2,3 のときは 2,3,4 ですが、i が 4 のときは 1 になります
(4 Mod 4 = 0、0 + 1 = 1)。
これで最後の頂点から最初の頂点へ戻る線が自動的に引けます。
Mod を「輪をつくる」ために使う ── よく出てくる書き方です。
3 次元では、回す軸が 3 本あります。元資料はα・β・γ の 3 段に分けて、 順に回していく形で書いています。式は 2 次元のものを、それぞれの平面に当てはめたものです。
3 次元の点を画面に描くには、平らにしなければなりません。
いちばん簡単なのは z を捨てて (x, y) だけ使うことです
(正投影といいます)。奥行きの感じは弱くなりますが、回転させれば立体だと分かります。
そして第 4 章の結合法則を思い出してください ──
3 つの回転をあらかじめ 1 つの行列にまとめておけば、
点ごとに 3 回計算しなくて済みます。これが 3D グラフィックスで行列を使う理由です。
Sub Macro5()
Dim vx(8) As Double, vy(8) As Double, vz(8) As Double
Dim px(8) As Double, py(8) As Double
Dim i As Integer, j As Integer
Dim a As Double, b As Double, t As Double
' 立方体の 8 頂点(原点が中心)
vx(1) = -70: vy(1) = -70: vz(1) = -70
vx(2) = 70: vy(2) = -70: vz(2) = -70
vx(3) = 70: vy(3) = 70: vz(3) = -70
vx(4) = -70: vy(4) = 70: vz(4) = -70
vx(5) = -70: vy(5) = -70: vz(5) = 70
vx(6) = 70: vy(6) = -70: vz(6) = 70
vx(7) = 70: vy(7) = 70: vz(7) = 70
vx(8) = -70: vy(8) = 70: vz(8) = 70
a = 0.6 ' x 軸まわり(ラジアン)
b = 0.5 ' y 軸まわり
For i = 1 To 8
' ① x 軸まわりに a 回す
t = vy(i) * Cos(a) - vz(i) * Sin(a)
vz(i) = vy(i) * Sin(a) + vz(i) * Cos(a)
vy(i) = t
' ② y 軸まわりに b 回す
t = vx(i) * Cos(b) + vz(i) * Sin(b)
vz(i) = -vx(i) * Sin(b) + vz(i) * Cos(b)
vx(i) = t
' ③ z を捨てて画面へ(中心を 240,180 に)
px(i) = 240 + vx(i)
py(i) = 180 + vy(i)
Next i
' 12 本の辺を引く
For i = 1 To 4
j = i Mod 4 + 1
GColor 0, 63, 140
GSolidLine px(i), py(i), px(j), py(j) ' 手前の面
GColor 130, 160, 200
GSolidLine px(i + 4), py(i + 4), px(j + 4), py(j + 4) ' 奥の面
GColor 240, 131, 0
GSolidLine px(i), py(i), px(i + 4), py(i + 4) ' つなぐ辺
Next i
End Sub
第 14 章の再帰が、ここで効いてきます。 「図形の一部が、全体と同じ形になっている」図形をフラクタルといい、 再帰でそのまま書けます。
作り方。1 本の線分を 3 等分し、
真ん中の 1 本を山形(正三角形の 2 辺)に置きかえます。
できた 4 本それぞれに、同じことを繰り返します。
段数を n とすると線分は 4n 本、
長さは (4/3)n 倍になります ──
段数を無限にすると長さが無限になるのに、囲む面積は有限という不思議な図形です。
Sub Macro6()
' n = 0 なら 1 本の線。1 で 4 本、2 で 16 本、5 で 1024 本
Call koch(30, 280, 450, 280, 4)
End Sub
Sub koch(ByVal x1 As Double, ByVal y1 As Double, _
ByVal x2 As Double, ByVal y2 As Double, ByVal n As Integer)
Dim ax As Double, ay As Double
Dim bx As Double, by As Double
Dim cx As Double, cy As Double
Dim h As Double
If n = 0 Then ' 終了条件(第 14 章)
GSolidLine x1, y1, x2, y2
Exit Sub
End If
' 3 等分の点
ax = x1 + (x2 - x1) / 3
ay = y1 + (y2 - y1) / 3
bx = x1 + (x2 - x1) * 2 / 3
by = y1 + (y2 - y1) * 2 / 3
' 山の頂点(中点から垂直に √3/6 だけ上へ)
h = Sqr(3) / 6
cx = (x1 + x2) / 2 + (y2 - y1) * h
cy = (y1 + y2) / 2 - (x2 - x1) * h
Call koch(x1, y1, ax, ay, n - 1) ' 4 つの小さい同じ問題
Call koch(ax, ay, cx, cy, n - 1)
Call koch(cx, cy, bx, by, n - 1)
Call koch(bx, by, x2, y2, n - 1)
End Sub
作り方。1 本の線分を、 直角二等辺三角形の 2 辺に置きかえます。 ただし折る向きを左右交互にするところが要点で、 これだけで驚くほど複雑な形が現れます。 段数 n で線分は 2n 本です。
Sub Macro7()
Call dragon(180, 200, 330, 200, 12, 1)
End Sub
Sub dragon(ByVal x1 As Double, ByVal y1 As Double, _
ByVal x2 As Double, ByVal y2 As Double, _
ByVal n As Integer, ByVal s As Integer)
Dim mx As Double, my As Double
If n = 0 Then
GSolidLine x1, y1, x2, y2
Exit Sub
End If
' 中点を、線分に垂直な向きへ半分だけずらす(s で左右が決まる)
mx = (x1 + x2) / 2 + s * (y2 - y1) / 2
my = (y1 + y2) / 2 - s * (x2 - x1) / 2
Call dragon(x1, y1, mx, my, n - 1, 1) ' 前半は左へ折る
Call dragon(mx, my, x2, y2, n - 1, -1) ' 後半は右へ折る
End Sub
プログラムは 10 行ほどしかありません。
それでこの複雑さが出るのが、再帰の力です。
「複雑な形」と「複雑なプログラム」は別のことだと分かります ──
自然界の形(海岸線、雪の結晶、木の枝分かれ、シダの葉)が
簡単な規則の繰り返しで説明できるのと同じです。
n を 1 から順に上げていくと、形が育っていくのが見えます。
14 にすると 16384 本になり、少し時間がかかります。
第 12 章でバブルソートを書きました。元資料はさらに挿入法と選択法を扱っています。 3 つとも同じ答えを出しますが、やり方が違います。
| 方法 | 考え方 | たとえると |
|---|---|---|
| バブルソート | 隣どうしを比べて、逆なら交換。何周もする | 泡が浮き上がるように、大きい値が端へ寄る |
| 挿入法 (挿入ソート) |
左から 1 枚ずつ取り、すでに並んだ列の正しい位置に差し込む | トランプを手札に並べていくときのやり方 |
| 選択法 (選択ソート) |
残りの中からいちばん小さいものを選んで先頭と交換 | 背の順に並ぶとき、いちばん小さい人を先に決める |
Dim hikaku As Long
Dim koukan As Long
Sub Macro8()
Dim a(1 To 8) As Integer
Dim i As Integer
Dim n As Integer
n = 8
' ① バブルソート
Call yomu(a, n)
hikaku = 0: koukan = 0
Call bubble(a, n)
Call kaku(a, n, 4, "バブル")
' ② 挿入法
Call yomu(a, n)
hikaku = 0: koukan = 0
Call insertion(a, n)
Call kaku(a, n, 7, "挿入法")
' ③ 選択法
Call yomu(a, n)
hikaku = 0: koukan = 0
Call selection(a, n)
Call kaku(a, n, 10, "選択法")
End Sub
Sub yomu(ByRef a() As Integer, ByVal n As Integer)
Dim i As Integer
For i = 1 To n
a(i) = Cells(2, i).Value
Next i
End Sub
Sub kaku(ByRef a() As Integer, ByVal n As Integer, ByVal gy As Integer, ByVal nm As String)
Dim i As Integer
Cells(gy, 1) = nm
For i = 1 To n
Cells(gy + 1, i) = a(i)
Next i
Cells(gy, 4) = "比較 " & hikaku & " 回"
Cells(gy, 6) = "交換 " & koukan & " 回"
End Sub
Sub bubble(ByRef a() As Integer, ByVal n As Integer)
Dim i As Integer, j As Integer, t As Integer
For i = 1 To n - 1
For j = 1 To n - i
hikaku = hikaku + 1
If a(j) > a(j + 1) Then
t = a(j): a(j) = a(j + 1): a(j + 1) = t
koukan = koukan + 1
End If
Next j
Next i
End Sub
Sub insertion(ByRef a() As Integer, ByVal n As Integer)
Dim i As Integer, j As Integer, x As Integer
For i = 2 To n
x = a(i) ' 手に取る 1 枚
j = i - 1
Do While j >= 1
hikaku = hikaku + 1
If a(j) <= x Then Exit Do ' ここに差し込めばよい
a(j + 1) = a(j) ' 1 つ右へずらす
koukan = koukan + 1
j = j - 1
Loop
a(j + 1) = x ' 差し込む
Next i
End Sub
Sub selection(ByRef a() As Integer, ByVal n As Integer)
Dim i As Integer, j As Integer, mn As Integer, t As Integer
For i = 1 To n - 1
mn = i ' いちばん小さいものの位置
For j = i + 1 To n
hikaku = hikaku + 1
If a(j) < a(mn) Then mn = j
Next j
If mn <> i Then
t = a(i): a(i) = a(mn): a(mn) = t
koukan = koukan + 1
End If
Next i
End Sub
| 方法 | 比較回数 | 交換(移動)回数 | 得意な場面 |
|---|---|---|---|
| バブル | つねに 28 回 n(n−1)/2 | 逆順の度合いによる | (学習用。実務では使わない) |
| 挿入法 | 並びぐあいによる 最良 n−1、最悪 n(n−1)/2 | ずらす回数 | ほぼ並んでいるデータに強い |
| 選択法 | つねに 28 回 | 最大 n−1 回だけ | 交換の負担が大きいとき(重いデータ) |
3 つの違いは「何を節約するか」です。
選択法は交換の回数がとても少ない(最大 n−1 回)ので、
1 件のデータが大きくて動かすのが重いときに有利です。
挿入法はすでにほぼ並んでいるときにほとんど比較せずに終わります。
上のモデルで、B2〜I2 にすでに小さい順に並んだ数を入れて実行してみてください ──
挿入法の比較回数が 7 回まで減ります(他の 2 つは 28 回のまま)。
「速さ」はデータの性質によって変わるのです。
x2 = x1·Cos − y1·Sin/y2 = x1·Sin + y1·Cos。
中心は原点なので、描くときに位置を足すこの授業の終わりに。
第 1 章でセルに 1 つ値を入れるところから始めて、
変数・条件・繰り返し・部品・配列・再帰と積み上げ、
いま10 行のプログラムでドラゴン曲線が描けるところまで来ました。
どの回も使った道具は同じ 3 つの構造(順次・分岐・繰り返し)と、
それを部品にまとめる仕組みだけです。
プログラミングで覚えることは、実はそれほど多くありません ──
難しいのは、やりたいことを機械が実行できる手順に分解することです。
それは練習で身につきます。
もとにしたのは「情報学入門(VBA)190・195・197・200・205」の 5 本(計 110 枚)です。
| 元資料 | この教材 | 種別 |
|---|---|---|
| 同じ内容が5 本に分かれている (195 と 200 がほぼ同内容、197 と 205 がほぼ同内容。表題は「〜2」「〜3」「〜5」「〜6」で、 「〜4」が欠番) | 1 つの章にまとめた。新しい版(200・205)を採った | 重複 |
| もくじの表題が「ユーザフォーム」なのに、 中身は回転体・コッホ曲線・ドラゴン曲線(197・205 の 2 枚目) | もくじの見出しと中身が対応していない。内容に合わせて節を立てた | 不整合 |
GSolidLine の中身が示されていない
(呼び出し方だけがある) |
この教材では処理系に組み込み、同じ呼び出し方で描けるようにした。
本物の Excel では Shapes.AddLine を使う実装が別途必要なことを明記 | 不足 |
| 座標系の向き(y が下)が図でしか示されていない | 本文で明示。回転の中心が原点であることと、位置合わせの必要性も追記 | 不足 |
Sin・Cos の引数がラジアンである
ことへの注意がない |
度から直す式(度 * pi / 180)を追記 | 不足 |
| 3 次元の回転を 3 段並べているが、画面に描くには z を捨てるという段が明示されていない | 正投影として明示し、第 4 章の結合法則(3 つの回転を 1 つの行列にまとめられる)と結びつけた | 不足 |
| コッホ曲線・ドラゴン曲線の線分の本数や性質に触れていない | 4n 本・2n 本、 コッホは「長さが無限で面積は有限」であることを追記。第 14 章の再帰と明示的につないだ | 不足 |
| 3 つの並べ替えを並べているが、使い分けが書かれていない | 比較回数・交換回数を実際に数えるモデルを作り、 選択法は交換が少ない/挿入法はほぼ並んだデータに強いという違いを示した | 不足 |
| バブルソートが 195・197・200・205 の4 本すべてに登場 | 第 12 章と本章に整理 | 重複 |
元資料で正しかったこと。
2 次元の回転の式、3 次元の回転を 3 軸に分けて重ねる考え方、
GSolidLine(ByVal x As Double, ByVal y As Double, ByVal xx As Double, ByVal yy As Double)
という引数の設計、コッホ曲線・ドラゴン曲線・回転体という題材の選び方
── いずれも適切です。
とくに「線分を引く手続きを 1 つ用意して、あとはその組み合わせで描く」という構成は、
グラフィックスの考え方そのもので、再帰(第 14 章)の応用としても的確な締めくくりです。