Rubyとの出会いを思い出した

irofさんのid:irof:20111203を読んで。
同感ですね〜!


思えば、同じ思い(「ブレイク処理」は正直論外で、分かり易く書くには?)がキッカケでRubyを知って、ハマっていった気がする。


当時、自分の使える言語ではスッキリしなくて、使ったことがない言語を試してたらRubyがドンピシャだった。
というか、全く使ったことなかったのに『こんな感じで書けたら〜』と気楽に書いてたら出来ちゃったし。
とにかくスッキリしちゃったので、使ったことがない言語での調査もそこで終了させちゃったw


てな感じで、記事を読んでたら思わずRubyと出会った頃を思い出しましたよ。


さて、当時よりわかりにくい気がするけど、今だとこう書きます。

Datum = Struct.new(:code, :name, :value)
data = [
	Datum.new("A01", "hoge", 100),
	Datum.new("A01", "piyo", 200),
	Datum.new("A02", "hoge", 300),
	Datum.new("A03", "hoge", 400),
	Datum.new("A03", "piyo", 500),
]
p data.group_by{ |d| d.code }.tap{ |_| _.each{ |key, data| _[key] = data.inject(0){ |r, d| r+d.value } } }
# => {"A01"=>300, "A02"=>300, "A03"=>900}

FizzBuzzコンバーター

最近FizzBuzz記事が多いですね。
ということで、私も乗っかってネタを一つ

ruby
# coding: UTF-8

class Converter
  
  def initialize
    @specs = []
  end
  
  def specs( value , &cond )
    @specs << lambda { |e| cond[e] ? value : nil }
  end
  
  def to_proc
    lambda { |e| 
      conved = @specs.map{ |spec| spec[e] }.join
      conved.empty? ? e : conved
    }
  end

end

if __FILE__ == $0

  ARGV[0] or raise "Requires an integer argument."
  max = ARGV[0].to_i
  
  fizzbuzz  = Converter.new

  fizzbuzz.specs( "Fizz" ){ |n| n % 3 == 0 }
  fizzbuzz.specs( "Buzz" ){ |n| n % 5 == 0 }

  puts 1.upto(max).map(&fizzbuzz)

end

追記:タイトルと関係ないけど、FizzBuzzメソッドチェイン

ruby
puts 1.upto(100).with_object(nil)
                .map { |n, o| ( o ||= [] ) << :Fizz if n % 3 == 0; [n, o] }
                .map { |n, o| ( o ||= [] ) << :Buzz if n % 5 == 0; [n, o] }
                .map { |n, o| ( o || [n] ) * "" }

お題:FizzBuzz

お題:FizzBuzz - No Programming, No Life

rubyで回答
1.upto(100){|i|puts"#{[:Fizz][i%3]}#{[:Buzz][i%5]}"[/.+/]||i}

お題:ある金額になるコインの組み合わせ

お題:ある金額になるコインの組み合わせ - No Programming, No Life

rubyで回答
# coding: CP932
require "rspec"

def total_coins_pattern(total, *coins)
  total = total.to_i
  coins = [*coins].flatten
  min_coin = coins.min.to_i
  size = min_coin.zero? || total.zero? ? exit(false) : total / min_coin
  1.upto(size).inject([]){|r, n| r + coins.repeated_combination(n).select{|c| c.inject(:+).eql?(total)} }.compact.sort
end

def total_coins_output(total, coins, &block)
  result = yield(total, coins)
<<EOS

コインの種類:#{coins.join(", ")}
金額:#{total}
組み合わせ数:#{result.count}
組み合わせ:
#{result.map(&:to_s).join("\n").delete('"')}
EOS
end

describe "total_coins_pattern" do

  context "with argument 1, [1]" do
    subject { total_coins_pattern(1, [1]) }
    it { should eq [[1]] }
  end

  context "coins [1, 5, 10, 50, 100, 500]" do

    let(:coins){[1, 5, 10, 50, 100, 500]}

    context "total 1" do
      subject { total_coins_pattern(1, coins) }
      it { should eq [[1]] }
    end

    context "total 4" do
      subject { total_coins_pattern(4, coins) }
      it { should eq [[1, 1, 1, 1]] }
    end

    context "total 5" do
      subject { total_coins_pattern(5, coins) }
      it { should eq [[1, 1, 1, 1, 1], [5]] }
    end

    context "total 6" do
      subject { total_coins_pattern(6, coins) }
      it { should eq [[1, 1, 1, 1, 1, 1], [1, 5]] }
    end

    context "total 10" do
      subject { total_coins_pattern(10, coins) }
      it { should eq [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 5], [5, 5], [10]] }
    end

    context "total 11" do
      subject { total_coins_pattern(11, coins) }
      it { should eq [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 5], [1, 5, 5], [1, 10]] }
    end

  end

end

describe "total_coins_output" do

  context "with argument 10, [1, 5, 10, 50, 100, 500], &block" do

    subject { total_coins_output(10, [1, 5, 10, 50, 100, 500]){|total, coins| total_coins_pattern(total, coins)} }
    it { should eq <<EOS }

コインの種類:1, 5, 10, 50, 100, 500
金額:10
組み合わせ数:4
組み合わせ:
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 5]
[5, 5]
[10]
EOS
  end

end

__END__
total_coins_pattern
  with argument 1, [1]
    should == [[1]]
  coins [1, 5, 10, 50, 100, 500]
    total 1
      should == [[1]]
    total 4
      should == [[1, 1, 1, 1]]
    total 5
      should == [[1, 1, 1, 1, 1], [5]]
    total 6
      should == [[1, 1, 1, 1, 1, 1], [1, 5]]
    total 10
      should == [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 5], [5, 5], [10]]
    total 11
      should == [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 5], [1, 5, 5], [1, 10]]

total_coins_output
  with argument 10, [1, 5, 10, 50, 100, 500], &block
    should ==
コインの種類:1, 5, 10, 50, 100, 500
金額:10
組み合わせ数:4
組み合わせ:
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 5]
[5, 5]
[10]

お題:文字列を先頭から見て同じところまで除去

お題:文字列を先頭から見て同じところまで除去 - No Programming, No Life

rubyで回答
# coding: CP932
require "rspec"

def shift_same_str(*args)
  args.map(&:chars).map(&:to_a).tap do |x|
    x.min_by(&:count).dup.each {|s| x.each(&:shift) if x.all?{|a| a.first.eql?(s)}}
  end.map!(&:join)
end

describe "shift_same_str" do
  
  context "with argument 'abcdef', 'abc123'" do
    subject { shift_same_str('abcdef', 'abc123') }
    it { should eq ['def', '123'] }
  end

  context "with argument 'あいうえお', 'あいさんさん', 'あいどる'" do
    subject { shift_same_str('あいうえお', 'あいさんさん', 'あいどる') }
    it { should eq ['うえお', 'さんさん', 'どる'] }
  end

  context "with argument '12345', '67890', '12abc'" do
    subject { shift_same_str('12345', '67890', '12abc') }
    it { should eq ['12345', '67890', '12abc'] }
  end
  
end

__END__
>rspec odai.rb -fd

shift_same_str
  with argument 'abcdef', 'abc123'
    should == ["def", "123"]
  with argument 'あいうえお', 'あいさんさん', 'あいどる'
    should == ["うえお", "さんさん", "どる"]
  with argument '12345', '67890', '12abc'
    should == ["12345", "67890", "12abc"]

Finished in 0.01562 seconds
3 examples, 0 failures

メモ:Enumerator

似てるけど大きな違い。メソッドが何を返すか気をつけよう。

Enumeratorオブジェクトを返すメソッドは、もっとあっても良いと思う。
id:ku-ma-meさんのmame/enumerabler · GitHubとか採用されるといいな。

# ruby 1.9.2p180 (2011-02-18) [i386-mingw32]

num = 10

puts num.downto( 1 ).map{ |n| "*" * n }   # Case.1
num.downto( 1 ).map{ |n| puts "*" * n }   # Case.2

num = 2**16

#puts num.downto( 1 ).map{ |n| "*" * n }  # => `*': failed to allocate memory (NoMemoryError)
#num.downto( 1 ).map{ |n| puts "*" * n }  # Case.2

num = 10

# Case.1
enum = num.downto( 1 ).map
fmt  = lambda{ |n| "*" * n }
data = enum.each( &fmt )
puts data

# Case.2
enum = num.downto( 1 ).map
out  = lambda{ |n| puts "*" * n }
enum.each( &out )

num = 2**16

# Case.1
#enum = num.downto( 1 ).map
#fmt  = lambda{ |n| "*" * n }
#data = enum.each( &fmt )  # => `*': failed to allocate memory (NoMemoryError)
#puts data

# Case.2
#enum = num.downto( 1 ).map
#out  = lambda{ |n| puts "*" * n }
#enum.each( &out )

RubyとWSHでユーザー環境変数を設定する

チクタク…チクタク…

# coding: CP932

module OLE
  require "win32ole"
  
  module Environment

    MEMBERS = {
      HTTP_PROXY: [
        [ :protocol, :username, :password, :host, :port ],
        %r{^(.*?)//(.*?):(.*?)@(.*?):(.*?)$},
        lambda{ |values| "%s//%s:%s@%s:%s"%values },
      ],
    }

    module User

      class << self

        def [](name)
          wsh = WIN32OLE.new("WScript.Shell")
          obj = wsh.Environment(self.name.split(/::/).last.capitalize)
          variable(obj, name)
        end

        def variable(obj, name)

          attrs, pattern, format = *MEMBERS[name.to_sym]

          Struct.new(name, *attrs) do
            define_method( :name )   { name }
            define_method( :value )  { values.join.empty? ? nil : format[values] }
            define_method( :show )   { puts value }
            define_method( :load )   { members.zip( obj[name].scan(pattern).flatten ).each{ |m, v| self[m] = v } ; self }
            define_method( :save )   { value ? obj[name] = value : remove ; self }
            define_method( :remove ) { load && obj.Remove(name) }
          end

        end

      end

    end
    
  end

end

http_proxy = OLE::Environment::User["HTTP_PROXY"].new
http_proxy.load.show                              # =>
http_proxy.protocol = "http:"
http_proxy.username = "username"
http_proxy.password = "password"
http_proxy.host     = "proxy.host.ne.jp"
http_proxy.port     = "8080"
http_proxy.show                                   # =>http://username:password@proxy.host.ne.jp:8080
http_proxy.save
http_proxy.load.show                              # =>http://username:password@proxy.host.ne.jp:8080
http_proxy.password = http_proxy.password.reverse
http_proxy.port     = http_proxy.port[0,3] << "8"
http_proxy.show                                   # =>http://username:drowssap@proxy.host.ne.jp:8088
http_proxy.save
http_proxy.load.show                              # =>http://username:drowssap@proxy.host.ne.jp:8088