`
cvu
  • 浏览: 105143 次
  • 性别: Icon_minigender_1
  • 来自: 上海
最近访客 更多访客>>
社区版块
存档分类
最新评论

我也来出道rubyquiz:each_component

    博客分类:
  • ruby
阅读更多
工作中碰到这样的问题,需要处理乘法法则排列组合后的每一种组合。具体说就是:用Hash表示一套组合,通过each_component把其中的每一种可能的组合抽出来,作为一个新的Hash,在block中使用。

改编成一个rubyquiz。要求:扩展Hash类,写一个each_component方法,让下面的代码可以运行出期望的结果来。
class Sneaker
  def initialize(attributes)
    @brand, @size, @store = attributes[:brand], attributes[:size], attributes[:store]
  end

  def to_s
    "#{@brand} sneakers of #{@size}in from #{@store} store."
  end
end

sneaker_combination = {
  :brand => [:adidas, :nike], 
  :size => [38, 40, 42], 
  :store => :shanghai
}

sneaker_combination.each_component do |sneaker|
  # sneaker_sample.should.be({:brand => :nike, :size => 38, :store => :shanghai})
  puts Sneaker.new(sneaker)
end
# should print:
# adidas sneakers of 38in from shanghai store.
# adidas sneakers of 40in from shanghai store.
# adidas sneakers of 42in from shanghai store.
# nike sneakers of 38in from shanghai store.
# nike sneakers of 40in from shanghai store.
# nike sneakers of 42in from shanghai store.


过几天贴出我的解法。
分享到:
评论
9 楼 yangtao309 2008-03-30  
yangtao309 写道
为什么我这样写 不行啊?
def add_method(c,m,&b)
  c.class_eval { 
    define_method(m,&b)
  }
end

add_method(Hash, :each_component) { 
      hash_size = self.values.select{|i|i.class == Array}.inject(1){|sum,i|sum*i.size}    
   hash = self.map{|k,v|{k=>(v.class == Array)?(v*(hash_size/v.size)).sort_by{|i|i.to_s}:[v]*hash_size}}.inject({}){|sum,i|sum.merge(i)}    
    hash_keys = self.keys    
   (0...hash_size).each{|index|yield Hash[*hash_keys.map{|i|[i,hash[i][index]]}.flatten] if block_given?}
}



下面这样可以了~~ 但是还是不明白上面为什么不行
def add_method(c,m)
  c.class_eval{
    define_method(m,instance_method(m))
  }
end

def each_component
  hash_size = self.values.select{|i|i.class == Array}.inject(1){|sum,i|sum*i.size}    
  hash = self.map{|k,v|{k=>(v.class == Array)?(v*(hash_size/v.size)).sort_by{|i|i.to_s}:[v]*hash_size}}.inject({}){|sum,i|sum.merge(i)}    
  hash_keys = self.keys    
  (0...hash_size).each{|index|yield Hash[*hash_keys.map{|i|[i,hash[i][index]]}.flatten] if block_given?}
end

add_method(Hash, :each_component)
8 楼 cvu 2008-03-30  
我的解法,参考了lgn21st的ruby风格:
class Hash   
  def each_component
    # dup a working hash
    hash, batch_size = self.dup, 1
    hash.each {|k,v| hash[k] = [v] unless v.is_a?(Array); batch_size *= hash[k].size }
    
    # build matrix
    before_size = 1
    hash.each do |k, v| 
      after_size = batch_size / before_size / v.size
      hash[k] = build_matrix(hash[k], before_size, after_size)
      before_size *= v.size
    end
    
    # iterator
    (0...batch_size).each do |index|
      yield Hash[*hash.keys.map{|k|[k,hash[k][index]]}.flatten] if block_given?
    end   
  end

  private
  def build_matrix(array, before_size, after_size)
    (array.collect{|item| Array.new(after_size, item) } * before_size).flatten
  end
end 

7 楼 cvu 2008-03-30  
@不是流氓 可能是我没说清楚,这个each_component是要解决一个通用问题的,所以brand,size这些指定的key要被抽象出来。

@rainchen 递归是个好办法,算法很容易读懂。我按照rainchen的思路,想写一个针对Hash的递归,虽然Hash也有shift,但是无法完成。看来这个算法只适合Array,Hash的话,要先把values组合成一个array,运行后再把结果转回Hash。

@yangtao309 c.class_eval的参数是个字符串,所以起码里面要写成define_method(#{m}),但是这样也不对,还要好好研究一下define_method的定义:http://www.ruby-doc.org/core/classes/Module.html#M001677 。

@lgn21st 这段代码很短很有ruby味,lgn21st功力深厚,不过考虑不周。下面这种2×2的情况结果就不对了。
sneaker_combination = {  
  :brand => [:adidas, :nike],   
  :size => [38, 40],   
  :store => :shanghai  
}  
  
sneaker_combination.each_component do |sneaker|  
  puts Sneaker.new(sneaker)  
end  

# 输出结果有重复了
# adidas sneakers of 38in from shanghai store.  
# nike sneakers of 40in from shanghai store.  
# adidas sneakers of 38in from shanghai store.  
# nike sneakers of 40in from shanghai store. 


6 楼 lgn21st 2008-03-28  
<div class='quote_title'>lgn21st 写道</div><div class='quote_div'>来个暴力计算的,让大家拍砖:<pre name='code' class='ruby'>module HashExtendsion
  def each_component
    hash_size = self.values.select{|i|i.class == Array}.inject(1){|sum,i|sum*i.size}
    hash = self.map{|k,v|{k=&gt;(v.class == Array)?(v*(hash_size/v.size)).sort_by{|i|i.to_s}:[v]*hash_size}}.inject({}){|sum,i|sum.merge(i)}
    hash_keys = self.keys
    (0...hash_size).each{|index|yield Hash[*hash_keys.map{|i|[i,hash[i][index]]}.flatten] if block_given?}
  end
end

class Hash
  include HashExtendsion
end
</pre> </div><br/>修正,计算hash的时候不应该排序,否则结果不对<br/><pre name='code' class='ruby'>class Hash
  def each_component
    hash_size = self.values.select{|i|i.class == Array}.inject(1){|sum,i|sum*i.size}
    hash = self.map{|k,v|{k=&gt;(v.class == Array)?(v*(hash_size/v.size)):[v]*hash_size}}.inject({}){|sum,i|sum.merge(i)}
    hash_keys = self.keys
    (0...hash_size).each{|index|yield Hash[*hash_keys.map{|i|[i,hash[i][index]]}.flatten] if block_given?}
  end
end

</pre> 
5 楼 yangtao309 2008-03-27  
为什么我这样写 不行啊?
def add_method(c,m,&b)
  c.class_eval { 
    define_method(m,&b)
  }
end

add_method(Hash, :each_component) { 
      hash_size = self.values.select{|i|i.class == Array}.inject(1){|sum,i|sum*i.size}    
   hash = self.map{|k,v|{k=>(v.class == Array)?(v*(hash_size/v.size)).sort_by{|i|i.to_s}:[v]*hash_size}}.inject({}){|sum,i|sum.merge(i)}    
    hash_keys = self.keys    
   (0...hash_size).each{|index|yield Hash[*hash_keys.map{|i|[i,hash[i][index]]}.flatten] if block_given?}
}


4 楼 leondu 2008-03-27  
@rainchen
haha...
是我问过你这个问题。
3 楼 lgn21st 2008-03-27  
来个暴力计算的,让大家拍砖:<pre name='code' class='ruby'>module HashExtendsion
  def each_component
    hash_size = self.values.select{|i|i.class == Array}.inject(1){|sum,i|sum*i.size}
    hash = self.map{|k,v|{k=&gt;(v.class == Array)?(v*(hash_size/v.size)).sort_by{|i|i.to_s}:[v]*hash_size}}.inject({}){|sum,i|sum.merge(i)}
    hash_keys = self.keys
    (0...hash_size).each{|index|yield Hash[*hash_keys.map{|i|[i,hash[i][index]]}.flatten] if block_given?}
  end
end

class Hash
  include HashExtendsion
end
</pre> 
2 楼 rainchen 2008-03-27  
有点眼熟,可以抽象为多维数组组合吧,以前写的:

data = [['a','b'],['c','d','e'],['f','g','h','i']] # or more items

def combine(a, b)
  if b.is_a?(Array) && !b.empty?
    tmp = []
    c = b.shift
    a.each do |aa|
      c.each do |cc|
        if aa.is_a?(Array)
          tmp.push aa.clone.push(cc) # may be a ruby bug: must use clone
        else
          tmp.push [aa, cc]
        end
      end
    end
    
    if !b.empty?
      return combine(tmp, b)
    else
      return tmp
    end
  end
end

p "result:"
result = combine(data.shift, data)
p result
p result.size
 
 
-------------
"result:"[["a", "c", "f"], ["a", "c", "g"], ["a", "c", "h"], ["a", "c", "i"], ["a", "d", "f"], ["a", "d", "g"], ["a", "d", "h"], ["a", "d", "i"], ["a", "e", "f"], ["a", "e", "g"], ["a", "e", "h"], ["a", "e", "i"], ["b", "c", "f"], ["b", "c", "g"], ["b", "c", "h"], ["b", "c", "i"], ["b", "d", "f"], ["b", "d", "g"], ["b", "d", "h"], ["b", "d", "i"], ["b", "e", "f"], ["b", "e", "g"], ["b", "e", "h"], ["b", "e", "i"]]

24

1 楼 不是流氓 2008-03-27  


def change_hash(hash)
  brand=[]
  size=[]
  store=[]
  hash.each do |key,value|
    if key.to_s.eql? "brand"
        value.each { |br| brand << br }
    end
    if key.to_s.eql? "size"
        value.each { |si| size << si }
    end
    if key.to_s.eql? "store"
        value.each { |st| store << st }
    end
  end
  
  brand.each do |br1|
    size.each do |si1|
      store.each do |st1|
       puts "#{br1} sneakers of #{si1}in from #{st1} store."  
      end
    end
  end
  
end


sneaker_combination = {  
   :brand => ["adidas", "nike"],   
   :size => [38, 40, 42],   
   :store => ["shanghai"]  
}  

change_hash(sneaker_combination)

我先来一个,不知道对还是不对,但是能得到预期的结果

#adidas sneakers of 38in from shanghai store.
#adidas sneakers of 40in from shanghai store.
#adidas sneakers of 42in from shanghai store.
#nike sneakers of 38in from shanghai store.
#nike sneakers of 40in from shanghai store.
#nike sneakers of 42in from shanghai store.

相关推荐

Global site tag (gtag.js) - Google Analytics