M.Hiroi's Home Page

お気楽 Ruby プログラミング入門

付録

[ PrevPage | R u b y | NextPage ]

Enumerator

irb> class Foo
irb>   def each
irb>     yield 1
irb>     yield 2
irb>     yield 3
irb>   end
irb> end
=> :each
irb> a = Foo.new
=> #<Foo: ... >
irb> a.map {|x| x * x}
(irb):?:in `<main>': undefined method `map' for #<Foo:...> (NoMethodError)
・・・省略・・・

irb> e = a.to_enum
=> #<Enumerator: ...>
irb> e.map {|x| x * x}
=> [1, 4, 9]
irb> e.next
=> 1
irb> e.next
=> 2
irb> e.next
=> 3
irb> e.next
(irb):?:in `next': iteration reached an end (StopIteration)
・・・省略・・・

irb> e.rewind
=> #<Enumerator: ...>
irb> e.peek
=> 1
irb> e.peek
=> 1
irb> e.peek_values
=> [1]
irb> e.next_values
=> [1]
irb> e.next_values
=> [2]
irb> [1,2,3,4,5].to_enum.with_index{|k, i| print i, " ", k, "\n"}
0 1
1 2
2 3
3 4
4 5
=> [1, 2, 3, 4, 5]
irb> [1,2,3,4,5].to_enum.with_index(1){|k, i| print i, " ", k, "\n"} 
1 1
2 2
3 3
4 4
5 5
=> [1, 2, 3, 4, 5]
irb> [1,2,3,4,5].each_with_index{|k, i| print i, " ", k, "\n"}
0 1
1 2
2 3
3 4
4 5
=> [1, 2, 3, 4, 5]
irb> ints = Enumerator.new(Float::INFINITY) {|y|
irb>   n = 1
irb>   loop {
irb>     y << n
irb>     n += 1
irb>   }
irb> }
=> #<Enumerator: ...>
irb> ints.next
=> 1
irb> ints.next
=> 2
irb> ints.next
=> 3
irb> ints.next
=> 4
irb> ints.next
=> 5
irb> ints.rewind
=> #<Enumerator: ...>
irb> ints.next
=> 1
irb> ints.next
=> 2
irb> ints.next
=> 3
irb> ints.size
=> Infinity

●参考 URL


Enumerator::Lazy

irb> intslazy = ints.lazy
=> #<Enumerator::Lazy: ...>
irb> intslazy.next
=> 1
irb> intslazy.next
=> 2
irb> intslazy.next
=> 3
irb> ints.lazy.drop(100)
=> #<Enumerator::Lazy: ...>
irb> ints.lazy.drop(100).first
=> 101
irb> ints.lazy.drop(100).first(10)
=> [101, 102, 103, 104, 105, 106, 107, 108, 109, 110]
irb> ints.lazy.map {|x| x * 2}
=> #<Enumerator::Lazy: ...>
irb> ints.lazy.map {|x| x * 2}.take(10).force
=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
irb> ints.lazy.map {|x| x * 2}.first(10)
=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

●簡単な例題

リスト : 無限数列の生成

def iterate(a, &func)
  Enumerator.new(Float::INFINITY) {|y|
    n = a
    loop {
      y << n
      n = func.call(n)
    }
  }.lazy
end

def tabulate(a = 0, &func)
  Enumerator.new(Float::INFINITY) {|y|
    n = a
    loop {
      y << func.call(n)
      n += 1
    }
  }.lazy
end
irb> iterate(1, &:itself).first(10)
=> [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
irb> iterate(2, &:itself).first(10)
=> [2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
irb> iterate(1){|x| x + 1}.first(10)
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
irb> iterate(1){|x| x + 2}.first(10)
=> [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
irb> iterate([0, 1]){|x, y| [y, y + x]}.first(10)
=> [[0, 1], [1, 1], [1, 2], [2, 3], [3, 5], [5, 8], [8, 13], [13, 21], [21, 34], [34, 55]]
irb> iterate([0, 1]){|x, y| [y, y + x]}.map{|x| x[0]}.first(10)    # フィボナッチ数
=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
irb> iterate([2, 1]){|x, y| [y, y + x]}.map{|x| x[0]}.first(10)    # リュカ数
=> [2, 1, 3, 4, 7, 11, 18, 29, 47, 76]
irb> iterate([0, 0, 1]){|x, y, z| [y, z, z + y + x]}.map{|x| x[0]}.first(20)  # トリボナッチ数
=> [0, 0, 1, 1, 2, 4, 7, 13, 24, 44, 81, 149, 274, 504, 927, 1705, 3136, 5768, 10609, 19513]

irb> tabulate(&:itself).first(10)
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
irb> tabulate(10, &:itself).first(10)
=> [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
irb> tabulate(1){|x| x * (x + 1) / 2}.first(10)     # 三角数
=> [1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
irb> tabulate(1){|x| x * x}.first(10)               # 四角数
=> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
irb> tabulate(1){|x| x * (3 * x - 1) / 2}.first(10) # 五角数
=> [1, 5, 12, 22, 35, 51, 70, 92, 117, 145]

irb> def to_fizzbuzz(n)
irb>   if n % 15 == 0
irb>     "FizzBuzz"
irb>   elsif n % 3 == 0
irb>     "Fizz"
irb>   elsif n % 5 == 0
irb>     "Buzz"
irb>   else
irb>     n.to_s
irb>   end
irb> end
=> :to_fizzbuzz
irb> tabulate(1){|x| to_fizzbuzz(x)}.first(100)
=> ["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", "Fizz", "52", "53", "Fizz", "Buzz", "56", "Fizz", "58", "59", 
"FizzBuzz", "61", "62", "Fizz", "64", "Buzz", "Fizz", "67", "68", "Fizz", "Buzz", 
"71", "Fizz", "73", "74", "FizzBuzz", "76", "77", "Fizz", "79", "Buzz", "Fizz", "82", 
"83", "Fizz", "Buzz", "86", "Fizz", "88", "89", "FizzBuzz", "91", "92", "Fizz", "94", 
"Buzz", "Fizz", "97", "98", "Fizz", "Buzz"]

irb> def newton(n)
irb>   iterate(n) {|x| (x + n / x) / 2.0}
irb> end
=> :newton
irb> newton(2).first(10)
=> [2, 1.5, 1.4166666666666665, 1.4142156862745097, 1.4142135623746899, 
1.414213562373095, 1.414213562373095, 1.414213562373095, 1.414213562373095, 
1.414213562373095]
irb> newton(3).first(10)
=> [3, 2.0, 1.75, 1.7321428571428572, 1.7320508100147274, 1.7320508075688772, 
1.7320508075688772, 1.7320508075688772, 1.7320508075688772, 1.7320508075688772]
リスト : 素数列 (myprime.rb)

class Myprime
  @@primes = [2, 3, 5]    # 求めた素数を格納

  # 次の素数を求める
  def Myprime.next_prime(n)
    while true
      for p in @@primes
        return n if p * p > n
        break if n % p == 0
      end
      n += 2
    end
  end

  # 素数列の生成
  def Myprime.make
    Enumerator.new(Float::INFINITY) {|y|
      n = 0
      while true
        if n == @@primes.size
          @@primes.push Myprime.next_prime(@@primes[-1] + 2)
        end
        y << @@primes[n]
        n += 1
      end
    }.lazy
  end
end
irb> load "myprime.rb"
=> true
irb> ps = Myprime.make
=> #<Enumerator::Lazy: ...>
irb> ps.first(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]
irb> ps.first(100)
=> [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, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 
173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 
269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 
373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 
467, 479, 487, 491, 499, 503, 509, 521, 523, 541]
irb> ps.drop(1000).first(10)
=> [7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 8017]

●参考 URL


Fiber

irb(main)> co = Fiber.new {
irb(main)>   for x in 1 .. 4
irb(main)>     Fiber.yield x
irb(main)>   end
irb(main)>   5
irb(main)> }
=> #<Fiber:0x000055e61811b3f0 (irb):? (created)>
irb> co.resume
=> 1
irb> co.resume
=> 2
irb> co.resume
=> 3
irb> co.resume
=> 4
irb> co.resume
=> 5
irb> co.resume
(irb):?:in `resume': attempt to resume a terminated fiber (FiberError)
・・・省略・・・

●簡単な例題

リスト : 複数のコルーチン

def print_code(code)
  loop {
    print code
    Fiber.yield true
  }
end

def test_a(n)
  xs = ["h", "e", "y", "!", " "].map {|x| Fiber.new {print_code(x)}}
  n.times {
    xs.each {|co| co.resume}
  }
end
irb> test_a(5)
hey! hey! hey! hey! hey! => 5
irb> test_a(10)
hey! hey! hey! hey! hey! hey! hey! hey! hey! hey! => 10
リスト : 順列の生成

def perm_sub(xs, n)
  if xs.size == n
    Fiber.yield []
  else
    co = Fiber.new { perm_sub(xs, n + 1) }
    while ys = co.resume
      for x in xs
        if not ys.member? x
          Fiber.yield(ys + [x])
        end
      end
    end
  end
end

def permutation(xs)
  Fiber.new { perm_sub(xs, 0) }
end
irb> co = permutation([1,2,3])
=> #<Fiber:0x0000563306296ac8 sample_fiber.rb:? (created)>
irb> while xs = co.resume
irb>   print xs, "\n"
irb> end
[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]
=> nil
リスト : エラトステネスの篩

# n から始まる整数列
def integers(n)
  Fiber.new {
    loop {
      Fiber.yield n
      n += 1
    }
  }
end

# フィルター
def filter(src)
  Fiber.new {
    while m = src.resume
      Fiber.yield(m) if yield(m)
    end
  }
end

def sieve(x)
  nums = integers(2)
  x.times {
    n = nums.resume
    print n, " "
    nums = filter(nums) {|x| x % n != 0}
  }
end
irb> sieve 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 => 25
irb> sieve 100
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 101 103 107 
109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 
229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 
353 359 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 
479 487 491 499 503 509 521 523 541 => 100

Rational

irb> Rational(1, 3)
=> (1/3)
irb> Rational("1/3")
=> (1/3)
irb> Rational(0.1)
=> (3602879701896397/36028797018963968)
irb> Rational("0.1")
=> (1/10)
irb> Rational(1e-3)
=> (1152921504606847/1152921504606846976)
irb> Rational("1e-3")
=> (1/1000)
irb> a = Rational(1, 2)
=> (1/2)
irb> b = Rational(1, 3)
=> (1/3)
irb> a + b
=> (5/6)
irb> a - b
=> (1/6)
irb> b - a
=> (-1/6)
irb> a * b
=> (1/6)
irb> a / b
=> (3/2)
irb> b / a
=> (2/3)
irb> a ** 2
=> (1/4)
irb> a ** 10
=> (1/1024)
irb> 2 ** a
=> 1.4142135623730951
irb> 8 ** b
=> 2.0
irb> a < b
=> false
irb> a > b
=> true
irb> a == b
=> false
irb> a != b
=> true
irb> a == a
=> true
irb> a <=> b
=> 1
irb> b <=> a
=> -1
irb> b <=> b
=> 0

Complex

irb> Complex(1, 2)
=> (1+2i)
irb> Complex.rect(3, 4)
=> (3+4i)
irb> Complex(1)
=> (1+0i)
irb> Complex("1+2i")
=> (1+2i)
irb> Complex("1.0+2.0i")
=> (1.0+2.0i)
irb> Complex::I
=> (0+1i)
irb> a = Complex(1.2, 3.4)
=> (1.2+3.4i)
irb> a.real
=> 1.2
irb> a.imag
=> 3.4
irb> a.conjugate
=> (1.2-3.4i)
irb> a.conj
=> (1.2-3.4i)
irb> b = Complex(1, 1)
=> (1+1i)
irb> b.abs
=> 1.4142135623730951
irb> b.arg
=> 0.7853981633974483
irb> Complex.polar(b.abs, b.arg)
=> (1.0000000000000002+1.0i)
irb> Complex(1.0, 0.0).arg
=> 0.0
irb> Complex(1.0, 1.0).arg
=> 0.7853981633974483
irb> Complex(0.0, 1.0).arg
=> 1.5707963267948966
irb> Complex(-1.0, 1.0).arg
=> 2.356194490192345
irb> Complex(-1.0, 0.0).arg
=> 3.141592653589793
irb> Complex(1.0, -1.0).arg
=> -0.7853981633974483
irb> Complex(0.0, -1.0).arg
=> -1.5707963267948966
irb> Complex(-1.0, -1.0).arg
=> -2.356194490192345
irb> Complex(-1.0, -0.0).arg
=> -3.141592653589793
irb> 1.0.arg
=> 0
irb> -1.0.arg
=> 3.141592653589793
irb> 0.0.arg
=> 0
irb> -0.0.arg
=> 3.141592653589793
irb> a = Complex(1.0, 2.0)
=> (1.0+2.0i)
irb> b = Complex(3.0, 4.0)
=> (3.0+4.0i)
irb> a + b
=> (4.0+6.0i)
irb> a - b
=> (-2.0-2.0i)
irb> a * b
=> (-5.0+10.0i)
irb> a / b
=> (0.44+0.08i)
irb> a ** 2
=> (-3.0+4.0i)
irb> a * a
=> (-3.0+4.0i)
irb> a ** 3
=> (-11.0-2.0i)
irb> a * a * a
=> (-11.0-2.0i)
irb> a == a
=> true
irb> a == b
=> false
irb> a != b
=> true
irb> Float::INFINITY
=> Infinity
irb> Float::NAN
=> NaN
irb> a = Complex(1e300, 1e300)
=> (1.0e+300+1.0e+300i)
irb> a.finite?
=> true
irb> a.infinite?
=> nil
irb> b = a * a
=> (NaN+Infinity*i)
irb> b.finite?
=> false
irb> b.infinite?
=> 1
irb> b.real.nan?
=> true
irb> b.real.nan?
=> true
irb> b.imag.infinite?
=> 1

整数のビット演算

irb> printf "%04b\n", 0b0101 & 0b0011
0001
=> nil
irb> printf "%04b\n", 0b0101 | 0b0011
0111
=> nil
irb> printf "%04b\n", 0b0101 ^ 0b0011
0110
=> nil
irb> ~1
=> -2
irb> ~0
=> -1
irb> 1 << 8
=> 256
irb> 1 << 16
=> 65536
irb> 256 >> 8
=> 1
irb> 65536 >> 8
=> 256
irb> -256 >> 8
=> -1
irb> for i in 0..5
irb>   print 0b010101[i], " "
irb> end
1 0 1 0 1 0 => 0..5
irb> printf "%b\n", 0b11001101[3, 1]
1
=> nil
irb> printf "%b\n", 0b11001101[3...7]
1001
=> nil
irb> printf "%b\n", 0b11001101[3..7]
11001
=> nil
irb> 0b11001101.allbits?(0b1101)
=> true
irb> 0b11001100.allbits?(0b1101)
=> false
irb> 0b11001101.anybits?(0b1100)
=> true
irb> 0b11000000.anybits?(0b1100)
=> false
irb> 0x100.bit_length
=> 9
irb> 0x800.bit_length
=> 12
irb> 0xffff.bit_length
=> 16
irb> 0.bit_length
=> 0

初版 2017 年 1 月 22 日
改訂 2023 年 1 月 28 日

構造体


パターンマッチ

「パターンマッチ (pattern matching)」は Ruby 2.7 から導入された機能です。Ruby のパターンマッチは関数型言語のパターンマッチングと同様の機能です。パターンマッチを使って条件分岐を行ったり、配列やハッシュなどのデータ構造を分解し、それらの要素を取り出すことができます。

●パターンマッチの基本

Ruby では case 文 (case / in) でパターンマッチを行います。パターンマッチの基本的な構文を以下に示します。

case expr
in pattern_1
  処理_1
  ...
in pattern_2
  処理_2
  ...
else
  処理_z
end

パターンマッチの動作は case / when と良く似ています。case は最初に式 expr を評価します。そのあと複数の in 節が続きます。in の後ろにはパターンを指定します。パターンはあとで説明します。そして、expr の評価結果とパターンを照合します。照合に成功 (マッチ) した場合、その in 節の処理を順番に実行します。照合に失敗した場合は、次の in 節をチェックします。

たとえば、pattern_1 とマッチしなかった場合、次の節に移り pattern_2 と照合します。一度 in 節が選択されたら、それ以降の in 節は実行されません。case は最後に実行した処理の結果を返します。もしも、どのパターンともマッチしなかった場合は else 節が実行されます。なお、else 節は省略することができますが、マッチングするパターンが見つからない場合は例外 (NoMatchingPatternError) が送出されます。ご注意くださいませ。

それでは簡単な例を示しましょう。パターンマッチを使って階乗を求める関数 fact() を定義すると次のようになります。

リスト : 階乗

# パターンマッチング未使用
def fact(n)
  if n == 0
    1
  else
    n * fact(n - 1)
  end
end

# パターンマッチング
def fact(n)
  case n
  in 0
    1
  in m
    m * fact(m - 1)
  end
end

数値や文字列などのリテラル、クラス名などの定数は、それ自身がパターンになります。この場合、式の評価結果とパターンを演算子 === で比較して、等しければその節とマッチします。これは case / when の動作とほとんど同じです。

最初の節はパターンが 0 なので、引数 n が 0 の場合にマッチします。これは if n == 0 と同じ処理です。パターンが変数 (局所変数) の場合はどんな値とでもマッチします。そして、その値が変数に代入されます。したがって、n が 0 以外の場合は 2 番目のパターンとマッチし、変数 m の値は n になります。ここで再帰呼び出しが行われます。

変数 m は n と同じ値なので、パターンにワイルドカード (_) を使う、もしくは else 節を使って定義してもかまいません。

リスト : 別解

def fact1(n) 
  case n
  in 0
    1
  in _
    n * fact1(n - 1)
  end
end

def fact2(n)
  case n
  in 0
    1
  else
    n * fact2(n - 1)
  end
end

なお、パターンマッチを使うときは、節を定義する順番に気をつけてください。fact() の場合、最初にパターン m を定義すると、引数が 0 の場合でもマッチするので、パターン 0 の節が実行されることはありません。特定のパターンから定義するように注意してください。

case / when は when の後ろに複数の値を , で区切って指定することができました。case / in でも in の後ろに複数のパターンを | で区切って指定することができます。複数指定したパターンのどれかとマッチすれば、その節の処理が実行されます。簡単な例として、フィボナッチ関数 fibo() を定義してみましょう。

リスト : フィボナッチ関数 (二重再帰)

def fibo(n)
  case n
  in 0 | 1
    n
  else
    fibo(n - 1) + fibo(n - 2)
  end
end

最初の in 節の 0 | 1 は、if n == 0 || n == 1 と同じ動作になります。n が 0 または 1 であれば n を返します。そうでなければ else 節で fibo() を再帰呼び出しします。fibo() は二重再帰なので、実行時間はとても遅くなります。

●演算子 === の動作

演算子 === の標準動作は演算子 == と同じです。Ruby の演算子はメソッドなので、'左辺 === 右辺' はメソッド呼び出し '左辺.===(右辺)' と同じです。左辺のオブジェクトによって === の動作を変更することが可能です。たとえば、左辺が範囲オブジェクトの場合、右辺の数値が範囲内にあれば true を返します。

irb> (1..10) === 1
=> true
irb> (1..10) === 11
=> false
irb> (1..10) === 1.5
=> true
irb> (1..10) === 0.5
=> false

左辺がクラスの場合、右辺のオブジェクトがそのクラスまたはサブクラスのインスタンスであれば true を返します。

irb> Integer === 123
=> true
irb> Integer === 123.4
=> false
irb> Numeric === 123.4
=> true
irb> Numeric === 123
=> true
irb> String === "abc"
=> true
irb> String === 123
=> false

範囲オブジェクトやクラスは case / when や case / in でも使用することができます。

irb> case 123
irb> in Integer
irb>   "integer"
irb> else
irb>   "other"
irb> end
=> "integer"
irb> case "abc"
irb> in Integer
irb>   "integer"
irb> else
irb>   "other"
irb> end
=> "other"
irb> case 5
irb> in (1..10)
irb>   "ok"
irb> else
irb>   "ng"
irb> end
=> "ok"
irb> case 15
irb> in (1..10)
irb>   "ok"
irb> else
irb>   "ng"
irb> end
=> "ng"

●配列のパターンマッチ

配列もパターンとマッチングすることができます。配列のパターンは角カッコ [ ... ] を使って表します。基本的な動作は、配列の要素とパターンの要素を照合し、それらがすべてマッチしたとき、配列とパターンはマッチしたと判定されます。簡単な例を示します。

(1) [x]         要素が 1 つの配列とマッチ
(2) [x, y]      要素が 2 つの配列とマッチ
(3) [x, *y]     要素が 1 つ以上ある配列とマッチ
    [*x, y]     末尾の要素が y とマッチ、それ以外の要素は x にマッチ
(4) [x, y, *z]  要素が 2 つ以上ある配列とマッチ
    [*x, y, z]  末尾から 2 つの要素が y と z にマッチ、それ以外の要素は x にマッチ
(5) [x, y, x]   エラー

(1), (2) は簡単ですね。パターンの要素は変数、定数、リテラルだけではありません。あとから説明しますが、配列のパターンやハッシュのパターンでもかまいません。(3), (4) の * は可変個引数を表す * と似ていて、複数の要素とマッチします。(3) の場合、先頭要素以外の要素は、配列に格納されて変数 y にセットされます。(4) は 2 つの要素を変数に取り出し、それ以外の要素をもう一つの変数に取り出します。* の後ろの変数名を省略すると、マッチした要素は捨てられます。

簡単な例を示しましょう。

irb> case [1]
irb>   in [x]
irb>     p x
irb> end
1
=> 1
irb> case [1, 2]
irb>   in [x]
irb>     p x
irb> end
=> エラー (NoMatchingPatternError)

irb> case [1, 2]
irb>   in [x, y]
irb>     p x
irb>     p y
irb> end
1
2
=> 2
irb> case [1, 2, 3, 4, 5]
irb>   in [x, *y]
irb>     p x
irb>     p y
irb> end
1
[2, 3, 4, 5]
=> [2, 3, 4, 5]
irb> case [1, 2, 3, 4, 5]
irb>   in [*x, y]
irb>     p x
irb>     p y
irb> end
[1, 2, 3, 4]
5
=> 5
irb> case [1, 2, 3, 4, 5]
irb>   in [*, y]
irb>     p y
irb> end
5
=> 5
irb> case [1, 2, 3, 4, 5]
irb>   in [x, *y, z]
irb>     p x
irb>     p y
irb>     p z
irb> end
1
[2, 3, 4]
5
=> 5

●ガード節

(5) のように、パターンの中に同名の変数を使うことはできません。この場合、[x, y, z] とマッチングさせてから x と z が等しいかチェックします。このような場合、次のようにパターンの後ろに条件式を指定することができます。

case 式
in パターン if 条件式
  処理
  ...
end

このようなマッチング節を「ガード付き節」とか「ガード節」といいます。パターンとの照合に成功して、かつ if の条件式が真を返す場合に限り処理が実行されます。if のかわりに unless を指定することもできます。その場合は、条件式が偽を返す場合に処理が実行されます。

簡単な例を示しましょう。

irb> case [1, 2, 1]
irb>   in [x, y, z] if x == z
irb>     p x
irb>     p y
irb>     p z
irb> end
1
2
1
=> 1
irb> case [1, 2, 3]
irb>   in [x, y, z] if x == z
irb>     p x
irb>     p y
irb>     p z
irb> end
=> エラー (NoMatchingPatternError)

●配列パターンの入れ子

配列のパターンを入れ子にすると、入れ子の配列とマッチさせることができます。簡単な例を示しましょう。

irb> case [[1, 2], [3, 4]]
irb>   in [[a, b], [c, d]]
irb>     p a
irb>     p b
irb>     p c
irb>     p d
irb> end
1
2
3
4
=> 4
irb> case [1, [2, [3], 4], 5]
irb>   in [a, [b, [c], d], e]
irb>     p a
irb>     p b
irb>     p c
irb>     p d
irb>     p e
irb> end
1
2
3
4
5
=> 5

●As pattern

ところで、パターン [a, b, c, d] を使うと配列から個々の要素を取り出すことができますが、要素だけではなく元の配列を参照したいときがあります。このような場合、=> を使うと変数とパターンを同時に設定することができます。

パターン => 変数名

関数型言語では => のかわりに as を使うことがあります。このため、これを As pattern と呼ぶようです。簡単な例を示しましょう。

irb> case [1, 2, 3, 4]
irb>   in [a, b, c, d] => e
irb>     p a
irb>     p b
irb>     p c
irb>     p d
irb>     p e
irb> end
1
2
3
4
[1, 2, 3, 4]
=> [1, 2, 3, 4]
irb> case [[1, 2], [3, 4]]
irb>   in [[a, b], [c, d] => e]
irb>     p a
irb>     p b
irb>     p c
irb>     p d
irb>     p e
irb> end
1
2
3
4
[3, 4]
=> [3, 4]

このように、変数 e の値は分解する前の配列の値 [1, 2, 3, 4] や [3, 4] になります。

●ハッシュのパターンマッチ

ハッシュもパターンとマッチングすることができます。ハッシュのパターンは波カッコ { ... } を使って表します。

{key1: pattern1, key2: pattern2, ..., **items}

照合するハッシュにキー key が存在し、その値とパターン pattern を照合します。パターンの要素がすべてマッチしたとき、ハッシュとパターンはマッチしたと判定されます。**items の ** は関数のキーワード引数と似ています。マッチした要素を除いた残りの要素をハッシュに格納し、それを変数 items にセットします。

pattern を省略して key だけを指定すると、キーに対応する値が変数 key にセットされます、つまり key: key と同じ動作になります。それから、パターン先頭の { と末尾の } は省略することができます。なお、ハッシュのパターンマッチで使用できるキーはシンボルだけです。ご注意くださいませ。

簡単な例を示します。

irb> case {foo: 10, bar: 20, baz: 30}
irb>   in {foo: a, baz: b}
irb>     p a
irb>     p b
irb> end
10
30
=> 30
irb> case {foo: 10, bar: 20, baz: 30}
irb>   in {foo:, **d}
irb>     p foo
irb>     p d
irb> end
10
{:bar=>20, :baz=>30}
=> {:bar=>20, :baz=>30}

もちろん、ハッシュのパターンも入れ子にすることができます。そして、配列のパターンにハッシュのパターンを入れる、逆に、ハッシュのパターンに配列のパターンを入れることもできます。簡単な例を示しましょう。

irb> d = {name: "Alice", data: {height: 150.0, rank: "a"}}
=> {:name=>"Alice", :data=>{:height=>150.0, :rank=>"a"}}
irb> case d
irb>   in {name:, data: {height:, rank:}}
irb>     p name
irb>     p height
irb>     p rank
irb> end
"Alice"
150.0
"a"
=> "a"
irb> d1 = {name: "Alice", data: [150.0, "a"]}
=> {:name=>"Alice", :data=>[150.0, "a"]}
irb> case d1
irb>   in {name:, data: [height, rank]}
irb>     p name
irb>     p height
irb>     p rank
irb> end
"Alice"
150.0
"a"
=> "a"

●クラスでパターンマッチを行う

クラスでパターンマッチを利用したい場合はメソッド deconstruct() または deconstruct_keys() を定義します。

deconstruct() => 配列
deconstruct_keys(keys) => ハッシュ

deconstruct() は配列のパターンマッチで、deconstruct_keys(keys) はハッシュのパターンマッチで使用します。引数の keys はパターンマッチで必要となるキーを格納した配列です。nil の場合はすべてのキーと値を格納したハッシュを返します。簡単な例を示しましょう。

irb> class Foo
irb>   def initialize(a, b, c)
irb>     @a = a
irb>     @b = b
irb>     @c = c
irb>   end
irb> end
=> :initialize
irb> class Foo
irb>   def deconstruct() = [@a, @b, @c]
irb> end
=> :deconstruct
irb> case Foo.new(1, 2, 3)
irb>   in [a, b, c]
irb>     printf "%d %d %d\n", a, b, c
irb> end
1 2 3
=> nil
irb> case Foo.new(1, 2, 3)
irb> in Foo[a, b, c]
irb>     printf "%d %d %d\n", a, b, c
irb> end
1 2 3
=> nil

irb> class Foo
irb>   def deconstruct_keys(keys) = {a: @a, b: @b, c: @c}
irb> end
=> :deconstruct_keys
irb> case Foo.new(1, 2, 3)
irb> in {a:, b:, c:}
irb>     printf "%d %d %d\n", a, b, c
irb> end
1 2 3
=> nil
irb> case Foo.new(1, 2, 3)
irb> in Foo(a:, b:, c:)
irb>     printf "%d %d %d\n", a, b, c
irb> end
1 2 3
=> nil

型 (クラス) をチェックしたい場合は、パターンの前にその名前を指定します。配列パターンの場合は Foo[ ... ] になりますが、ハッシュパターンは Foo{ ... } はなく Foo( ... ) となることに注意してください。


Copyright (C) 2017-2023 Makoto Hiroi
All rights reserved.

[ PrevPage | R u b y | NextPage ]