ツナでもわかるmruby [5回目：配列と遅延評価]



５回目は配列と遅延評価です。さらっと試してみた所ではmrubyとRubyで動作上の違いは無いようでした。ということで特にmrubyだからどう、という事も無いのですが、せっかくなので試した内容を以下に記します。

![ツナ_array.jpg](https://qiita-image-store.s3.amazonaws.com/0/13835/017b46a1-ec18-b960-ee46-fb5066431113.jpeg "ツナ_array.jpg")
ちなみにこの写真は他の猫達とベッドに収まるツナです。配列っぽいので載せてみました。


## 配列

####添字での値の取得

```ruby:mirb
a = [1,2,3,4,5]
a[0]  #=> 1
a[1]  #=> 2
a[10] #=> nil #配列の要素数を超えた指定はnilをかえす
```

####配列の要素数

```ruby:mirb
[1,2,3,4,5].length #=> 5
[1,2,3,4,5].size   #=> 5
```
<code>length</code>と<code>size</code>は同じ動作をします。

####2つの配列を結合

```ruby:mirb
[1,2,3] + [4,5,6] #=> [1, 2, 3, 4, 5, 6]
```

####配列の末尾に値を追加

```ruby:mirb
[1,2,3].push(4) #=> [1, 2, 3, 4] #破壊的
```

<code>push</code>は元の配列を破壊的に変更します。
<code><<</code>も同様に動作します。

```ruby:mirb
[1,2,3] << 4 #=> [1, 2, 3, 4] #破壊的
```

####指定範囲の要素からなる配列オブジェクトを返す

```ruby:mirb
a = [1, 2, 3, 4, 5, 6]
a[0..3]   #=> [1, 2, 3, 4]
a[0...3]  #=> [1, 2, 3]
a[0..-1]  #=> [1, 2, 3, 4, 5, 6] # -1は一番最後の要素位置
```

####指定位置から指定個数分の要素からなる配列を返す

```ruby:mirb
a = [1, 2, 3, 4, 5]
a[0, 3]  #=> [1, 2, 3]
a[1, 3]  #=> [2, 3, 4]
```

####配列の各要素をブロックで渡された式に従って評価した結果の配列を返す

```ruby:mirb
[1,2,3,4].collect{|e| e * 2} #=> [2, 4, 6, 8]
[1,2,3,4].map{|e| e * 2}     #=> [2, 4, 6, 8]
```

<code>map</code>は<code>collect</code>の別名です。
メソッド名の後ろに<code>!</code>を付加すると破壊的メソッドとなり、もとの配列を変更します。

```ruby:mirb
a = [1, 2, 3, 4]
a.collect!{|e| e * 2} #=> [2, 4, 6, 8]

a #=> [2, 4, 6, 8]
```

#### 配列からnilを取り除く

```ruby:mirb
[1, 2, 3, nil, 4, 5, nil].compact #=> [1, 2, 3, 4, 5]
```

####値を削除

```ruby:mirb
a = [1, 2, 3, 4, 5]

a.delete(1) #=> 1  #破壊的
a           #=> [2, 3, 4, 5] 
```

<code>delete</code>の戻り値は削除された値です。
このメソッドは元の配列を破壊的に変更します。

####指定位置の値を削除

```ruby:mirb
a = [1, 2, 3, 4, 5]
a.delete_at(1)  #=> 2 #破壊的
a               #=> [1, 3, 4, 5]
```

<code>delete_at</code>の戻り値は削除された値です。
このメソッドは元の配列を破壊的に変更します。

####各要素をブロックで評価して、結果が真となる値を削除

```ruby:mirb
a = [1, 2, 3, 4, 5]
a.delete_if{|e| e >= 3} #=> [1, 2] #破壊的
a                       #=> [1, 2]
```

このメソッドは元の配列を破壊的に変更します。

####要素数分繰り返し
各要素について繰り返し

```ruby:mirb
[1, 2, 3, 4, 5].each do |e|
  puts e
end

1
2
3
4
5

#=> [1, 2, 3, 4, 5]
```

添字付きの繰り返し

```ruby:mirb
[1, 2, 3, 4, 5].each_with_index do |e, index|
  puts "#{index}: #{e}"
end

0: 1
1: 2
2: 3
3: 4
4: 5

#=> [1, 2, 3, 4, 5]
```

####配列が空かどうかを判定する

```ruby:mirb
[1,2].empty? #=> false
[].empty?    #=> true
```

####条件に合致する要素のインデックス番号を取得する

```ruby:mirb
[1,2,3,4,5].find_index{|e| e == 3} #=> 2
```

####配列を含む配列を平滑にする

```ruby:mirb
[1, ["a", "b"], 2, 3, [["c", "d", "e"], 4]].flatten
 #=> [1, "a", "b", 2, 3, "c", "d", "e", 4]

[1, ["a", "b"], 2, 3, [["c", "d", "e"], 4]].flatten!
 #=> [1, "a", "b", 2, 3, "c", "d", "e", 4] #破壊的
```

####指定要素を含むかどうかを判定

```ruby:mirb
a = [1,2,3,4,5]
a.include?(3)  #=> true
a.include?(10) #=> false
```

####配列の各要素を連結した文字列をかえす

```ruby:mirb
["tuna", "pochi", "tama", "mike"].join
 #=> "tunapochitamamike"

["tuna", "pochi", "tama", "mike"].join(",")
 #=> "tuna,pochi,tama,mike"
```

##Lazy(遅延評価)

![ツナ_sleep.jpg](https://qiita-image-store.s3.amazonaws.com/0/13835/3eb8a0c6-4499-665e-47b0-79523e3041b5.jpeg "ツナ_sleep.jpg")
写真はLazyなツナです。

mrubyにおいてもRuby2.0で導入された遅延評価ライブラリLazyがmrbgems:<code>mruby-enum-lazy</code>として提供されています。

配列に対してLazyを使うと、mapなどの配列に対する操作を実際にはその時点では処理せずに、結果の値が必要になった時点で処理を行う、という事ができるそうです。

build_config.rbでgemboxに<code>default</code>を指定している場合は既に読み込まれています。

```ruby:mirb
lazy_list = [1,2,3,4,5].lazy.map{|e| e * 2}
 #=> #<Enumerable::Lazy: #<Enumerator::Generator:0x7fa8c88077a0>:each>
```

lazyを使用した場合の返り値はEnumerable::Lazyクラスのインスタンスです。
この時点でブロックの中身は評価されず、<code>first</code>または<code>force</code>で値を取り出した時点で評価されます。

```ruby:mirb
lazy_list.force #=> [2, 4, 6, 8, 10]
lazy_list.first #=> 2
```

他にselectなどと組み合わせて使えます。例えば配列の全要素を2乗して、結果が偶数のものだけ抽出した配列から先頭3要素だけ取り出す場合。

```ruby:mirb
[1,2,3,4,5,6,7,8,9,10].lazy.map{|e| e * e}.
                       select{|e| (e % 2) == 0}.
                       take(3).
                       force
```

lazyを通して使用可能なメソッドは以下
* map
* select
* reject
* grep
* drop
* drop_while
* take
* take_while
* flat_map (collect_concat)
* zip

##Ruby2.1.1とmruby1.0.0でArray#mapの速度比較

#### 遅延評価なし
試しに以下のコードをRuby2.1.1とmruby1.0.0で比較してみました。
単純に百万個の要素に対して<code>map</code>を回して先頭3要素を返すまでの経過時間を報告するスクリプト。

```ruby:array-time.rb
start_time = Time.now
Array.new(1000000, 1).map{|e| e}[0, 3]
end_time = Time.now

process_time = end_time - start_time
puts "process time: #{process_time}"
```

まずは Ruby2.1.1

```ruby2.1.1
$ ruby -v
ruby 2.1.1p76 (2014-02-24 revision 45161) [x86_64-linux]

$ ruby array-time.rb 
process time: 0.093344695
```

続いて mruby1.0.0

```mruby1.0.0
$ mruby -v
mruby 1.0.0 (2014-01-10)

$ mruby array-time.rb 
process time: 2.60280699999999
```

oh...約27倍の差でRuby2.1.1の圧勝です。まあ用途が違うので単純な速度比較はあまり意味が有りませんが。

####遅延評価あり
同様の処理をLazyを使って実行したらどうなるんでしょう？

```ruby:array-time-lazy.rb
start_time = Time.now
Array.new(1000000, 1).lazy.map{|e| e}.take(3).force
end_time = Time.now

process_time = end_time - start_time
puts "process time: #{process_time}"
```

Ruby2.1.1の場合

```ruby2.1.1
$ ruby array-time-lazy.rb 
process time: 0.00678566
```

mruby1.0.0の場合

```mruby1.0.0
$ mruby array-time-lazy.rb 
process time: 0.17267199999999
```

速度差は同程度ですが、mrubyにおいて処理にかかる時間は約15分の1となりました。リソースの限られた環境での使用が想定されるmrubyにおいてはLazyは強力な武器になるかもしれません。