object.to_enum(method, *args) => Enumerator
with_index(offset=0) {|(*args), idx| ... } => object
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]
1. Enumerator.new(object, method, *args) => Enumerator 2. Enumerator.new(size=nil) {|y| ... } => Enumerator
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
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]
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(実部, 虚部 = 0) Complex.rect(実部, 虚部 = 0) Complex.rectangular(実部, 虚部 = 0)
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)
Complex.polar(絶対値, 偏角)
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
1. Struct.new("Class_name", name1, name2, ...) => Struct::Class_name 2. Class_name = Struct.new(name1, name2, ...)
1. Struct::Class_name.new(...) => object 2. Class_name.new(...) => object
irb> Foo = Struct.new(:a, :b) => Foo irb> x = Foo.new(1, 2) => #<struct Foo a=1, b=2> irb> x.a => 1 irb> x[:a] => 1 irb> x["a"] => 1 irb> x.b => 2 irb> x.b = 20 => 20 irb> x[:b] => 20 irb> x["b"] = 200 => 200 irb> x.b => 200 irb> y = Foo.new(10) => #<struct Foo a=10, b=nil> irb> y = Foo.new(10, 20, 30) => エラー (ArgumentError) irb> Foo.new(1, 2) == Foo.new(1, 2) => true irb> Foo.new(1, 2) == Foo.new(1, 3) => false irb> Foo === Foo.new(10, 20) => true irb> Foo.new(10, 20).instance_of?(Foo) => true
Class_name = Struct.new(...) do # メソッドの定義など ... end
irb> Cons = Struct.new(:car, :cdr) do irb> def deconstruct() = self.to_a irb> end => Cons irb> x = Cons.new(1, nil) => #<struct Cons car=1, cdr=nil> irb> y = Cons.new(2, x) => #<struct Cons car=2, cdr=#<struct Cons car=1, cdr=nil>> irb> z = Cons.new(3, y) => #<struct Cons car=3, cdr=#<struct Cons car=2, cdr=#<struct Cons car=1, cdr=nil>>> irb> case z irb> in [a, d] irb> p a irb> p d irb> end 3 #<struct Cons car=2, cdr=#<struct Cons car=1, cdr=nil>> => #<struct Cons car=2, cdr=#<struct Cons car=1, cdr=nil>> irb> case z irb> in [a, [b, [c, _]]] irb> p a irb> p b irb> p c irb> end 3 2 1 => 1
「パターンマッチ (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
ところで、パターン [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( ... ) となることに注意してください。