melborne さんの Object#sequence メソッド(Ruby)

RubyにおけるシーケンスはObject#repeatに任せなさい!

いまはもう更新されていませんが、melborne さんの「hp12c」というブログがあります。この方はおもしろい Rubyist で、僕はときどきヒマに任せてこれを読むのが好きなのですね。文章がうまいし、特殊な才能があるように思われます。

で、この方の考えられた、Ruby の Object#sequence メソッドというのがあるのですよね(参照)。コードはこんなのです。
sequence.rb

class Object
  def sequence(init = true)
    x = self
    Enumerator.new do |y|
      y << x if init
      loop {y << (x = yield x)}
    end
  end
end

わかりますかね。外部イテレータを使った、一種の反復「無限リスト」ジェネレータともいえるでしょうか。もちろんこれは「リスト」(Ruby の配列)だけに留まるものではありません。とにかく「無限リスト」の Ruby での実装として、すばらしいのではないかと思ったのです。実際に Matz もツイッターで反応したくらいですからね。Feature リクエストは結局採用されなかったようですが、それでもおもしろいと思います。


で、どんな風に使うかなのですが、実際に作者がブログ記事でたくさんの例を挙げておられるので、それを御覧下さい。ってそれだけではそっけなさすぎるので、例えば(初項 3、項差 5の)等差数列は

p 3.sequence {|x| x + 5}.take(10)
#=>[3, 8, 13, 18, 23, 28, 33, 38, 43, 48]

という感じです。超簡潔ですね。

フィボナッチ数列

p [1, 1].sequence {|a, b| [b, a + b]}.take(15).map(&:first)
#=>[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]

すばらしい。

単なる「無限リスト」もこんな感じ。

p 7.sequence(&:succ).take(20)
#=>[7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]

7 から始まる「無限リスト」ですね。

このメソッドはすごく読みやすくて「きれいな」印象なのですよね。Ruby らしい感じです。


なお、もともとのメソッド名は repeat ですが、作者が Feature リクエストに採用した sequence で統一しました。
何で採用されなかったのですかね。あんまり使われなさそうだからかなあ。

よく考えてみると、Object クラスに入れるのは大袈裟かも知れませんね。Numeric, Array, String くらいで充分な気もする(あ、それだと自作のクラスに使えなくなっちゃうな)。それかせめて Kernel か。採用されなかったのはそのあたりもあるかな。

HaskellScala には iterate という名前でこの機能があるそうです。

RubyGem 'Salamander' でコッホ曲線を描く

以前、自作の Gem 'oekaki' でコッホ曲線を描いてみましたが(参照)、タートルグラフックスを実装している Gem 'salamander'(参照)を使って同じことをしてみました。

4次のコッホ曲線です。
20170819014516
 
salamander_koch_curve.rb

require 'salamander'
require 'salamander/support/radians'
include Salamander
include Salamander::Drawing

screen = Salamander.setup(600, 300)

Actor.draw(screen) do
  move_to 50, 200
  color :cyan
  face  :east
  
  drawing = proc do |length, depth|
    if depth.zero?
      line length
    else
      drawing[length / 3, depth - 1]
      turn -60.degrees
      drawing[length / 3, depth - 1]
      turn 120.degrees
      drawing[length / 3, depth - 1]
      turn -60.degrees
      drawing[length / 3, depth - 1]
    end
  end
  
  drawing[500.0, 4]
end
screen.redraw

loop { break if SDL.WaitEvent.type == SDL::QUIT }

 

みんな大好き FizzBuzz(Ruby, Python)

外部イテレータ FizzBuzz

a = ["Fizz", "Buzz", "FizzBuzz"]
h = {0=>a[2], 3=>a[0], 6=>a[0], 9=>a[0], 12=>a[0], 5=>a[1], 10=>a[1]}

g = Enumerator.new do |y|
  loop.with_index(1) do |_, i|
    y << (h[i % 15] || i.to_s)
  end
end

p g.take(20)
#=>["1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz",
#   "11", "Fizz", "13", "14", "FizzBuzz", "16", "17", "Fizz", "19", "Buzz"]

Ruby では外部イテレータを使うことはあまりないように思いますが、この例のように Ruby でも「無限リスト」を実装することができます。
あと、上ではハッシュを使ったのが工夫してみたところです。それから、Enumerator#with_index って知っていました?

n 番目の値を出力するため、遊んでみました。上にさらにコードを追加します。

class Enumerator
  def [](num)
    first(num).last
  end
end

1.upto(15) {|i| print "#{g[i]} "}
puts
p g[105]

結果。

1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 
"FizzBuzz"

まあこんなことをするのなら、最初から外部イテレータなど使わなければいいので、倒錯していますね(笑)。
 
※参考

 

Python

同等のことを Python でもやってみました。Python は全然慣れていないので、もっと上手くやるような仕方があると思うのですが。リスト処理のところがまったく無様なので、このやり方を採る意味がないですね。内包表記を使ってもっときれいに書けないでしょうか。

def generate():
    i = 1
    while True:
        if i % 15 == 0:
            a = "FizzBuzz"
        elif i % 3 == 0:
            a = "Fizz"
        elif i % 5 == 0:
            a = "Buzz"
        else:
            a = str(i)
        yield a
        i += 1

g = generate()
l = []
for i in range(20):
    l.append(next(g))
print(l)
#=>['1', '2', 'Fizz', '4', 'Buzz', 'Fizz', '7', '8', 'Fizz', 'Buzz',
#   '11', 'Fizz', '13', '14', 'FizzBuzz', '16', '17', 'Fizz', '19', 'Buzz']

ジェネレータ関数を一度変数に入れなければならないというのも美しくないですね。余分な操作のようにも思えます。
 
こうすれば内包表記で書けるか。

g = generate()
print([next(g) for _ in range(20)])

Ruby のブロックはオブジェクト

これ、タイトルは釣りだと思うのだけれど、一応。

Ruby のブロックはもちろんオブジェクトです。Proc オブジェクトとして持ち運ぶことができます。例えば

def hoge(&bk)
  p bk
  bk.call
end

hoge do
  puts "fuga"
end

というコードがあります(むっちゃテキトーですが)。これを実行すると

#<Proc:0x005637e7cddff0@hoge.rb:6>
fuga

などと出力されます。hoge メソッドはブロックが Proc オブジェクトであることを出力し、さらにブロックを実行していることがわかると思います。なお、bk.call はここでは yield と同等です。


なお、上のリンク先の引用内の

If I ask it to tell me its class it replies "syntax error"!

というのはどういうことなのでしょうね。

p ({puts "fuga"})

ということでしょうか*1。確かにこれは syntax error になります。でもこれ、意味がないのですけれど。

p (proc {puts "fuga"})
#=>#<Proc:0x0055dc8c132568@hoge.rb:1>

とすればいいのですから。これではイカンの? インチキ? この Proc オブジェクトはブロックとして使うことができます。実際

a = proc {puts "fuga"}
hoge(&a)

は、最初の例とまったく同じ結果をもたらすのですから*2。「&」はブロックを Proc オブジェクトに、また Proc オブジェクトをブロックに変換します。

*1:p {puts "fuga"} と書かないのは、括弧内がメソッド p のブロックとして解釈されて syntax error にならないからです。Ruby のすべてのメソッドはブロックを取ることができます。処理が定義されていない場合は無視されます。

*2:自分はやりませんが、hoge(&proc {puts "fuga"}) とだって書けます。

RubyGem 'Salamander' でヒルベルト曲線を描く

RubyGem 'Salamander' は Ruby/SDL を使ってタートルグラフックスを実装しています。これを使ってヒルベルト曲線を描いてみました。Linux Mint 18.2 と Ruby 2.3.3 で確認しています。

インストールは Bundler で入りますが($ gem install salamander でもたぶん入るでしょう)、その前に Linux MintSDL 用のライブラリを入れなくてはなりません。

$ sudo apt-get install libsdl1.2-dev libsdl-gfx1.2-dev libsdl-image1.2-dev libsdl-mixer1.2-dev libsdl-ttf2.0-dev

Ruby/SDL は何もしなくても入るようです。

5次のヒルベルト曲線です。 
20170814224406

salamander_hilbert.rb

require 'salamander'
require 'salamander/support/radians'
include Salamander
include Salamander::Drawing

Step = 15
screen = Salamander.setup(500, 500)

Actor.draw(screen) do
  def draw(depth, angle)
    return if depth <= 0
    turn  angle.degrees
    draw(depth - 1, -angle)
    line Step
    turn -angle.degrees
    draw(depth - 1,  angle)
    line Step
    draw(depth - 1,  angle)
    turn -angle.degrees
    line Step
    draw(depth - 1, -angle)
    turn  angle.degrees
  end
  
  color :red
  move_to 20, 20
  face :east
  draw(5, 90)
end
screen.redraw

loop { break if SDL.WaitEvent.type == SDL::QUIT }

 
Salamander を使うための試行錯誤は以下。
RubyGem 'Salamander' が動かないよ - Marginalia
RubyGem 'Salamander' がようやく動いたよ - Marginalia
 
ヒルベルト曲線についての過去記事は以下。
Python の Turtle でヒルベルト曲線 - Camera Obscura
GTK+でヒルベルト曲線(Ruby) - Camera Obscura
JavaScript でヒルベルト曲線を描く - Camera Obscura
Ruby/Tk でヒルベルト曲線を描く - Camera Obscura
 

Ruby のイテレーションされる配列の破壊的変更について

この Ruby コードの出力がわかりますか。

ar = [["a"],["b"]]
ar.each do |x|
  p x
  ar[1] = 1
end
p ar

 
結果はこうです。

["a"]
1
[["a"], 1]

イテレーションされる配列をブロック内で変更すると、イテレーションがおかしくなります。わかりにくいので、これを前提としたコーディングはしない方が無難かと思われます。

『Cプログラミング診断室』について

Cプログラミング診断室
おもしろいサイトを見つけました。

C言語のヒドいコード(いわゆるクソコード^^;)を眺めて、リファクタリングしようというものです。僕などのように、独学で誰もまわりにプログラミングを知っている人のいない孤独なアマチュアプログラマにはとても参考になるものです。書籍化もされている(というか、書籍の方が先なのかな?)ようですが、既に絶版のようです。クソコードか、耳が痛いな…。
 

改訂新版 Cプログラミング診断室

改訂新版 Cプログラミング診断室