ドラゴン曲線を描く(Ruby)

自己相似図形であるドラゴン曲線を Ruby で描いてみました。
 
3次。

5次。

10次。これだと確かにドラゴンみたいですね。

 
描画には自作の Gem 'oekaki' を使っています。 

require 'oekaki'

Width, Height = 600, 400

class Point < Struct.new(:x, :y)
end

Oekaki.app width: Width, height: Height, title: "Dragon curve" do
  draw do
    color(0, 0, 0)
    rectangle(true, 0, 0, Width, Height)
    
    drawing = proc do |a, b, depth|
      x = b.x - a.x
      y = a.y - b.y
      
      c = Point.new
      c.x = a.x + (x + y) / 2
      c.y = b.y + (x + y) / 2
      
      if depth.zero?
        color(0, 65535, 0)
        line(a.x, a.y, c.x, c.y)
        line(b.x, b.y, c.x, c.y)
      else
        drawing[a, c, depth - 1]
        drawing[b, c, depth - 1]
      end
      true
    end
    
    a, b = Point.new, Point.new
    a.x, a.y = 150.0, 150.0 
    b.x, b.y = Width - 150.0, 150.0
    
    drawing[a, b, 5]
  end
end

 
Gem 'oekaki' については以下。
oekaki | RubyGems.org | your community gem host
GTK+でお絵かきしてみた(Ruby) - Camera Obscura

マンデルブロ集合を描いてみる(Ruby)


 
このサイトのそのままパクリです(ありがとうございます!)。やったのは Java から Ruby へ移植しただけ。
 

def mandelbrot_count(c)
  z = Complex(0)
  100.times do |i|
    z = z ** 2 + c
    return i if z.abs > 10
  end
  100
end

Diff = 0.001
io = open("mandelbrot_data.dat", "w+")

-2.step(1, Diff) do |r|
  -1.step(1, Diff) do |i|
    value = mandelbrot_count(Complex(r, i))
    next if value.zero?
    io.puts "#{r}\t#{i}\t#{value}"
  end
  io.print "\n"
end

io.close

自分の環境では 4分あまりかかりました。
 
 
gnuplot で描画します。これは上サイトそのまま。

set pm3d
set pm3d map
set palette defined(0"#000099",1"#ffffff",2"black")
set terminal png size 1024,768
set output 'mandelbrot-pm3d.png'
splot 'mandelbrot_data.dat' notitle

コッホ曲線を描く(Python, Ruby)

自己相似図形であるコッホ曲線を PythonRuby で描いてみました。


Python では手軽にタートル・グラフィックスが使えるので、これを利用するのが簡単です。

 
3次のコッホ曲線を描きます。

from turtle import *

def draw(length, depth):
    if depth == 0:
        forward(length)
    else:
        draw(length / 3, depth - 1)
        left(60)
        draw(length / 3, depth - 1)
        right(120)
        draw(length / 3, depth - 1)
        left(60)
        draw(length / 3, depth - 1)

color('firebrick')
up()
setx(-250)
down()

draw(500, 3)
input()

 
 
Ruby では簡単なタートル・グラフィックスを実装して描くことにします(Turtle クラス)。描画は自作の Gem 'oekaki' で行っています。

 
4次のコッホ曲線を描きます。

require 'oekaki'

Width, Height = 600, 300

class Turtle
  def initialize(x, y, ob)
    @pen_po = Vector[x, y]
    @pen = ob
    @dir = Vector[1, 0]
  end
  
  def left(deg)
    θ = PI * deg / 180
    @dir = Matrix[[cos(θ), -sin(θ)], [sin(θ), cos(θ)]] * @dir
  end
  
  def right(deg)
    left(-deg)
  end
  
  def forward(length)
    next_po = @pen_po + @dir * length
    @pen.line(Width / 2 + next_po[0], Height / 2 - next_po[1],
       Width / 2 + @pen_po[0], Height / 2 - @pen_po[1])
    @pen_po = next_po
  end
end


Oekaki.app width: Width, height: Height, title: "Koch curve" do
  draw do
    color(0, 0, 0)
    rectangle(true, 0, 0, Width, Height)
    
    t = Turtle.new(-250, -50, self)
    
    drawing = proc do |length, depth|
      if depth.zero?
        t.forward(length)
      else
        drawing[length / 3, depth - 1]
        t.left(60)
        drawing[length / 3, depth - 1]
        t.right(120)
        drawing[length / 3, depth - 1]
        t.left(60)
        drawing[length / 3, depth - 1]
      end
    end
    
    color(0, 65535, 0)
    drawing[500.0, 4]
  end
end

Python でも Ruby でもやっていることはほぼ同じです。

Gem 'oekaki' については以下。
oekaki | RubyGems.org | your community gem host
GTK+でお絵かきしてみた(Ruby) - Camera Obscura
 
誰かえらい人、Ruby でタートル・グラフィックスを実装しないですかね。初心者が遊ぶのに手頃だと思うのだけれど。自分でやれるといいのだけれど、Python みたいにインタラクティブに描画させるようにすると自分のスキルではむずかしそう。
 
 
※参考
GTK+でヒルベルト曲線(Ruby) - Camera Obscura
Python の Turtle でヒルベルト曲線 - Camera Obscura

Ruby でローレンツアトラクタを描画する

Ruby + gnuplotローレンツアトラクタを描いてみました。
 

 
全体的にここなどを参考にしました。微分方程式オイラー法(参考)で数値計算しています。

gnuplot での描画は numo/gnuplot という Gem を使っています。

require 'numo/gnuplot'

fx = lambda {|x, y, z, r, p, b| -p * x + p * y}
fy = lambda {|x, y, z, r, p, b| -x * z + r * x - y}
fz = lambda {|x, y, z, r, p, b|  x * y - b * z}

dt = 1e-3
x, y, z = 1, 1, 1
p, r, b = 10, 28, 8 / 3.0

ax, ay, az = [], [], []

100000.times do
  x += dt * fx[x, y, z, r, p, b]
  y += dt * fy[x, y, z, r, p, b]
  z += dt * fz[x, y, z, r, p, b]
  ax << x
  ay << y
  az << z
end

Numo.gnuplot do
  unset :key
  splot ax, ay, az, w: :dots
end
gets    #終了待ち

1Ωの抵抗10個で黄金比の値に近づける(Ruby)

問題:

1Ω の抵抗 10個を使い、合成抵抗が黄金比 1.6180339887..Ωにもっとも近づく場合の値を、少数第10位まで求めよ。

 
aΩ と bΩ の抵抗をつなげる場合、直列つなぎにすれば合成抵抗はたんに a + b Ω になりますが、並列つなぎの場合はそれらの逆数の和の逆数、つまり
  
になるのが重要なところです。抵抗10個できわめて複雑な組み合わせをつくることができます。

Ruby で求めてみました。

require 'set'

def product(a, b)
  @ar[a].each do |i|
    @ar[b].each do |j|
      @ar[a + b] << i + j
      @ar[a + b] << (i * j) / (i + j)
    end
  end
end


@ar = Array.new(11) {Set.new}
@ar[1] << Rational(1, 1)
for i in 2..10
  for j in 1..(i / 2)
    product(j, i - j)
  end
end

ans = 10
@ar[10].each do |x|
  ans = x if (x - 1.6180339887).abs < (ans - 1.6180339887).abs
end
puts "%.10f" % ans.to_f
puts ans

答えは 1.6181818182 となります。分数だと 89/55 ですね。
ちなみに、模範解答よりもずっとシンプルなコードだと思います。

さて、これがどのような回路なのかですが、それも求めるとなるとさらに手を入れないといけないですね。だいぶ複雑になりそうです。
 
 

ナップサック問題(Ruby)

次のような問題があるとします。

学校でクラブ活動をするのに、選んだクラブの人数の合計が 150人以下になるようにするとします。クラブの想定人数とそれが使う敷地面積は次のように与えられています。
20170526161808
クラブを幾つか適当に選ぶとき、必要な敷地面積の総和の最大値を求めなさい。

 
Ruby で解いてみました。総当り法でも解けますが、動的計画法を使いました。

number = [40, 30, 24, 20, 14, 16, 15, 40, 10, 12]
square = [11000, 8000, 400, 800, 900, 1800, 1000, 7000, 100, 300]

area = Array.new(151, 0)
for i in 0..9
  tmp = area.dup
  area.each_with_index do |sq, j|
    next if sq.zero?
    index = j + number[i]
    next if index > 150
    a = sq + square[i]
    tmp[index] = a if a > tmp[index]
  end
  tmp[number[i]] = square[i] if square[i] > tmp[number[i]]
  area = tmp
end
puts area.max

答えは 28800平方メートルになります。
 

 
 

追記

配列ではなくハッシュを使うべきかも知れません。この程度ならそれほど実行時間に差はでませんが。

number = [40, 30, 24, 20, 14, 16, 15, 40, 10, 12]
square = [11000, 8000, 400, 800, 900, 1800, 1000, 7000, 100, 300]

area = {}
for i in 0..9
  tmp = area.dup
  area.each do |num, sq|
    index = num + number[i]
    next if index > 150
    a = sq + square[i]
    tmp[index] = a if !tmp[index] or a > tmp[index]
  end
  tmp[number[i]] = square[i] if !tmp[number[i]] or square[i] > tmp[number[i]]
  area = tmp
end
puts area.values.max

右折禁止(アルゴリズム・パズル)

アルゴリズム・パズルを Ruby で解いてみました。
20170526124128
 

class TurnLeft
  class Position
    def initialize(x, y)
      @x, @y = x, y
    end
    attr_accessor :x, :y
    
    def +(dir)
      Position.new(@x + dir[0], @y + dir[1])
    end
  end
  
  class Field
    def initialize
      @yoko = Array.new(Height + 1) {Array.new(Width , 0)}
      @tate = Array.new(Width  + 1) {Array.new(Height, 0)}
    end
    
    def set(po, dir)
      if dir[1].zero?
        x = (dir[0] > 0) ? po.x : po.x - 1
        @yoko[po.y][x] = 1
      else
        y = (dir[1] > 0) ? po.y : po.y - 1
        @tate[po.x][y] = 1
      end
    end
    private :set
    
    def get(po, dir)
      if dir[1].zero?
        x = (dir[0] > 0) ? po.x : po.x - 1
        return 1 if x >= Width or x < 0
        a = @yoko[po.y][x]
      else
        y = (dir[1] > 0) ? po.y : po.y - 1
        return 1 if y >= Height or y < 0
        a = @tate[po.x][y]
      end
      set(po, dir) if a.zero?
      a
    end
    
    def dup
      Marshal.load(Marshal.dump(self))
    end
  end
  
  def go
    puts main(Position.new(0, 0), [1, 0], Field.new)
  end
  
  def main(po, dir, field)
    co = 0
    nxt = po + dir
    return 1 if nxt.x == Width and nxt.y == Height   #ゴールか?
    return 0 if nxt.x > Width or nxt.x < 0 or nxt.y > Height or nxt.y < 0
    return 0 unless field.get(po, dir).zero?         #すでに通っているか?
    
    co += main(nxt, dir, field.dup)                  #直進
    co += main(nxt, [-dir[1], dir[0]], field.dup)    #左折
  end
  private :main
end

Width, Height = 6, 4

TurnLeft.new.go

答えは 2760通りです。かかった時間は自分の環境で約 3.7秒でした。