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

ste*_*uck 4 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
Run Code Online (Sandbox Code Playgroud)

Jör*_*tag 5

我不知道这是否是"更多Ruby方式".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 }}
Run Code Online (Sandbox Code Playgroud)

这里不多说.我们不是简单地循环ARGF并设置哈希键,而是reduce让它为我们工作.我们使用具有默认值的散列0而不是手动检查密钥是否存在.

我们使用Enumerable#drop简单地删除第一行.

ARGFPerl是一个非常酷的功能(像大多数脚本功能一样):如果你只是简单地调用脚本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}"
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

]

modes现在是一个排序数组,其中包含具有该特定频率的所有数字.如果我们采用第一个元素,我们有最小的模式.

# mode unique?
puts "Mode is #{unless modes.size == 1 then '*not* ' end}unique."
Run Code Online (Sandbox Code Playgroud)

如果数组的大小不是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?)
Run Code Online (Sandbox Code Playgroud)

看起来这里有很多东西,但它实际上很简单.您在等号后开始阅读,只需从左向右阅读.

  1. 我们选择哈希值中的所有项目(即频率)1.我,我们选择所有的单身人士.
  2. 我们将所有得到的键值对映射到它们的第一个元素,即数字 - 我们扔掉频率.
  3. 我们对列表进行排序
  4. 然后将其反转(对于较大的列表,我们应该反向排序,因为这会浪费CPU周期和内存)
  5. 最后,我们将数组分成两个数组,一个包含所有奇数,另一个包含所有偶数
  6. 现在我们终于查看等号的左侧:Enumerable#partition返回一个包含带有分区元素的两个数组的双元素数组,我们使用Ruby的解构赋值将两个数组分配给两个变量

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

现在我们有一个奇怪的单例列表,它们的数量就是列表的大小.

puts "Elements repeated an odd number of times: #{
  h.select {|_, f| f.odd?}.map(&:first).sort.reverse.join(', ')
}"
Run Code Online (Sandbox Code Playgroud)

这与上面非常类似:选择所有具有奇数频率的数字,映射出键(即数字),排序,反向,然后通过将它们连接在一起,将它们之间的逗号和空格连接在一起.

puts "Elements with an even value that appear exactly once: #{evens.join(', ')}"
Run Code Online (Sandbox Code Playgroud)

再说一遍,既然我们有一个甚至单身的列表,打印它们只是用逗号连接列表元素的问题.

# print fib numbers in the hash
Run Code Online (Sandbox Code Playgroud)

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

class Integer
Run Code Online (Sandbox Code Playgroud)

算法中没有任何东西依赖于一定大小的数字,所以我把方法拉到了Integer课堂上.

  def fib?
Run Code Online (Sandbox Code Playgroud)

我删掉了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(', ')}"
Run Code Online (Sandbox Code Playgroud)

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

以下是如何重构此算法的想法.Fibonacci一个非常有趣的实现,使用Hash默认值进行memoizing:

fibs = {0 => 0, 1 => 1}.tap do |fibs|
  fibs.default_proc = ->(fibs, n) { fibs[n] = fibs[n-1] + fibs[n-2] }
end
Run Code Online (Sandbox Code Playgroud)

它看起来有点像这样:

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(', ')}"
Run Code Online (Sandbox Code Playgroud)

如果任何人能想到的一个优雅的方式来摆脱的i = 0,i += 1和整个until环,我将不胜感激.