如何简化此类方法?

时间:2018-04-27 17:34:48

标签: ruby class oop

我有一个班级,有50个随机分数,随机分数,存储在哈希数组中:

def initialize
    # adding fake names and numbers
    require 'faker'
    @people = []
    (1..50).each do
      @people << { name: Faker::Name.first_name, score: Faker::Number.between(1, 1000).to_i }
    end
end

特别是top方法(下面)将返回字符串中排名前N位的人。作为一个更新的红宝石,我认为可能有一种方法可以使这更简单。这是方法:

def top(number=10)
    top_people = @people.group_by { |person| person[:score] }
                        .sort_by { |key, value| -key }     # largest -> smallest
                        .first(number)
                        .map(&:last)
                        .flatten
                        .map { |person| "#{person[:name]} (#{person[:score]})" }
                        .join(", ")
    puts "The top #{number} are here: #{top_people}"
end

供参考,使用Ruby 2.3.3

3 个答案:

答案 0 :(得分:1)

将业务逻辑(如何获得最佳人才)与输出(如何显示人员列表)混合起来可能不是一个好主意。您可以通过组合sort_by / group_by并使用flat_map

来略微简化
class MyClass
  def top number=10
    top_groups = @people.group_by { |person| person[:score] }.max_by(number, &:first)
    top_groups.flat_map(&:last)
  end

  def self.show_people people
    people.map { |person| "%{name} (%{score})" % person }.join(", ")
  end
end

some_class = MyClass.new
top_people = some_class.top 10
puts "The top #{top_people.size} people are #{MyClass.show_people(top_people)}"

答案 1 :(得分:1)

Be Welcoming是我们的新动力,所以这里......

当您提出问题时,请确保它是一个完整且实用的问题。例如“我有一个有50个随机分组的班级,随机分数存储在哈希数组中:[...]特别是顶级方法将返回带有分数的字符串中的前N个人。”

请向我们提供一个课程:

# require your dependencies outside of the class 
# this is where they will end up anyway and it makes it easier for us
# to find them
require 'faker'

class Scoreboard
   def initialize
     # adding fake names and numbers

     @people = []
     (1..50).each do
       @people << { name: Faker::Name.first_name, 
                    score: Faker::Number.between(1, 1000).to_i }
     end
  end
  def top(number=10)
    top_people = @people.group_by { |person| person[:score] }
                    .sort_by { |key, value| -key }     # largest -> smallest
                    .first(number)
                    .map(&:last)
                    .flatten
                    .map { |person| "#{person[:name]} (#{person[:score]})" }
                    .join(", ")
    puts "The top #{number} are here: #{top_people}"
  end
end

现在让我们在Scoreboard#top

中谈谈你实际在做什么

步骤:

  • 按分数分组Hash
  • 按分数排序(反向)
  • 采取前n个分组
  • 地图删除分数
  • 展平Array
  • 再次映射到名称(得分)字符串
  • 加入逗号
  • 将它们全部打印在同一行

这看起来有点矫枉过正。让我们通过找到切断来尝试不同的解决方案

def cut(n=10) 
  @people.map {|p| p[:score]}.uniq.sort.last(n).first
end

现在我们知道我们将接受的最小分数,所以现在只需要那些people

def top(n=10)
  top_people = @people.select {|p| p[:score] >= cut(n) }
               .sort_by {|p| -p[:score]}
               .map { |person| "#{person[:name]} (#{person[:score]})" }
  puts "The top #{n} are here: #{top_people.join(',')}"
end 

现在看起来有点干净,但People不应该降级到字典(毕竟我们有感情)所以让它们成为真正的Object(顺便说一句,人们在现实生活中仍然是错的)。由于它们是只有first_namescore Struct的简单生物,所以会很好。

Person = Struct.new(:name, :score) 

这实际上创建了一个看起来像这样的对象

class Person 
  attr_accessor :name, :score 
  def initialize(name,score)
    @name = name
    @score = score
  end
end 

现在我们可以像这样创造我们的人

def initialize
  # we will use Enumerable#map rather than initializing an Array
  # and pushing into it
  @people = (1..50).map do 
    Person.new(Faker::Name.first_name, 
        Faker::Number.between(1, 1000).to_i)
  end
end

现在而不是Hash#[]访问权限我们有scorename的方法,所以我们可以使用一些Symbol#to_proc糖(现在不要担心这个但是请随意调查它,因为它是非常红宝石的惯用语)

def cut(n=10) 
  @people.map(&:score).uniq.sort.last(n).first
end
def top(n=10)
 top_people = @people.select {|p| p.score >= cut(n) }
              .sort_by(&:score).reverse
              .map { |person| "#{person.name} (#{person.score})" }
 puts "The top #{n} are here: #{top_people.join(',')}"
end 

我们现在几乎就在那里,但这个"#{person.name} (#{person.score})"似乎很愚蠢,因为这些是唯一的属性,所以让我们通过为{Person定义to_s来使其成为Person的默认表示。 1}}

Person = Struct.new(:name, :score) do 
   def to_s
      "#{name} (#{score})"
   end
end 

现在我们有了

def top(n=10)
  top_people = @people.select {|p| p.score >= cut(n) }
               .sort_by(&:score).reverse
end 

此外,由于puts返回nil,您可以在其他地方处理显示,我已删除puts语句。由于您已经从电话会议外了解n,我建议您使用以下内容:

n = 12
puts "The top #{n} people are:" 
puts Scoreboard.new.top(n) 

哦,现在这么干净了。希望你喜欢这个答案并一路上学到一些东西。 Full Example

答案 2 :(得分:1)

我也试图改善其他事情。我想念更多的背景,但作为理论练习回答你的问题,这就是我要做的事情:

 require 'faker'

 class ScoredPerson
  attr_reader :name
  attr_reader :score

  def initialize
    @name = Faker::Name.first_name
    @score = Faker::Number.between(1, 1000).to_i
  end
end

class TopPeople
  attr_accessor :people

  def initialize
    @people = 50.times.map do
      ScoredPerson.new
    end.sort_by { |p| p.score }.reverse
  end

  def top(number=10)
    people.first(number)
      .map { |p| "#{p.name} (#{p.score})"}.join(", ")
  end
end
  • 创建一个有序人员列表(最开始)。这将阻止每次调用top方法时订购。
  • 创建一个ScoredPerson类来封装得分人的逻辑。