Будь доброжелателен - наш новый девиз, так что ...
Когда вы задаете вопрос, пожалуйста, убедитесь, что это полный и функциональный вопрос. например «У меня есть класс, в котором 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)
Ох, теперь намного чище. Надеюсь, вам понравился ответ, и вы чему-то научились. Полный пример