5回目は配列と遅延評価です。さらっと試してみた所ではmrubyとRubyで動作上の違いは無いようでした。ということで特にmrubyだからどう、という事も無いのですが、せっかくなので試した内容を以下に記します。
ちなみにこの写真は他の猫達とベッドに収まるツナです。配列っぽいので載せてみました。
配列
添字での値の取得
a = [1,2,3,4,5]
a[0] #=> 1
a[1] #=> 2
a[10] #=> nil #配列の要素数を超えた指定はnilをかえす
配列の要素数
[1,2,3,4,5].length #=> 5
[1,2,3,4,5].size #=> 5
length
とsize
は同じ動作をします。
2つの配列を結合
[1,2,3] + [4,5,6] #=> [1, 2, 3, 4, 5, 6]
配列の末尾に値を追加
[1,2,3].push(4) #=> [1, 2, 3, 4] #破壊的
push
は元の配列を破壊的に変更します。
<<
も同様に動作します。
[1,2,3] << 4 #=> [1, 2, 3, 4] #破壊的
指定範囲の要素からなる配列オブジェクトを返す
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は一番最後の要素位置
指定位置から指定個数分の要素からなる配列を返す
a = [1, 2, 3, 4, 5]
a[0, 3] #=> [1, 2, 3]
a[1, 3] #=> [2, 3, 4]
配列の各要素をブロックで渡された式に従って評価した結果の配列を返す
[1,2,3,4].collect{|e| e * 2} #=> [2, 4, 6, 8]
[1,2,3,4].map{|e| e * 2} #=> [2, 4, 6, 8]
map
はcollect
の別名です。
メソッド名の後ろに!
を付加すると破壊的メソッドとなり、もとの配列を変更します。
a = [1, 2, 3, 4]
a.collect!{|e| e * 2} #=> [2, 4, 6, 8]
a #=> [2, 4, 6, 8]
配列からnilを取り除く
[1, 2, 3, nil, 4, 5, nil].compact #=> [1, 2, 3, 4, 5]
値を削除
a = [1, 2, 3, 4, 5]
a.delete(1) #=> 1 #破壊的
a #=> [2, 3, 4, 5]
delete
の戻り値は削除された値です。
このメソッドは元の配列を破壊的に変更します。
指定位置の値を削除
a = [1, 2, 3, 4, 5]
a.delete_at(1) #=> 2 #破壊的
a #=> [1, 3, 4, 5]
delete_at
の戻り値は削除された値です。
このメソッドは元の配列を破壊的に変更します。
各要素をブロックで評価して、結果が真となる値を削除
a = [1, 2, 3, 4, 5]
a.delete_if{|e| e >= 3} #=> [1, 2] #破壊的
a #=> [1, 2]
このメソッドは元の配列を破壊的に変更します。
要素数分繰り返し
各要素について繰り返し
[1, 2, 3, 4, 5].each do |e|
puts e
end
1
2
3
4
5
# => [1, 2, 3, 4, 5]
添字付きの繰り返し
[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]
配列が空かどうかを判定する
[1,2].empty? #=> false
[].empty? #=> true
条件に合致する要素のインデックス番号を取得する
[1,2,3,4,5].find_index{|e| e == 3} #=> 2
配列を含む配列を平滑にする
[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] #破壊的
指定要素を含むかどうかを判定
a = [1,2,3,4,5]
a.include?(3) #=> true
a.include?(10) #=> false
配列の各要素を連結した文字列をかえす
["tuna", "pochi", "tama", "mike"].join
#=> "tunapochitamamike"
["tuna", "pochi", "tama", "mike"].join(",")
#=> "tuna,pochi,tama,mike"
Lazy(遅延評価)
mrubyにおいてもRuby2.0で導入された遅延評価ライブラリLazyがmrbgems:mruby-enum-lazy
として提供されています。
配列に対してLazyを使うと、mapなどの配列に対する操作を実際にはその時点では処理せずに、結果の値が必要になった時点で処理を行う、という事ができるそうです。
build_config.rbでgemboxにdefault
を指定している場合は既に読み込まれています。
lazy_list = [1,2,3,4,5].lazy.map{|e| e * 2}
#=> #<Enumerable::Lazy: #<Enumerator::Generator:0x7fa8c88077a0>:each>
lazyを使用した場合の返り値はEnumerable::Lazyクラスのインスタンスです。
この時点でブロックの中身は評価されず、first
またはforce
で値を取り出した時点で評価されます。
lazy_list.force #=> [2, 4, 6, 8, 10]
lazy_list.first #=> 2
他にselectなどと組み合わせて使えます。例えば配列の全要素を2乗して、結果が偶数のものだけ抽出した配列から先頭3要素だけ取り出す場合。
[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要素を返すまでの経過時間を報告するスクリプト。
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
$ 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
$ mruby -v
mruby 1.0.0 (2014-01-10)
$ mruby array-time.rb
process time: 2.60280699999999
oh...約27倍の差でRuby2.1.1の圧勝です。まあ用途が違うので単純な速度比較はあまり意味が有りませんが。
遅延評価あり
同様の処理をLazyを使って実行したらどうなるんでしょう?
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の場合
$ ruby array-time-lazy.rb
process time: 0.00678566
mruby1.0.0の場合
$ mruby array-time-lazy.rb
process time: 0.17267199999999
速度差は同程度ですが、mrubyにおいて処理にかかる時間は約15分の1となりました。リソースの限られた環境での使用が想定されるmrubyにおいてはLazyは強力な武器になるかもしれません。