すべての組み合わせについて配列を n 個に分割する(Ruby)

Array#divide(n) です。遊びで作ってみました。
配列を n 個に分割する、すべての組み合わせをブロックに渡します。ブロックが与えられなければ Enumerator を返します。

[*2..6].divide(3) {|i| p i} 
#=>
#[[2], [3], [4, 5, 6]]
#[[2], [3, 4], [5, 6]]
#[[2], [3, 4, 5], [6]]
#[[2, 3], [4], [5, 6]]
#[[2, 3], [4, 5], [6]]
#[[2, 3, 4], [5], [6]]
p "Ruby".chars.divide(3).with_object([]) {|i, h| h << i.map(&:join)}
#=>[["R", "u", "by"], ["R", "ub", "y"], ["Ru", "b", "y"]]

あんまり使いみちは思いつかないのですが(笑)。
 
コード。
divide.rb

class Array
  def divide(n)
    if !n.class.ancestors.include?(Integer) or !n.between?(1, size)
      raise ArgumentError, "can not devide into #{n}."
    end
    e = Enumerator.new do |y|
      [*0...size - 1].combination(n - 1) do |ar|
        ar.sort!
        take_num = []
        ([-1] + ar + [size - 1]).each_cons(2) {|i, j| take_num << j - i}
        result = []
        tmp = self
        take_num.each do |i|
          result << tmp.take(i)
          tmp = tmp.drop(i)
        end
        y << result
      end
    end
    block_given? ? loop {yield(e.next)} : e
  end
end

Gem 'oekaki' にタートルグラフィックスを追加

前回のエントリで作った Turtle クラスを Gem 'oekaki' に同梱しました。これでバージョンは 0.1.5 になります。
oekaki | RubyGems.org | your community gem host
GTK+でお絵かきしてみた(Ruby) - Camera Obscura
 
使い方はほぼ同じです。インスタンスの生成に

t = Turtle.new(width, height, self)

だったのが、

t = Oekaki::Turtle.new

になるだけです。引数はまったく要りません。

例。

require 'oekaki'

Oekaki.app width: 600, height: 400, title: "C curve" do
  draw do
    clear
    
    t = Oekaki::Turtle.new
    t.move(-130, -100)
    t.color(0, 65535, 0)
    ratio = sqrt(2) / 2
    
    drawing = lambda do |length, depth|
      if depth.zero?
        t.forward(length)
      else
        t.left(45)
        drawing[length * ratio, depth - 1]
        t.right(90)
        drawing[length * ratio, depth - 1]
        t.left(45)
      end
    end
    
    drawing[260.0, 10]
  end
end

 

obelisk.hatenablog.com

タートルグラフィックスで再帰曲線をいろいろ描いてみる(Ruby)

Ruby で簡単なタートルグラフィックスを実装して、いろいろ再帰曲線を描いてみました。描画には自作の Gem 'oekaki' を使っています。
oekaki | RubyGems.org | your community gem host
GTK+でお絵かきしてみた(Ruby) - Camera Obscura
 
※追記
Gem化しました。
Gem 'oekaki' にタートルグラフィックスを追加 - Camera Obscura
再帰曲線を描くなら、L-system も参考になると思います。これも実装しました。
再帰曲線を描く言語「L-system」を Ruby で実装した - Camera Obscura
 

タートルグラフィックスの実装はこんな感じです。
turtle.rb(Gist

class Turtle
  def initialize(width, height, ob)
    @width, @height = width, height
    @pen_po = Vector[0, 0]
    @pen = ob
    @dir = Vector[1, 0]
    @color = [65535, 65535, 65535]
  end
  attr_accessor :pen_po, :dir
  
  def left(deg)
    θ = PI * deg / 180
    @dir = Matrix[[cos(θ), -sin(θ)], [sin(θ), cos(θ)]] * @dir
  end
  
  def right(deg)
    left(-deg)
  end
  
  def forward(length, draw = true)
    next_po = @pen_po + @dir * length
    if draw
      @pen.color(*@color)
      @pen.line(@width / 2 + next_po[0], @height / 2 - next_po[1],
         @width / 2 + @pen_po[0], @height / 2 - @pen_po[1])
    end
    @pen_po = next_po
  end
  
  def back(length)
    forward(-length, false)
  end
  
  def color(r, g, b)
    @color = [r, g, b]
    @pen.color(*@color)
  end
  
  def circle(radius, fill = false)
    @pen.color(*@color)
    @pen.circle(fill, @width / 2 + @pen_po[0], @height / 2 - @pen_po[1], radius)
  end
  
  def move(x, y)
    @pen_po = Vector[x, y]
  end
end

特にむずかしいところはないと思います。なお、内部の座標 @pen_po は原点がキャンバスの中心で、(x, y) 座標の向きは数学の慣例どおり(つまり、y 座標は上がプラス方向)です。メソッド left(), right() は向きの回転、forward(length, draw) は長さ length だけ前進(draw が false なら位置が移動するだけで描画されない)、circle(radius, fill) はペンの位置に半径 radius の円を描く(fill が true ならば内部を満たす)、move() はその座標へ移動、back(length) は length だけ戻る(描画はされない)。

Gem 'oekaki' が内部で require 'matrix', include Math をしているので、行列・ベクトル演算と Math モジュールがそのまま使えます。Turtle.new(w, h, self) は draw メソッドなどのブロック内で行って下さい。(インスタンスはブロックの外へ持ちだしてもかまいません。)


C曲線。Gist

require 'oekaki'
require_relative 'turtle'

Width, Height = 600, 400

Oekaki.app width: Width, height: Height, title: "C curve" do
  draw do
    clear
    
    t = Turtle.new(Width, Height, self)
    t.move(-130, -100)
    t.color(0, 65535, 0)
    ratio = sqrt(2) / 2
    
    drawing = lambda do |length, depth|
      if depth.zero?
        t.forward(length)
      else
        t.left(45)
        drawing[length * ratio, depth - 1]
        t.right(90)
        drawing[length * ratio, depth - 1]
        t.left(45)
      end
    end
    
    drawing[260.0, 10]
  end
end

 
シェルピンスキー曲線。Gist

require 'oekaki'
require_relative 'turtle'

Width, Height = 600, 600

Oekaki.app width: Width, height: Height, title: "Sierpinski curve" do
  draw do
    clear
    
    depth = 5
    l = 550
    num = 2 ** (depth - 1)
    
    t = Turtle.new(Width, Height, self)
    t.color(0xffff, 0, 0xffff)
    step = l / ((2 * sqrt(2) + 1) * num + num - 1)
    t.move(-l / 2 + step / sqrt(2), l / 2)
    
    drawing = lambda do |depth, angle = 45|
      if depth.zero?
        t.forward(step)
      else
        t.right(angle)
        drawing[depth - 1, -angle]
        t.left(angle)
        t.forward(step)
        t.left(angle)
        drawing[depth - 1, -angle]
        t.right(angle)
      end
    end
    
    4.times do
      drawing[2 * depth - 1]
      t.right(45)
      t.forward(step)
      t.right(45)
    end
  end
end

 
コッホ曲線。Gist

require 'oekaki'
require_relative 'turtle'

Width, Height = 600, 600

Oekaki.app width: Width, height: Height, title: "Koch curve" do
  draw do
    clear
    
    l = 500
    t = Turtle.new(Width, Height, self)
    t.color(0, 65535, 65535)
    t.move(l / 2, -150)
    t.dir = Vector[-1, 0]
    
    drawing = lambda 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
    
    3.times do
      drawing[l, 3]
      t.right(120)
    end
  end
end

 
ヒルベルト曲線。Gist

require 'oekaki'
require_relative 'turtle'

Width, Height = 600, 600

Oekaki.app width: Width, height: Height, title: "Hilbert curve" do
  draw do
    clear
    
    depth = 5
    
    l = 500
    t = Turtle.new(Width, Height, self)
    t.color(0xffff, 0x7dff, 0)
    t.move(-l / 2, l / 2)
    step = l / (2 ** depth - 1)
    
    drawing = lambda do |depth, angle|
      if depth.zero?
        return
      else
        t.right(angle)
        drawing[depth - 1, -angle]
        t.forward(step)
        t.left(angle)
        drawing[depth - 1,  angle]
        t.forward(step)
        drawing[depth - 1,  angle]
        t.left(angle)
        t.forward(step)
        drawing[depth - 1, -angle]
        t.right(angle)
      end
    end
    
    drawing[depth, 90]
  end
end

 
ドラゴン曲線。Gist

require 'oekaki'
require_relative 'turtle'

Width, Height = 600, 450

Oekaki.app width: Width, height: Height, title: "Dragon curve" do
  draw do
    clear
    
    depth = 12
    
    l = 350
    t = Turtle.new(Width, Height, self)
    t.color(0xf0ff, 0xffff, 0xffff)
    t.move(-l / 2, 50)
    
    drawing = lambda do |length, depth|
      if depth.zero?
        t.forward(length)
      else
        len = length / sqrt(2)
        po1 = t.pen_po
        t.forward(length, false)
        po2, t.pen_po = t.pen_po, po1
        t.right(45)
        drawing[len, depth - 1]
        t.pen_po = po2
        t.right(90)
        drawing[len, depth - 1]
        t.pen_po = po2
        t.left(135)
      end
    end
    
    drawing[l, depth]
  end
end

 

参考にしたサイト。(ありがとうございます!)
再帰プログラムによるフラクタル図形の描画:CodeZine(コードジン)
再帰曲線で遊ぼう
Pythonとタートルグラフィックスによる(再帰)プログラミング教育処方案 - Read -> Blog
特にいちばん下のブログ記事がすごいです。せっかくなので出来るだけ自力でやりました^^;

 

追加

このサイトの描画例を Python から移植しました。ありがとうございます!

格子曲線。Gist

require 'oekaki'
require_relative 'turtle'

Width, Height = 400, 400

Oekaki.app width: Width, height: Height, title: "Grate curve" do
  draw do
    clear(color(0xffff, 0x7dff, 0))
    
    t = Turtle.new(Width, Height, self)
    t.color(0, 0, 0)
    t.move(-160, 150)
    
    two = lambda do |a, c, w|
      return if c <= 1
      t.right(a)
      t.forward(1)
      t.right(a)
      t.forward(w)
      t.left(a)
      t.forward(1) if c > 1
      t.left(a)
      t.forward(w)
      two[a, c - 2, w]
    end
    
    square = lambda do |a, h, w|
      t.forward(w)
      two[a, h - 1, w]
    end
    
    grate = lambda do |n, a, w, h|
      if n.zero?
        square[a, h, w]
      else
        t.right(a)
        grate[n - 1, -a, h / 4, w]
        t.forward(h / 8)
        grate[n - 1,  a, h / 4, w]
        t.forward(h / 8)
        grate[n - 1, -a, h / 4, w]
        t.left(a)
      end
    end
    
    grate[4, 90, 320, 320]
  end
end

 
クヌース曲線。Gist

require 'oekaki'
require_relative 'turtle'

Width, Height = 600, 350

Oekaki.app width: Width, height: Height, title: "Knuth curve" do
  draw do
    clear
    
    t = Turtle.new(Width, Height, self)
    t.color(0xf0ff, 0xffff, 0xffff)
    t.move(250, -100)
    t.left(180)
    step = 8
    
    drawing = lambda do |depth, a, tn|
      if depth.zero?
        t.right(45 + tn)
        t.forward(step)
        t.left(45 + tn)
      else
        t.right(2 * tn + a)
        drawing[depth - 1, 2 * tn, -tn]
        t.right(45 - 3 * tn - a)
        t.forward(step)
        t.left(45 - tn + a)
        drawing[depth - 1, 0, -tn]
        t.right(a)
      end
    end
    
    drawing[9, -90, 45]
  end
end

 
まだコードの意味はよくわかっていないのですが(汗)。よく考えてみたいと思います。

『プロを目指す人のための Ruby 入門』を読んでみた

 
評判がよいので読んでみました。アマゾンのレヴューで、独学で Ruby を勉強している人なら読むべきみたいなことも書いてありましたので。

結論からいうと、自分にはあまり得るところは多くなかったですね。プログラミング初心者では読めないでしょうが、本当に Ruby 初心者向きですね。Ruby を使いこなすにはリファレンス・マニュアル(日本語です)を読むのがいちばんですが、これが読める人なら本書は必要ないかも知れません。

けれども、本書がいい本でないというのではまったくありません。基本的に Ruby が使えるようになる(Ruby minimum)という目的のためなら、これはいい本だと思います。本書で弱いのは正規表現*1、ファイル処理*2クロージャ*3の説明くらいで、あとはじつに丁寧に書かれています。文章も読みやすいです。特に、アマゾンのレヴューにもありましたが、テストを積極的に書いて開発するという体裁になっていて、仕事に使う人には必須な気がします。まあ素人の僕などは最悪なことにテストなどまったく書かないので、こういうのを真似てはいけません。さすがに仕事で使っている人だなと思いました。

本書では最終的に Ruby on Rails を使うということが念頭におかれています。もっとも Rails に特化した本ではまったくないので、その点は Rails に関係ない人でも大丈夫です。ただ、これは本書とは関係ないですが、Ruby の人気はあまりにも RoR に負っているのですよね。いま出る Ruby 本は、Rails 関係のものが多いです。そもそも Ruby が世界的な人気言語になったのは RoR のおかげで、それゆえに RoR の退潮とともに Ruby 人気が陰ってきているのはまちがいありません。自分はこれは仕方のないことだと思いますが、Ruby というのがそれ自体非常に優れた、興味深い言語であることが忘れられがちなのは、Rubyist として残念な気がします。実際、Ruby 以降の言語で Ruby に何も影響を受けていないというような人気言語は少ないように思えます。まあそれは自分のような初心者の正確に判断できることではないですが。


話は替りますが、Ruby の入門書の定番といえばこれですよね。僕も愛用してきました。いまだに簡単なリファレンス本としてもよく参考にするくらいです。

たのしいRuby 第5版

たのしいRuby 第5版

これ、最新版は第五版なのですが、アマゾンのレヴューで非常に評価が悪い。自分は第四版で読んだのですが、たぶん中身はほとんど変っていないと推測します。なのに…。特に、「題に『たのしい』とあるが、全然楽しくない!」というレヴューが多くて、何なのだという感じ。Ruby が楽しい言語だというのが題の意図だと思うのですが…。しかし、これを読んでわからないとは。確かに、プログラミングを初めてやるという人のため本ではないですが、最良の Ruby 入門書として読み継がれてきたのですけれどね。読者の層が替わったということでしょうか。

あと Ruby の入門書としてはこれが有名です。

初めてのRuby

初めてのRuby

ただこれは他言語の使える人が最速で Ruby を習得するための本です。Ruby が初めてのプログラミング言語という人には向きません。

Ruby の特徴のひとつである「メタプログラミング」についてはこれ。

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

わかりやすい本ですが、中身はやさしい本ではありません。入門書を読んでからでしょうね。


何だかまとまりのない記事になってしまった…。

*1:RubyPerl 由来で文字列処理が非常に強力なのも特徴のひとつなので、これは何かで補足しておくべきです。

*2:それにしても、ファイル処理の説明がないに等しいというのには驚きました。いまってファイル処理しないの? そんなことはないと思うのですが。

*3:クロージャはいまやたいていの人気言語でサポートされている重要なプログラミング機能です。クロージャが使えるとプログラミングの世界が大きく広がるので、是非身に付けたほうがよいと思います。よく説明は JavaScript でされています。もちろん Ruby でも使えます。Proc あるいは lambda がそれです。

無駄に複雑な関数型 FizzBuzz(Ruby)

trans = ->(i) {
  if i % 15 == 0
    "FizzBuzz"
  elsif i % 3 == 0
    "Fizz"
  elsif i % 5 == 0
    "Buzz"
  else
    i.to_s
  end
}

fizzbuzz = ->(n) {
  generate = ->(i, ar) {
    return ar if i > n
    generate[i + 1, ar + [trans[i]]]
  }
  generate[1, []]
}

p fizzbuzz[50]

何でも再帰で書きたくなる病…。ちなみに、逐字的に Lisp に移植できると思います。

※参考
FizzBuzz問題(Ruby) - Camera Obscura
みんな大好き FizzBuzz(Ruby, Python) - Camera Obscura

ふたたび Ruby で 8 queen(関数型プログラミングっぽく)

ここで Ruby で「8 クイーン問題」を解いてみましたが、関数型プログラミングのお勉強に Common Lisp のコード例を移植してみました。

参考にしたのはこのサイトです。

実行するとこんな感じです。

----------
No.1
...@....
.@......
......@.
..@.....
.....@..
.......@
....@...
@.......
----------
No.2
....@...
.@......
...@....
......@.
..@.....
.......@
.....@..
@.......
----------
...

全部で解は92通りあります。

コード。

class Array
  def car; first;   end
  def cdr; drop(1); end
end

attack = ->(x, xs) {
  attack_sub = ->(n, ys) {
    if ys.empty?
      true
    elsif ys.car + n == x or ys.car - n == x
      false
    else
      attack_sub[1 + n, ys.cdr]
    end
  }
  attack_sub[1, xs]
}

safe = ->(ls) {
  if ls.empty?
    true
  elsif attack[ls.car, ls.cdr]
    safe[ls.cdr]
  else
    false
  end
}

counter = 0

print = ->(board) {
  puts "-" * 10
  puts "No.#{counter += 1}"
  board.each do |po|
    st = "." * board.size
    st[po - 1] = "@"
    puts st
  end
}

queen = ->(nums, board) {
  if nums.empty?
    print[board] if safe[board]
  else
    nums.each do |x|
      queen[nums - [x], [x] + board]
    end
  end
}

iota = ->(n) {
  (n == 1) ? [1] : iota[n - 1] + [n]
}

queen[iota[8], []]

高速化版です。

queen_fast = ->(nums, board) {
  if nums.empty?
    print[board]
  else
    nums.each do |x|
      queen_fast[nums - [x], [x] + board] if attack[x, board]
    end
  end
}

queen_fast[iota[8], []]

Common Lisp 版のほぼ忠実な移植で、しかもずっと読みやすくなっていると思います。このような形の関数型プログラミングなら、Ruby で充分可能だということがわかります。
 
かかった時間です。最初のバージョン。

real	0m0.313s
user	0m0.276s
sys	0m0.004s

高速化されたバージョン。

real	0m0.091s
user	0m0.048s
sys	0m0.012s

6倍くらい高速化されていますね。

パスカルの三角形(Ruby)

パスカルの三角形とは、こんな感じのものです。

              1               
             1 1              
            1 2 1             
           1 3 3 1            
          1 4 6 4 1           
        1 5 10 10 5 1         
       1 6 15 20 15 6 1       
     1 7 21 35 35 21 7 1      

規則性がわかりますね。多項式 (a + b)n を展開すると、係数がこんな風に並びます。

Ruby関数型プログラミングっぽく書いてみました。結構きれいに書けます。

pascal = ->(n) {
  each_cons = ->(ar) {
    (ar.size < 2) ? [] : [ar[0] + ar[1]] + each_cons.(ar.drop(1))
  }
  n.zero? ? [1] : [1] + each_cons.(pascal.(n - 1)) + [1]
}

n = 8
n.times {|i| puts pascal.(i).join(" ").center(30)}

 

追記(2020/1/7)

Enumerator を使って。

def generate_pascal_triangle
  Enumerator.new do |y|
    y << [1]
    ary = [1, 1]
    loop do
      y << ary
      ary = [1] + ary.each_cons(2).map { |a, b| a + b } + [1]
    end
  end
end

n = 8
puts generate_pascal_triangle.take(n).map { |ary| ary.join(" ").center(30) }

 
パターンマッチを使って。

def calc_pascal_triangle(*args)
  case args
  in [1] then [[1]]
  in [2, Array => result] then result
  in [Integer => n]
    calc_pascal_triangle(n, [[1], [1, 1]])
  in [Integer => n, Array => result]
    ary = [1] + result[-1].each_cons(2).map { |a, b| a + b } + [1]
    calc_pascal_triangle(n - 1, result + [ary])
  end
end

n = 8
puts calc_pascal_triangle(n).map { |ary| ary.join(" ").center(30) }

パターンマッチ、意外とおもしろいな。無理やりだけれども、型記述や多重ディスパッチみたいなことも可能かも。Ruby に合うかどうかはわからないが。