Ruby で遅延ストリーム(コピペ)

お気楽Rubyプログラミング入門/番外編 遅延評価と遅延ストリーム
下はこのページのコードをまとめたもの(コピペ)。

lazystream.rb

#
# lazystream.rb : 遅延ストリーム
#
#                 Copyright (C) 2017 Makoto Hiroi
#

# 遅延評価
class Delay
  def initialize(&func)
    @func = func
    @flag = false
    @value = false
  end

  def force
    unless @flag
      @value = @func.call
      @flag = true
    end
    @value
  end
end

#
# 遅延ストリーム
#
class LazyStreamError < StandardError
end

class LazyStream
  def initialize(x, &func)
    @item = x
    @next = Delay.new &func
  end

  def inspect                                                             
    "(" + @item.to_s + ", ?)"                                             
  end                              

  # 終端
  @@NIL = LazyStream.new(nil) { nil }
  def empty?
    self == @@NIL
  end
  
  def LazyStream.empty
    @@NIL
  end
  
  # 生成
  def LazyStream.iterate(a, &func)
    LazyStream.new(a) { iterate(func.call(a), &func) }
  end

  def LazyStream.tabulate(a = 0, &func)
    LazyStream.new(func.call(a)) { tabulate(a + 1, &func) }
  end
  
  def LazyStream.iota(n, m, stepno = 1)
    return @@NIL if n > m
    LazyStream.new(n) { iota(n + stepno, m) }
  end

  def LazyStream.of(xs, n = 0)
    return @@NIL if xs.size == n
    LazyStream.new(xs[n]) { of(xs, n + 1) }
  end

  #
  # 基本操作
  #
  def first
    raise LazyStreamError, "Stream is empty" if empty?
    @item
  end

  def rest
    raise LazyStreamError, "Stream is empty" if empty?
    @next.force
  end

  def get(n)
    xs = self
    n.times {
      xs = xs.rest
    }
    xs.first
  end

  def take(n)
    xs = self
    ys = []
    n.times {
      break if xs.empty?
      ys.push xs.first
      xs = xs.rest
    }
    ys
  end
  
  def drop(n)
    xs = self
    n.times {
      break if xs.empty?      
      xs = xs.rest
    }
    xs
  end

  def zip(xs)
    return @@NIL if empty? || xs.empty?
    LazyStream.new([first, xs.first]) { rest.zip(xs.rest) }
  end

  def append(xs)
    return xs if empty?
    LazyStream.new(first) { rest.append(xs) }
  end

  def interleave(xs)
    return xs if empty?
    LazyStream.new(first) { xs.interleave(rest) }
  end  

  def lazy_append(ys)
    return ys.force if empty?
    LazyStream.new(first) { rest.lazy_append(ys) }
  end

  def each_cons(n)
    return @@NIL if empty?
    a = []
    xs = self
    n.times {
      return @@NIL if xs.empty?
      a.push xs.first
      xs = xs.rest
    }
    LazyStream.new(a) { rest.each_cons(n) }
  end

  def each_slice(n)
    return @@NIL if empty?
    a = []
    xs = self
    n.times {
      break if xs.empty?
      a.push xs.first
      xs = xs.rest
    }
    LazyStream.new(a) { xs.each_slice(n) }
  end

  # 併合
  def merge(xs)
    if empty?
      xs
    elsif xs.empty?
      self
    else
      if first <= xs.first
        LazyStream.new(first) { rest.merge(xs) }
      else
        LazyStream.new(xs.first) { merge(xs.rest) }
      end
    end
  end

  # 和集合
  def union(xs)
    if empty?
      xs
    elsif xs.empty?
      self
    else
      if first < xs.first
        LazyStream.new(first) { rest.union(xs) }
      elsif xs.first < first
        LazyStream.new(xs.first) { union(xs.rest) }
      else
        LazyStream.new(first) { rest.union(xs.rest) }
      end
    end
  end

  # 積集合
  def intersection(ys)
    xs = self
    loop {
      if xs.empty? || ys.empty?
        return @@NIL
      elsif xs.first == ys.first
        return LazyStream.new(xs.first) { xs.rest.intersection(ys.rest) }
      elsif xs.first < ys.first
        xs = xs.rest
      else
        ys = ys.rest
      end
    }
  end
  
  #
  # 高階関数
  #
  def map(&func)
    return @@NIL if empty?
    LazyStream.new(func.call(first)) { rest.map(&func) }
  end

  def flat_map(&func)
    return @@NIL if empty?
    func.call(first).lazy_append(Delay.new { rest.flat_map(&func) })
  end

  def zip_with(xs, &func)
    return @@NIL if empty? || xs.empty?
    LazyStream.new(func.call(first, xs.first)) {
      rest.zip_with(xs.rest, &func)
    }
  end

  def filter(&func)
    xs = self
    while !xs.empty?
      if func.call xs.first
        return LazyStream.new(xs.first) { xs.rest.filter(&func) }
      end
      xs = xs.rest
    end
    @@NIL
  end

  def reduce(a, &func)
    xs = self
    while !xs.empty?
      a = func.call a, xs.first
      xs = xs.rest
    end
    a
  end

  def each(&func)
    xs = self
    while !xs.empty?
      func.call xs.first
      xs = xs.rest
    end
  end
    
  def take_while(&func)
    xs = self
    ys = []
    while !xs.empty? && func.call(xs.first)
      ys.push xs.first
      xs = xs.rest
    end
    ys
  end

  def drop_while(&func)
    xs = self
    while !xs.empty? && func.call(xs.first)
      xs = xs.rest
    end
    xs
  end
end

require_relative 'lazystream_lib'


自分による追加。
lazystream_lib.rb

class LazyStream
  def dig(n)
    LazyStream.new(first[n]) {rest.dig(n)}
  end
  
  def to_a
    xs = self
    ar = []
    until xs.empty?
      ar << xs.first
      xs = xs.rest
    end
    ar
  end
  
  def length
    return 0 if empty?
    1 + rest.length
  end
  
  def inspect
    return "//" if empty?
    first.inspect + " " + rest.inspect
  end
end

 

FizzBuzz

require_relative 'lazystream'

def change(n)
  return "FizzBuzz" if (n % 15).zero?
  return "Fizz"     if (n %  3).zero?
  return "Buzz"     if (n %  5).zero?
  n.to_s
end

p LazyStream.iota(1, 50).map {|x| change(x)}.to_a
#=>["1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz", "11", "Fizz", "13",
# "14", "FizzBuzz", "16", "17", "Fizz", "19", "Buzz", "Fizz", "22", "23", "Fizz", "Buzz",
# "26", "Fizz", "28", "29", "FizzBuzz", "31", "32", "Fizz", "34", "Buzz", "Fizz", "37", "38",
# "Fizz", "Buzz", "41", "Fizz", "43", "44", "FizzBuzz", "46", "47", "Fizz", "49", "Buzz"]

あるいは

p LazyStream.iterate(1) {|x| x + 1}.map {|x| change(x)}.take(50)

あるいは

p LazyStream.tabulate(1) {|x| change(x)}.take(50)

 

フィボナッチ数列

def fibo(a, b)
  LazyStream.new(a) {fibo(b, a + b)}
end

fibos = fibo(1, 1)
result = []
20.times do
  result << fibos.first
  fibos = fibos.rest
end
p result
#=>[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]

あるいは

fibos = LazyStream.iterate([1, 1]) {|a, b| [b, a + b]}
p fibos.dig(0).take(20)
#=>[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]

あるいは

def add_stream(xs, ys)
  xs.zip_with(ys) {|x, y| x + y}
end

fibo = LazyStream.new(1) do
  LazyStream.new(1) {add_stream(fibo.rest, fibo)}
end

p fibo.take(20)
#=>[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]

 

エラトステネスの篩

def sieve(xs)
  p = xs.first
  LazyStream.new(p) { sieve(xs.rest.filter {|x| x % p != 0}) }
end

ps = sieve(LazyStream.iterate(2) {|x| x + 1})
p ps.take(25)
#=>[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

 

ニュートン-ラプソン法による平方根

なぜ関数プログラミングは重要か
「なぜ関数プログラミングは重要か」解読(Ruby) - Camera Obscura

a0 = 1.0
eps = 0.001

repeat = ->(n) { LazyStream.iterate(a0) {|x| (x + n / x) / 2} }

within = ->(ls) do
  a = ls.first
  b = ls.rest.first
  return b if (a - b).abs <= eps
  within[ls.rest]
end

p within[repeat[2.0]]    #=>1.4142135623746899

relative = ->(ls) do
  a = ls.first
  b = ls.rest.first
  return b if (a - b).abs <= eps * b.abs
  relative[ls.rest]
end

p relative[repeat[3.0]]  #=>1.7320508100147274

 

いろいろ

a = LazyStream.iota(1, 5)
b = LazyStream.iota(8, 12)
p a.length
p a.append(b)

p LazyStream.of([1, "2", :a])

def factorial(n)
  LazyStream.iota(1, n).reduce(1) {|r, i| r * i}
end

p factorial(10)

def dice
  LazyStream.tabulate {rand(1..6)}
end

p dice.take(20)

#=>
5
1 2 3 4 5 8 9 10 11 12 //
1 "2" :a //
3628800
[5, 1, 6, 5, 3, 1, 6, 4, 1, 6, 5, 5, 4, 2, 6, 2, 5, 4, 4, 2]