module Enumerable def my_zip(*args) if block_given? enum = args.map{ |e| e.enum_for(:each)} self.each do |e| yield([e, *enum.map{ |e| e.next rescue nil }]) end else (0...self.length).inject([]) do |re, i| re.tap{ re[i] = [self[i], *args.map{ |e| e[i]}]} end end end end [1,2,3,4,5].my_zip([3,4,5]){ |e| p e} # [1, 3] # [2, 4] # [3, 5] # [4, nil] # [5, 3] p [1,2,3].my_zip([3,4]) # [[1, 3], [2, 4], [3, nil]]
"[1,2,3].zip([1,10,100]).map{|x,y| x * y }"と同じような処理をするときには、zipでArrayを生成しないようにblockを使った方がいいかも.
[1,2,3].enum_for(:zip, [1,10,100]).map{|x,y| x * y }
速度はあまり変わらない。
require 'benchmark' Benchmark.bmbm do |x| n = 1000000 a = Array.new(n,3) b = Array.new(n,10) x.report("zip -> map"){ a.zip(b).map{ |x,y| x*y}} x.report("to_enum(:zip, *args"){ a.to_enum(:zip, b).map{ |x,y| x * y}} end =begin Rehearsal ------------------------------------------------------- zip -> map 1.160000 0.050000 1.210000 ( 1.229003) to_enum(:zip, *args 1.370000 0.020000 1.390000 ( 1.404765) ---------------------------------------------- total: 2.600000sec
nの値を大きくしてみたらかなり変わった。
Arrayを生成してから計算。
Rehearsal ----------------------------------------------
zip -> map 14.370000 0.700000 15.070000 ( 15.180819)
------------------------------------ total: 15.070000sec
user system total real
zip -> map 12.820000 0.440000 13.260000 ( 13.406720)
||<
メモリの使用量は70%位に
Arrayを生成しないでブロックで
>||
Rehearsal -------------------------------------------------------
to_enum(:zip, *args 165.000000 0.570000 165.570000 (166.855531)
-------------------------------------------- total: 165.570000sec
user system total real
to_enum(:zip, *args 163.970000 0.570000 164.540000 (165.858670)
メモリの使用量は15%程度。でも、速度は10分の1位。
以下自分なりの回答
#"1 3 4 5 7" => "1, 3-5, 7." def f lst a = lst.split(" ").map{ |e| e.to_i}.sort a[1..-1].inject([[a.first]]) do |tmp, e| tmp.last.last == e-1? tmp.last << e : tmp << [e] tmp end.map{ |e| e.size==1? e.first : "#{e.first}-#{e.last}"}.join(", ") end f("7 3 4 5 1") # => "1, 3-5,
こういう処理をしたいときは、速さよりも書きやすさを意識した方がいいのかな?
要素を整理するメソッドと出力に適した形にするメソッドを分けた方がいいかも。
こんな感じに
def f lst a = lst.split(" ").map{ |e| e.to_i}.sort a[1..-1].inject([[a.first]]) do |tmp, e| tmp.last.last == e-1? tmp.last << e : tmp << [e] tmp end end def layout lst lst.map{ |e| e.size==1? e.first : "#{e.first}-#{e.last}"}.join(", ") end a = f("7 3 4 5 1") # => [[1], [3, 4, 5], [7]] layout(a) # => "1, 3-5, 7" #scriptにしたい場合 #layout(f(ARGV)).display =begin 出力するぎりぎりまで計算が可能な状態を保った方が便利なような気がする。 a = f("3 5 4 2 1 7") # => [[1, 2, 3, 4, 5], [7]] a.map{ |xs| xs.map{ |n| n*n}} # => [[1, 4, 9, 16, 25], [49]] のようなこともできるし(意味があるかはともかく) =end
配列内に自分の指定する値が存在するかどうかを調べるだけなら、Array#include?を使うべきだったみたい。
今までは、以下のような書き方をしていた。
a.find{|e| e == i} #ここで i が調べたい値
require 'benchmark' Benchmark.bmbm do |x| n = 1000 a = (1..1000).to_a a.each_index{ |i| j = rand(i+1); a[i], a[j] = a[j], a[i]} x.report("find"){ n.times{ |i| a.find{ |e| e == i}}} x.report("include?"){ n.times{ |i| a.include?(i)}} end # >> Rehearsal -------------------------------------------- # >> find 0.900000 0.190000 1.090000 ( 1.090316) # >> include? 0.090000 0.000000 0.090000 ( 0.093744) # >> ----------------------------------- total: 1.180000sec # >> # >> user system total real # >> find 0.850000 0.190000 1.040000 ( 1.044942) # >> include? 0.090000 0.000000 0.090000 ( 0.093762)
*1.8と1.9の差を吸収する方法
一度考えてから、答えを見ることにしよう.
こんな風にすればいいのかな?
-使いたいメソッドを持っているかどうか調べる
-なかったら追加する。
unless [].respond_to? :max_by class Array def max_by map{ |e| [yield(e),e]}.max{ |xs, ys| xs[0] - ys[0]}.last end end end def f(n) (1..n).map{ |e| "*"*(5*rand).to_i} end require 'pp' a = (1..10).map{ f(5)} pp (1 .. a.length).zip(a) # !> (...) interpreted as grouped expression pp a.map{ |e| e.max_by{ |x| x.length}} # >> [[1, ["**", "***", "****", "*", "**"]], # >> [2, ["****", "***", "***", "**", "****"]], # >> [3, ["****", "*", "*", "***", ""]], # >> [4, ["***", "**", "*", "***", "***"]], # >> [5, ["****", "**", "**", "", ""]], # >> [6, ["**", "**", "**", "", "*"]], # >> [7, ["****", "*", "**", "*", ""]], # >> [8, ["***", "****", "****", "****", ""]], # >> [9, ["****", "****", "**", "*", "*"]], # >> [10, ["**", "", "**", "*", ""]]] # >> ["****", "****", "****", "***", "****", "**", "****", "****", "****", "**"]
でも、これを直接ソースの中に書くとすでに存在することが分かっているメソッドに対しても、メソッドの有無を調べてしまっている。1.9が無駄に遅くなってしまう。
たぶん、実際に使用する時はこんな感じにするんだと思う。
-1.8用(1.9との差を吸収するため)のファイルを作る
こんな感じかな?
unless VERSION.to_f > 1.8 require 'for_1.8' #1.8用のファイル end #...以下スクリプト
でも、これだと1.8.xで新たに追加されたファイルとの差を吸収することができないかも。if文を二個つければいいのか,こんな感じに
何か良い方法はないのかな?
module Enumerable def accumulate_n(init) (0..self.first.length-1).inject([]) do |re, i| xs = self.map{ |e| e[i]} re << xs.inject(init){ |re,e| yield(re,e)} end end end a = (0..4).map{ |e| e * 3}.map{ |e| [e,e+1,e+2]} a # => [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14]] a.accumulate_n(0){ |re,e| re + e} # => [30, 35, 40] #このbのような結果をもらいたい。 b = (0..a.first.length-1).map{ |i| a.map{ |e| e[i]}} b # => [[0, 3, 6, 9, 12], [1, 4, 7, 10, 13], [2, 5, 8, 11, 14]] #これでできそうな気がするけど、配列の値が共有されてしまう。 c = a.accumulate_n([]){ |re,e| p e; re << e} c # => [[0, 3, 6, 9, 12, 1, 4, 7, 10, 13, 2, 5, 8, 11, 14], [0, 3, 6, 9, 12, 1, 4, 7, 10, 13, 2, 5, 8, 11, 14], [0, 3, 6, 9, 12, 1, 4, 7, 10, 13, 2, 5, 8, 11, 14]] # >> 0 # >> 3 # >> 6 # >> 9 # >> 12 # >> 1 # >> 4 # >> 7 # >> 10 # >> 13 # >> 2 # >> 5 # >> 8 # >> 11 # >> 14
c = a.accumulate_n([]){ |re,e| p e; re.dup << e} # => [[0, 3, 6, 9, 12], [1, 4, 7, 10, 13], [2, 5, 8, 11, 14]]