Как я могу упростить этот метод класса? - PullRequest
0 голосов
/ 27 апреля 2018

У меня есть класс, в котором 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 лучших людей в строке с их счетом. Будучи немного новичком в ruby, я думаю, что есть способ сделать это проще. Вот метод:

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 голосов
/ 27 апреля 2018

Будь доброжелателен - наш новый девиз, так что ...

Когда вы задаете вопрос, пожалуйста, убедитесь, что это полный и функциональный вопрос. например «У меня есть класс, в котором 50 случайных людей со случайными оценками хранятся в массиве хешей: [...] В частности, метод top возвращает первые 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_name и score, то 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#[] доступа у нас есть методы для score и name, поэтому мы можем использовать немного сахара 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

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) 

Ох, теперь намного чище. Надеюсь, вам понравился ответ, и вы чему-то научились. Полный пример

0 голосов
/ 27 апреля 2018

Я пытался улучшить и другие вещи. Я пропускаю немного больше контекста, но отвечая на ваш вопрос в качестве теоретического упражнения, я бы сделал следующее:

 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 для инкапсуляции логики забитого человека.
0 голосов
/ 27 апреля 2018

Вероятно, не стоит смешивать бизнес-логику (как получить лучших людей) с выводом (как отобразить список людей). И вы можете немного упростить, комбинируя 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)}"
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...