【问题标题】:Array interpreted as a Fixnum数组解释为 Fixnum
【发布时间】:2023-04-03 22:11:02
【问题描述】:

我目前正在学习 ruby​​,我写了这段代码:

def multi_gen
  s = []
  for i in (3..10)
    if i%3 == 0 || i%5 == 0
      s<<i
    end
  end
  return s
end

puts multi_gen

def rec_sum(num_arr)
  if num_arr == []
    return 0
  else
    num_arr.first + rec_sum(num_arr.shift)
  end
end

puts rec_sum(multi_gen)

这应该返回所有 3 和 5 的倍数之和,最大为 1000。

但我得到一个错误:

myrbfile.rb:17:in `rec_sum': undefined method `first' for 3:Fixnum (NoMethodError)
        from villani.rb:17:in `rec_sum'
        from villani.rb:21:in `<main>'

但是当我这样重写它时:

def multi_gen
  s = []
  for i in (3..10)
    if i%3 == 0 || i%5 == 0
      s<<i
    end
  end
  return s
end

puts multi_gen

def rec_sum(num_arr)
  if num_arr == []
    return 0
  else
    num_arr[0] + rec_sum(num_arr[1..num_arr.last])
  end
end

puts rec_sum(multi_gen)

我没有得到错误。

那么为什么我的第一个 rec_sum 函数在第一种情况下将我的 Array 解释为 Fixnum?

【问题讨论】:

    标签: arrays ruby fixnum


    【解决方案1】:

    问题出在递归调用中:

    rec_sum(num_arr.shift)
    

    Array#shift 返回移位的元素,而不是剩余的数组。您应该将数组作为参数显式传递给递归调用:

    rec_sum(num_arr[1..-1])
    

    rec_sum(num_arr.tap(&:shift))
    

    后者 [可能] 对于初学者来说看起来太麻烦了,但这是一种非常常见的 ruby​​ish 方法:Object#tap 将接收器交给块,返回接收器。在块内(num_arr.tap(&amp;:shift)num_arr.tap { |a| a.shift } 的简写,我们通过将元素移出来改变数组,并作为结果返回。

    【讨论】:

    • “我们改变了数组”——我认为我们不想这样做。至少不是原来的。
    • @Stefan 最初在 OP 中有 shift,我只是重现了预期的行为。
    • 查看 OP 中的第二个(工作)实现,我认为 shift 引入的副作用是无意的。
    【解决方案2】:

    mudasobwa already explained 为什么使用 shift 没有给出预期的结果。除此之外,您的代码在某种程度上是单调的。

    multi_gen 中,您正在创建一个空数组并使用for 循环向其追加元素。您很少需要手动填充数组。相反,您通常可以使用 Ruby 的 ArrayEnumerable 方法之一来生成数组。 select 是一个非常常见的 - 它返回一个数组,其中包含给定块返回 true 的元素:

    (1..1000).select { |i| i % 3 == 0 || i % 5 == 0 }
    #=> [3, 5, 6, 9, 10, 12, ...]
    

    rec_sum 中,您检查if num_arr == []。虽然这可行,但您正在创建一个空的一次性数组。要判断一个数组是否为空,你应该调用它的empty?

    if num_arr.empty?
      # ...
    end
    

    要从数组中获取剩余元素,请使用:

    num_arr[1..num_arr.last]
    

    可以通过将负索引传递给[]来缩写:

    num_arr[1..-1]
    

    还有drop 可能看起来更好一点:

    num_arr[0] + rec_sum(num_arr[1..-1])
    # vs
    num_arr.first + rec_sum(num_arr.drop(1))
    

    另一个从数组中获取第一个和剩余元素的选项是 Ruby 的array decomposition 功能(注意*):

    def rec_sum(num_arr)
      if num_arr.empty?
        0
      else
        first, *remaining = num_arr
        first + rec_sum(remaining)
      end
    end
    

    您也可以考虑使用guard clause 提前从该方法返回:

    def rec_sum(num_arr)
      return 0 if num_arr.empty?
      first, *remaining = num_arr
      first + rec_sum(remaining)
    end
    

    编写递归方法非常适合用于学习目的,但 Ruby 也有一个内置的 sum 方法:

    multi_gen.sum #=> 234168
    

    或者——因为你使用的是旧的 Ruby 版本——inject

    multi_gen.inject(0, :+) #=> 234168
    

    【讨论】:

    • 感谢您的回答!我现在明白了,我会尝试改进我的语法并开始使用更多 Ruby 功能:)
    • @SuperTotoGo 不客气,但你应该给 mudasobwa 打勾。我的回答更多的是作为补充。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-12
    • 1970-01-01
    相关资源
    最近更新 更多