编写此代码的更“红宝石”是什么?

时间:2010-06-15 06:37:16

标签: ruby coding-style

这是我的学生(我是助教)的家庭作业,我正在尝试学习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 个答案:

答案 0 :(得分:5)

我不知道这是否是“更多Ruby方式”。至少at是一种更“高阶”的方式,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来删除第一行。

来自Perl的

ARGF是一个非常酷的功能(如大多数脚本功能):如果您只是将脚本称为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#firstArray#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. 我们将所有生成的键值对映射到它们的第一个元素,即数字 - 我们扔掉频率。
  3. 我们对列表进行排序
  4. 然后将其反转(对于较大的列表,我们应该反向排序,因为这会浪费CPU周期和内存)
  5. 最后,我们将数组分成两个数组,一个包含所有奇数,另一个包含所有偶数
  6. 现在我们终于查看等号的左侧:Enumerable#partition返回一个包含带有分区元素的两个数组的双元素数组,我们使用Ruby的解构赋值将两个数组分配给两个变量

    puts“具有奇数值但仅出现一次的元素数量:#{odds.size}”

  7. 现在我们有一个奇怪的单身人士列表,他们的号码只是列表的大小。

    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
    

    我不觉得重构这个算法更有效,特别是memoize。我做了一些小调整。

    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(', ')}"
    

    这可能不需要太多解释:拿钥匙,对它们进行排序,选择所有斐波那契数字并用逗号连接起来。

    以下是如何重构此算法的想法。 a very interesting implementation of Fibonacci使用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 = 0i += 1和整个until循环,我会很感激。

答案 1 :(得分:2)

以上是关于上半场的一些想法......

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 }

答案 2 :(得分:1)

我认为你的代码很好。我唯一能看到效率低下的就是你的斐波那契检查 - 你每次都重新计算斐波纳契数,重复大量的工作。计算Fibonacci数的数组一次,直到输入集中的最大值更有意义,然后检查每个值以查看它是否在该数组中。

此外,您可以通过将值传递给Hash.new来简化顶部的哈希初始化 - 这将成为尚未看到的任何键的默认值。所以你可以这样做:

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

否则,单个计算没有问题。让我感到震惊的是“un-Rubylike”只是有一个意大利面条脚本,它可以计算出六种不同的东西并将它们吐出来。将每个不同的数学函数包装到一个方法中会更加优雅,并将这些方法放入一个模块中。然后你的脚本可以只包含模块,用你的哈希调用方法,并输出它需要的任何东西。