ひがきの日記

2013-02-09

解答例 ─ Ruby初級者向けレッスン 44回

第56回 Ruby/Rails勉強会@関西 での Ruby初級者向けレッスン 44回 の解答例 *1

演習問題 1

0 から 9 までの数値をもつ配列 a がある。
a = (0..9).to_a
a # => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  1. 各要素を順番に表示しよう。
  2. 各要素を 2倍した値を持つ配列を作ろう。
  3. 全要素の合計値を計算しよう。
  • スライドのおさらい。
  • 改めてやってみると、意外とできないかも知れないよ。
# -*- coding: utf-8; -*-

# 0 から 9 までの数値をもつ配列 a がある。
a = (0..9).to_a         # => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


# 各要素を順番に表示しよう
a.each{|i| puts i}

# >> 0
# >> 1
# >> 2
# >> 3
# >> 4
# >> 5
# >> 6
# >> 7
# >> 8
# >> 9


# 各要素を 2倍した値を持つ配列を作ろう
a.map{|i| i * 2}  # => [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]


# 全要素の合計値を計算しよう
a.inject{|s, i| s + i}      # => 45

## マニアが書くと
a.inject(:+)                # => 45

演習問題 2

0 から 9 までの数値をもつ配列 a がある。
  • パズルだと思って、いろいろやってみよう。
# -*- coding: utf-8; -*-

# 0 から 9 までの数値をもつ配列 a がある。
a = (0..9).to_a       # => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


# 奇数の要素だけを持つ配列を作ろう
a.select{|i| i.odd?}            # => [1, 3, 5, 7, 9]


# ただし odd? メソッドは使用禁止


# 2 で割った余りが 1 なら奇数
a.select{|i| i % 2 == 1}        # => [1, 3, 5, 7, 9]


# 偶数でなければ、それは奇数
a.select{|i| !i.even?}          # => [1, 3, 5, 7, 9]


# rubyist は true/false の否定を嫌う
# reject は式が真の要素を捨て去る
a.reject{|i| i.even?}           # => [1, 3, 5, 7, 9]

## マニアが書くと
a.reject(&:even?)               # => [1, 3, 5, 7, 9]

マニアのコードに出てきた & は、スライドに登場した、ブロックの受け渡しに使う & とは少し違う。*2


演習問題 3

Enumerable#map を自作してみよう。
module Enumerable
  def my_map
    ……
  end
end
ただしEnumerable#map とEnumerable#map! は使用禁止。

会場には「mapが使えないなら collectを使えばいいじゃない」と言う人がいた。
こういうことですね。わかります。*3

module Enumerable
  def my_map &block
    collect(&block)
  end
end

a = [*0..4]             # => [0, 1, 2, 3, 4]

a.map{|i| i * 2}        # => [0, 2, 4, 6, 8]
a.my_map{|i| i * 2}     # => [0, 2, 4, 6, 8]
a.map                   # => #<Enumerator: [0, 1, 2, 3, 4]:map>
a.my_map                # => #<Enumerator: [0, 1, 2, 3, 4]:collect>

惜しい。
多くのブロック付きメソッドはブロックを渡さないと Enumerator を返す。
my_map の方は #<Enumerator: ... :collect> になっちゃってる。

# -*- coding: utf-8; -*-

# Enumerable#map を自作してみよう
# ただし Enumerable#map と Enumerable#map! は使用禁止

module Enumerable
  def my_map
    unless block_given?
      # ブロックがもらえなかったら Enumerator を返す
      to_enum __callee__
    else
      # 空の Array を用意し、
      inject([]) do |result, item|
        # ブロックの評価結果を追加する
        result << yield(item)
      end
    end
  end
end

# 試してみる
a = [*0..3]           # => [0, 1, 2, 3]

# map と my_map の比較
a.map{|i| i * 2}      # => [0, 2, 4, 6]
a.my_map{|i| i * 2}   # => [0, 2, 4, 6]

# Enumerator も比較
i = a.map             # => #<Enumerator: [0, 1, 2, 3]:map>
j = a.my_map          # => #<Enumerator: [0, 1, 2, 3]:my_map>

i.next                # => 0
i.next                # => 1
i.next                # => 2
i.next                # => 3

j.next                # => 0
j.next                # => 1
j.next                # => 2
j.next                # => 3
j.next                # => 

# ~> -:40:in `next': iteration reached an end (StopIteration)
# ~> 	from -:40:in `<main>'

詳しくは、時間がとれたら書きたい。*4

*1:There's More Than One Way To Do It. もっといいやり方があったら教えて。

*2:どう違うかはうまく説明できない。><

*3:これも反則です。

*4:コメントいただければ可能な限りお答えしたい。

2013-02-02

Ruby初級者向けレッスン 44回 ― ブロック ―

第56回 Ruby/Rails勉強会@関西での初級者向けレッスンのスライドを公開します。*1

f:id:mas-higa:20130205231822p:image

繰り返し

a = [0, 1, 2]

a.each do |i|
  puts i
end

a.each{|i| puts i}

# >> 0
# >> 1
# >> 2

f:id:mas-higa:20130204232616p:image

  • ブロックの代表的な使い方は繰り返し処理。
  • do と end で囲まれたもの、{ と } で囲まれたものがブロック。
    • 上のコードと下のコードは同じ処理をしている。
  • Array のように、たくさんのオブジェクトを持っていると、全てのオブジェクトに繰り返し同じ処理をしたいことがよくある。
  • | と | で囲まれた変数 i が Array の各要素を順に指す。

便利な例

a = [0, 1, 2, 3]        # => [0, 1, 2, 3]

a.map{|i| i * i}        # => [0, 1, 4, 9]
a.select{|i| i.even?}   # => [0, 2]
a.inject{|s, i| s + i}  # => 6
a.find{|i| i.odd?}      # => 1
a.all?{|i| i.even?}     # => false
a.any?{|i| i.even?}     # => true

f:id:mas-higa:20130204232617p:image

  • 初級者には、このスライドがおすすめ。

f:id:mas-higa:20130204232620p:image


ブロックを渡す

  • メソッドには、ブロックをひとつ渡せる。
  • ブロックをどう使うかは、メソッド次第。
    • 繰り返し
    • ハリウッドの原理
open('hello.txt')   # => #<File:hello.txt>
open('hello.txt'){|f| f.read}
                    # => "こんにちは\n"

f:id:mas-higa:20130204232618p:image

  • ブロックの役割りは、繰り返しだけではない。
  • ブロックの有無で動作を変えるメソッドがある。

ハリウッドの原理

# open('hello.txt'){|f| f.read}

begin
  f = open('hello.txt')
  f.read
ensure
  f.close unless f.nil?
end

f:id:mas-higa:20130204232619p:image

  • 処理のスケルトンメソッドで用意しておき、処理の一部をブロックで切り替える。
  • ブロック付きの 1行で書いた open メソッドは、ブロックなしで書くと、だいたいこんな感じ。
  • close の部分は f.close unless f.closed? かも。

ブロックのない open

f:id:mas-higa:20130206000309p:image

  • ファイルを扱う場合は、以下の処理が必要。
    1. open
    2. read/write など
    3. close
  • コードを書く際の負担が多い。

ブロック付き open

f:id:mas-higa:20130206000308p:image

  • open メソッドが忘れずに close してくれる。
  • コードを書く際の負担が少ない。

値を受け取る

f:id:mas-higa:20130204232621p:image


値を受け取る (2)

  • 受け取るか受け取らないかは、ブロック次第。
2.times{puts 'こんにちは'}
# >> こんにちは
# >> こんにちは

2.times{|i| puts i}
# >> 0
# >> 1

f:id:mas-higa:20130204232622p:image

  • 上の例は値を受け取っていない。
  • しかし times メソッドは値を渡してくれる。(下の例)
  • ブロックは必ずしも値を受け取る必要はない。

Hash の例

people = {matz: 47, dhh: 32}
            # => {:matz=>47, :dhh=>32}

people.each{|person| p person}
# >> [:matz, 47]
# >> [:dhh, 32]

f:id:mas-higa:20130204232623p:image

  • key と value のペアを受け取れる。
  • ふたつの値が、ひとつの Array オブジェクトに。

Hash の例 (2)

people = {matz: 47, dhh: 32}

people.each do |name, age|
  p "#{name}(#{age})"
end

# >> "matz(47)"
# >> "dhh(32)"

f:id:mas-higa:20130204232624p:image

  • ふたつの値を、それぞれ別の変数で受け取れる。
  • | と | の間に , で区切って変数を列挙する。

each_cons の例

midosuji = ["梅田", "淀屋橋", "本町", "心斎橋", "なんば"]

midosuji.each_cons(2){|path| p path}

# >> ["梅田", "淀屋橋"]
# >> ["淀屋橋", "本町"]
# >> ["本町", "心斎橋"]
# >> ["心斎橋", "なんば"]

f:id:mas-higa:20130204232625p:image


each_cons の例 (2)

midosuji.each_cons(2) do |from, to|
  p "#{from} - #{to}"
end

# >> "梅田 - 淀屋橋"
# >> "淀屋橋 - 本町"
# >> "本町 - 心斎橋"
# >> "心斎橋 - なんば"

f:id:mas-higa:20130204232626p:image

  • ふたつの値を、ふたつの変数で受け取る。

each_cons の例 (3)

a = [*0..3]     # => [0, 1, 2, 3]

a.each_cons(3){|i| p i}
# >> [0, 1, 2]
# >> [1, 2, 3]

a.each_cons(3){|i, j| p [i, j]}
# >> [0, 1]
# >> [1, 2]

f:id:mas-higa:20130204232627p:image

  • 繰り返しの回数は、どちらも 2回。
  • みっつの値を、ひとつの変数で受け取ると Array オブジェクトになる。(上)
  • みっつの値を、ふたつの変数で受け取ると、みっつ目の値が受け取れない。(下)

おかしいな? と思ったら

p unknowns.first
            # >> [1, ["matz", 47]]

unknowns.each do |id, (name, age)|
  id        # => 1
  name      # => "matz"
  age       # => 47
end

f:id:mas-higa:20130204232628p:image

  • 要素を、ひとつ取り出して見る。
  • 例えば、こんなふうに表示されたら、値は幾つか?
    • 答えは、ふたつ! *2
      1. 数値 (1)
      2. Array
  • みっつの変数で受け取るには Array の中の Array を ( ) で表記する。

ブロックを受け取るメソッド

  • こんな感じで呼びたい
monta{puts 'block!'}

# >> block!
# >> block!
# >> 大切なことなので

f:id:mas-higa:20130204232629p:image

  • monta というメソッドを作る。
    • ブロックを 2回評価して、
    • 最後に '大切なことなので' と出力する。

ブロックを受け取る方法は、ふたつある。


ブロックを受け取る

def monta
  yield
  yield
  puts '大切なことなので'
end

f:id:mas-higa:20130204232630p:image

  • yield メソッドで受け取ったブロックを評価する。

ブロックを受け取る (2)

def monta &block
  block.call
  block.call
  puts '大切なことなので'
end

f:id:mas-higa:20130204232631p:image

  • 引数でブロックを受け取る。
  • call メソッドでブロックを評価する。

値を渡す

  • monta メソッドの仕様を変更。
  • ブロックに '大切なことなので' という文字列を渡す。
def monta
  yield '大切なことなので'
  yield '大切なことなので'
end

monta{|i| puts "#{i} block!"}

# >> 大切なことなので block!
# >> 大切なことなので block!

f:id:mas-higa:20130204232632p:image


値を渡す (2)

  • また monta メソッドの仕様を変更。
  • ブロックに '大切な', 'ことなので' という、ふたつの文字列を渡す。
def monta &block
  block.call '大切な', 'ことなので'
  block.call ['大切な', 'ことなので']
end

monta{|i| puts "#{i} block!"}

# >> 大切な block!
# >> ["大切な", "ことなので"] block!

f:id:mas-higa:20130204232633p:image

  • block.call に、ふたつの文字列を渡すだけだと……
    • ブロックがひとつの変数で待ち受けていると、ふたつ目の文字列が受け取ってもらえない。
  • ふたつ以上の値を渡すときは、Array のオブジェクトにして渡す。

ブロックは Proc

block = Proc.new do |i, j|
  puts "#{i}#{j} block!"
end

monta &block

# >> 大切なことなので block!
# >> 大切なことなので block!

f:id:mas-higa:20130204232634p:image

  • あらかじめブロックだけ生成しておける。
  • メソッドに渡す際には & を付ける。

Q&A

Q1
ブロックをふたつ渡せないの?
A1
& なしで普通の引数としてなら渡せます。
Q2
さっきから EmacsRuby のコード実行してるけど、それなに?
A2
るびきちさんが作られた rcodetools を使ってます。Ruby を起動して実行結果を # => の後に埋め込んでくれます。(コード補完もできます) コメントなので、結果の埋め込まれたコードは、そのまま保存・実行できます。
Q3
どんなキーバインドなの?
A3
えっ、それ重要?

*1:スライドだけ欲しい人は直接どうぞ http://higaki-it.jp/ruby/56/slide.pdf

*2:こんなデータ構造が Array で渡ってきたら、データ構造の設計が間違ってる。