Я не знаю, является ли это "более рубиновым способом".По крайней мере, это более «более высокий порядок», FWIW.
# first line is number of inputs (Don't need it), thus drop the first line
# read inputs as ints
h = ARGF.drop(1).reduce(Hash.new(0)) {|h, n| h.tap {|h| h[n.to_i] += 1 }}
Не так много, чтобы сказать здесь.Вместо того, чтобы просто зацикливаться на ARGF
и устанавливать ключи хеша, мы используем reduce
, чтобы позволить ему сделать всю работу за нас.И мы используем хэш со значением по умолчанию 0
вместо ручной проверки ключей на наличие.
Мы используем Enumerable#drop
, чтобы просто отбросить первую строку.
ARGF
isдействительно классная функция, украденная (как и большинство скриптовых функций) из Perl: если вы просто вызываете скрипт как script.rb
без аргументов, тогда ARGF
является стандартным вводом.Однако если вы называете свой скрипт как script.rb a.txt b.txt
, то Ruby будет интерпретировать все аргументы как имена файлов, откроет все файлы для чтения и ARGF
будет объединением их содержимого.Это позволяет вам очень быстро писать сценарии, которые могут вводить их либо через стандартный ввод, либо через файл.
# find smallest mode
modes = h.group_by(&:last).sort.last.last.map(&:first).sort
puts "Mode is: #{modes.first}"
В Ruby нет явного типа пары ключ-значение, вместо этого большинство циклических операций над хешамииспользовать двухэлементные массивы.Это позволяет нам ссылаться на ключ и значение с помощью Array#first
и Array#last
.
. В данном конкретном случае мы используем Enumerable#group_by
для группировки хэша в разные сегменты, и мы используем критерий группировкииспользуется метод last
, то есть значение, которое в нашем хэше является частотой.Другими словами, мы группируем по частоте.
Если мы теперь сортируем полученный хеш, самый последний элемент - это элемент с самой высокой частотой (т. Е. Режим).Мы берем последний элемент (значение пары ключ-значение) этого, а затем последний элемент этого, который является массивом пар ключ-значение (number => frequency
), из которых мы извлекаем ключи (цифры) и сортируйте их.
[Примечание: просто распечатывайте результаты на каждом промежуточном этапе, и это будет намного легче понять.Просто замените строку modes = ...
выше на что-то вроде этого:
p modes = h.tap(&method(:p)).
group_by(&:last).tap(&method(:p)).
sort.tap(&method(:p)).
last.tap(&method(:p)).
last.tap(&method(:p)).
map(&:first).tap(&method(:p)).
sort
]
modes
теперь является отсортированным массивом со всеми числами, которые имеют эту конкретную частоту.Если мы возьмем первый элемент, у нас будет наименьший режим.
# mode unique?
puts "Mode is #{unless modes.size == 1 then '*not* ' end}unique."
А если размер массива не 1
, то режим не был уникальным.
# print number of singleton odds,
# odd elems repeated odd number times in desc order
# even singletons in desc order
odds, evens = h.select {|_,f|f==1}.map(&:first).sort.reverse.partition(&:odd?)
Похоже, здесь много чего происходит, но на самом деле все просто.Вы начинаете читать после знака равенства и просто читаете слева направо.
- Мы выбираем все элементы в хэше, значение (то есть частота) которых составляет
1
.Итак, мы выбираем все синглеты. - мы отображаем все полученные пары ключ-значение только на первый элемент, то есть на число - IOW мы выбрасываем частоту.
- мы сортируемlist
- и затем перевернуть его (для больших списков мы должны сначала отсортировать в обратном порядке, так как это довольно пустая трата циклов ЦП и памяти)
- наконец, мы разбиваем массив на двамассивы, один из которых содержит все нечетные числа, а другой - все четные числа
. Теперь мы наконец смотрим на левую сторону знака равенства: Enumerable#partition
возвращает двухэлементный массив, содержащий два массива.с разделенными элементами, и мы используем назначение деструктуризации Ruby, чтобы назначить два массива двум переменным*
Теперь, когда у нас есть список нечетных синглетонов, их число просто равно размеру списка.
puts "Elements repeated an odd number of times: #{
h.select {|_, f| f.odd?}.map(&:first).sort.reverse.join(', ')
}"
Это очень похоже на приведенное выше: выберите все номерас нечетной частотой сопоставьте ключи (то есть числа), отсортируйте, переверните и затем преобразуйте их в строку, соединив их вместе запятой и пробелом между ними.
puts "Elements with an even value that appear exactly once: #{evens.join(', ')}"
Опять же, теперь мыу меня есть список четных синглетонов, их печать - это просто вопрос объединения элементов списка с запятыми.
# print fib numbers in the hash
Мне не хотелось реорганизовывать этот алгоритм, чтобы сделать его более эффективным и запоминающимся.Я только что сделал небольшие корректировки.
class Integer
В алгоритме не было ничего, что зависело от числа определенного размера, поэтому я переместил метод в класс Integer
.
def fib?
И я уронил префикс is_
. Тот факт, что это логический метод, уже указан в вопросительном знаке.
l, h = 0, 1
while h <= self
return true if h == self
l, h = h, l+h
end
end
end
puts "Fibonacci numbers: #{h.keys.sort.select(&:fib?).join(', ')}"
Это, вероятно, не требует особых объяснений: возьмите ключи, отсортируйте их, выберите все числа Фибоначчи и соедините их запятыми.
Вот идея о том, как реорганизовать этот алгоритм. Существует очень интересная реализация Фибоначчи , использующая Hash
со значениями по умолчанию для запоминания:
fibs = {0 => 0, 1 => 1}.tap do |fibs|
fibs.default_proc = ->(fibs, n) { fibs[n] = fibs[n-1] + fibs[n-2] }
end
Это будет выглядеть примерно так:
class Integer
@@fibs = {0 => 0, 1 => 1}.tap do |fibs|
fibs.default_proc = ->(fibs, n) { fibs[n] = fibs[n-1] + fibs[n-2] }
end
def fib?
i = 0
until @@fibs[i += 1] > self
break true if @@fibs[i] == self
end
end
end
puts "Fibonacci numbers: #{h.keys.sort.select(&:fib?).join(', ')}"
Если кто-нибудь может придумать элегантный способ избавиться от i = 0
, i += 1
и всего цикла until
, я был бы признателен.