2010-03-17 65 views
4

如果我有一个球的列表,其中每个都有一个颜色属性。我怎么能干净地得到最频繁颜色的球的列表。使用红宝石的阵列中的对象的频率

[m1,m2,m3,m4] 

说,

 m1.color = blue 
     m2.color = blue 
     m3.color = red 
     m4.color = blue 

[m1,m2,m4]是最频繁色

我的做法球的名单是做:

[m1,m2,m3,m4].group_by{|ball| ball.color}.each do |samecolor| 
    my_items = samecolor.count 
end 

其中count定义为

class Array 
    def count 
    k =Hash.new(0) 
    self.each{|x|k[x]+=1} 
    k 
    end 
end 

my_items将是同一颜色组的频率散列值。我的实施可能是越野车,我觉得必须有一个更好,更聪明的方式。 有什么想法吗?

+1

如果您从数据库中获取数据尝试优化sql以加快计算速度 – Nishu 2010-03-17 14:03:04

+0

除非您的数据集非常小,否则我强烈建议使用下面的线性时间解决方案。如果您在相同的数据上迭代多次(例如,group_by后面跟着每个或每个类别),那么您的工作量应该高达两倍。 – 2010-03-17 15:16:44

回答

2

你的代码不错,但效率不高。如果我是你,我会寻求通过您的数组遍历一次的解决方案,如:

balls = [m1, m2, m3, m4] 
most_idx = nil 

groups = balls.inject({}) do |hsh, ball| 
    hsh[ball.color] = [] if hsh[ball.color].nil? 
    hsh[ball.color] << ball 

    most_idx = ball.color if hsh[most_idx].nil? || hsh[ball.color].size > hsh[most_idx].size 
    hsh 
end 

groups[most_idx] # => [m1,m2,m4] 

这基本上做同样的事情为group_by,但同时它计数的团体,并保持记录其中组最大(most_idx)。

+0

嗯,我想要对它进行基准测试。调用核心(用C实现)方法肯定会给你带来巨大的性能提升。 – 2010-03-17 16:43:49

+0

如果您查看源代码,注入是一个非常短的方法 - 它只是一个赋值,后跟一个赋值和一个返回值。如果你想直接使用它们,你可以,但是你不会看到任何性能增益。无论哪种方式,只迭代一次是重要的。 http://ruby-doc.org/core/classes/Enumerable.src/M003140.html – 2010-03-17 16:59:56

+1

当谈到Ruby时,你认为“迭代一次是重要的”是非常错误的。我在Ruby 1.8.7中测试了两种算法,随机生成的球体数量增加到100万个,而且你的每个支架的速度比eastafri慢得多。一般来说,将Ruby中的内置函数结合起来将比滚动您自己的“优化版本”更快。 – Chuck 2010-03-17 17:19:09

2

如何:

color,balls = [m1,m2,m3,m4].group_by { |b| b.color }.max_by(&:size)

+0

甚至'[a,b,c,d] .group_by {| z | z.color} .values.max_by(&:size)' – 2010-03-17 14:40:35

+0

@Mladen:更新为使用'max_by' – ezpz 2010-03-17 16:24:15

+0

注意:这实际上假设为Ruby 1.9.1 – 2010-03-17 20:11:42

0
myhash = {} 

mylist.each do |ball| 
    if myhash[ball.color] 
    myhash[ball.color] += 1 
    else 
    myhash[ball.color] = 1  
    end 
end 

puts myhash.sort{|a,b| b[1] <=> a[1]} 
+1

你可以用'myhash = Hash.new(0)'实例化一个哈希,所以你不需要检查一个值是否与一个给定的键已经存在,只是增加它。 – 2010-03-17 15:28:36

5

您发现group_by但错过了max_by

max_color, max_balls = [m1,m2,m3,m4].group_by {|b| b.color}.max_by {|color, balls| balls.length} 
2

以下是我想做到这一点。基本思想是使用注入来将值累加到散列中,并且来自“The Ruby Cookbook”中的“12 - 创建直方图”。

 
#!/usr/bin/env ruby 

class M 
    attr_reader :color 
    def initialize(c) 
    @color = c 
    end 
end 

m1 = M.new('blue') 
m2 = M.new('blue') 
m3 = M.new('red') 
m4 = M.new('blue') 

hash = [m1.color, m2.color, m3.color, m4.color].inject(Hash.new(0)){ |h, x| h[x] += 1; h } # => {"blue"=>3, "red"=>1} 
hash = [m1, m2, m3, m4].inject(Hash.new(0)){ |h, x| h[x.color] += 1; h } # => {"blue"=>3, "red"=>1} 

有两种不同的方法可以做到这一点,具体取决于您希望inject()知道多少知识来了解您的对象。

2

这种通过频率产生的球的反向排序列表

balls.group_by { |b| b.color } 
    .map { |k, v| [k, v.size] } 
    .sort_by { |k, count| -count} 
2

两个部分,我会用你的怪球的例子,但也包括我自己的轨道例如

ary = [m1,m2,m3,m4] 
colors = ary.each.map(&:color) #or ary.each.map {|t| t.color } 
Hash[colors.group_by(&:w).map {|w, ws| [w, ws.length] }] 
#=> {"blue" => 3, "red" => 1 } 

我的ActiveRecord的例子

stocks = Sp500Stock.all 
Hash[stocks.group_by(&:sector).map {|w, s| [w, s.length] }].sort_by { |k,v| v } 
#=> {"Health Care" => 36, etc]