Hatena::Grouprubyist

trotrの日記

2008-07-13

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 }"と同じような処理をするときには、zipArrayを生成しないようにblockを使った方がいいかも.

**こんな感じで、

>|ruby|

[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の値を大きくしてみたらかなり変わった。

n = 10000000でやってみると

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位。

2008-04-19

"1 3 4 5 7" => "1, 3-5, 7." 18:38

http://builder.japan.zdnet.com/sp/ruby-doukaku-panel/story/0,3800086254,20369264,00.htm?mode=all#comment-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

2008-03-28

*includeというmethodの存在に気づかなかった。

配列内に自分の指定する値が存在するかどうかを調べるだけなら、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)

2008-02-06

*1.8と1.9の差を吸収する方法

一度考えてから、答えを見ることにしよう.

こんな風にすればいいのかな?

-使いたいメソッドを持っているかどうか調べる

-なかったら追加する。

**試しにmax_byで

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との差を吸収するため)のファイルを作る

-起動時に、rubyversionを調べる。

-1.8なら、1.8用のファイルをrequireする。

こんな感じかな?

unless VERSION.to_f > 1.8
  require 'for_1.8' #1.8用のファイル
end

#...以下スクリプト

でも、これだと1.8.xで新たに追加されたファイルとの差を吸収することができないかも。if文を二個つければいいのか,こんな感じに

2008-01-23

rubyの配列値が共有されてしまう. 00:09

何か良い方法はないのかな?

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

rubikitchrubikitch2008/01/24 13:17「Array#<<」は破壊的メソッドなのでコピーしましょう。
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]]

trotrtrotr2008/01/25 19:08ありがとうございます。そうか、コピーすればいいのかー。