Что является более "рубиновым способом" для написания этого кода? - PullRequest
4 голосов
/ 15 июня 2010

Это было домашнее задание для моих учеников (я ассистент преподавателя) в c, и я пытаюсь выучить Ruby, поэтому я решил написать его.Цель состоит в том, чтобы прочитать целые числа из перенаправленного файла и распечатать некоторую простую информацию.Первая строка в файле - это число элементов, а затем каждое целое число находится в отдельной строке.

Этот код работает (хотя, возможно, неэффективно), но как я могу сделать код более похожим на Ruby?

#!/usr/bin/ruby -w

# first line is number of inputs (Don't need it)
num_inputs = STDIN.gets.to_i

# read inputs as ints
h = Hash.new
STDIN.each do |n|
  n = n.to_i
  h[n] = 1 unless h[n] and h[n] += 1      
end

# find smallest mode
h.sort.each do |k,v|
  break puts "Mode is: #{k}", "\n" if v == h.values.max
end

# mode unique?
v = h.values.sort
print "Mode is unique: "
puts v.pop == v.pop, "\n"

# print number of singleton odds, 
#       odd elems repeated odd number times in desc order
#       even singletons in desc order
odd_once = 0
odd = Array.new
even = Array.new
h.each_pair do |k, v|
  odd_once += 1 if v == 1 and k.odd?
  odd << k if v.odd?
  even << k if v == 1 and k.even?
end
puts "Number of elements with an odd value that appear only once: #{odd_once}", "\n"
puts "Elements repeated an odd number of times:"
puts odd.sort.reverse, "\n"
puts "Elements with an even value that appear exactly once:"
puts even.sort.reverse, "\n"

# print fib numbers in the hash
class Fixnum
  def is_fib?
    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.each do |n|
  puts n if n.is_fib?
end

Ответы [ 3 ]

5 голосов
/ 15 июня 2010

Я не знаю, является ли это "более рубиновым способом".По крайней мере, это более «более высокий порядок», 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. Мы выбираем все элементы в хэше, значение (то есть частота) которых составляет 1.Итак, мы выбираем все синглеты.
  2. мы отображаем все полученные пары ключ-значение только на первый элемент, то есть на число - IOW мы выбрасываем частоту.
  3. мы сортируемlist
  4. и затем перевернуть его (для больших списков мы должны сначала отсортировать в обратном порядке, так как это довольно пустая трата циклов ЦП и памяти)
  5. наконец, мы разбиваем массив на двамассивы, один из которых содержит все нечетные числа, а другой - все четные числа
  6. . Теперь мы наконец смотрим на левую сторону знака равенства: 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, я был бы признателен.

2 голосов
/ 15 июня 2010

Вот несколько идей о первой половине ...

STDIN.gets # just need to get past the first value - not needed, as already noticed

h = Hash.new(0)                    # use the default value argument to Hash#new, 
STDIN.each { |n| h[n.to_i] += 1 }  # so don't have to test for existence

# code seems to be seeking the largest, comment says "smallest"
# Hash#invert switches keys & values, works here if there's only one mode,
# otherwise presents one random key for the modal value
modal_value = h.values.max
mode = h.invert[modal_value]
puts "Mode is: #{mode}"

# OK, so mode may not be unique?
uniqueness = h.select { |k, v| v == modal_value}.size == 1 ? '' : 'NOT'
puts "Mode is #{uniqueness} unique"

#singleton odds ...
singleton_odd_count = h.select { |k,v| v == 1 && k % 2 == 1 }.map { |k, v| k }.size
# ...and evens
singleton_evens = h.select { |k,v| v == 1 && k % 2 == 0 }.map { |k, v| k }
# odd odd counts
odd_odd_count = h.select { |k,v| v % 2 == 1 && k % 2 == 1 }.map { |k, v| k }
1 голос
/ 15 июня 2010

Я думаю, что ваш код в основном в порядке. Единственное, что я вижу, что это неэффективно - это проверка Фибоначчи - вы каждый раз пересчитываете последовательность Фибоначчи, дублируя большую работу. Было бы разумнее вычислять массив чисел Фибоначчи один раз, вплоть до максимального значения в вашем входном наборе, а затем просто проверять каждое значение, чтобы увидеть, находится ли оно в этом массиве.

Кроме того, вы можете упростить инициализацию хеша сверху, передав значение Hash.new - это станет значением по умолчанию для любого ключа, который он еще не видел. Так что вы можете просто сделать:

h = Hash.new(0)
STDIN.each {|n| h[n.to_i] += 1}

В остальном проблем с индивидуальными вычислениями нет. То, что мне кажется "нерубиоподобным", это просто наличие одного сценария спагетти, который вычисляет полдюжины разных вещей и выплевывает их обратно. Было бы более элегантно обернуть каждую из этих различных математических функций в метод и поместить эти методы в модуль. И тогда ваш скрипт может просто включать модуль, вызывать методы с вашим хешем и выводить все, что ему нужно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...