LoginSignup
4
2

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-05-06

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

ツナ_array.jpg
ちなみにこの写真は他の猫達とベッドに収まるツナです。配列っぽいので載せてみました。

配列

添字での値の取得

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

配列の要素数

mirb
[1,2,3,4,5].length #=> 5
[1,2,3,4,5].size   #=> 5

lengthsizeは同じ動作をします。

2つの配列を結合

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

配列の末尾に値を追加

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

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

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

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

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は一番最後の要素位置

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

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

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

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]

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

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

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

配列からnilを取り除く

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

値を削除

mirb
a = [1, 2, 3, 4, 5]

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

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

指定位置の値を削除

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

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

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

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

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

要素数分繰り返し

各要素について繰り返し

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

1
2
3
4
5

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

添字付きの繰り返し

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]

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

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

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

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

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

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] #破壊的

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

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

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

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

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

Lazy(遅延評価)

ツナ_sleep.jpg
写真はLazyなツナです。

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

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

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

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

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

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

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

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で比較してみました。
単純に百万個の要素に対してmapを回して先頭3要素を返すまでの経過時間を報告するスクリプト。

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を使って実行したらどうなるんでしょう?

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は強力な武器になるかもしれません。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2