4

我有一个 Ruby 方法,如下所示:

# Retrieve all fruits from basket that are of the specified kind.
def fruits_of_kind(kind)
  basket.select { |f| f.fruit_type == kind.to_s }
end

现在,您可以这样称呼它:

fruits_of_kind(:apple)    # => all apples in basket
fruits_of_kind('banana')  # => all bananas in basket

等等。

如何更改方法以使其正确处理可迭代输入以及无输入和零输入?例如,我希望能够支持:

fruits_of_kind(nil) # => nil
fruits_of_kind(:apple, :banana)    # => all apples and bananas in basket
fruits_of_kind([:apple, 'banana']) # => likewise

这可以惯用地做吗?如果是这样,编写方法以使它们可以接受零个、一个或多个输入的最佳方法是什么?

4

4 回答 4

3

您需要使用 Ruby splat 运算符,它将所有剩余的参数包装到一个数组中并将它们传递给:

def foo (a, b, *c)
  #do stuff
end

foo(1, 2) # a = 1, b = 2, c = []
foo(1, 2, 3, 4, 5) #a = 1, b = 2, c = [3, 4, 5]

在你的情况下,这样的事情应该有效:

def fruits_of_kind(*kinds)
  kinds.flatten!
  basket.select do |fruit|
    kinds.each do |kind|
      break true if fruit.fruit_type == kind.to_s
    end == true #if no match is found, each returns the whole array, so == true returns false
  end
end

编辑

我将代码更改为扁平化类型,以便您可以发送列表。这段代码根本不处理任何类型的输入,但如果你想明确输入,请在开头nil添加该行。kinds = [] if kinds.nil?

于 2009-05-08T15:18:19.557 回答
2

使用 Ruby 的 VARARGS 功能。

# Retrieve all fruits from basket that are of the specified kind.
# notice the * prefix used for method parameter
def fruits_of_kind(*kind)
  kind.each do |x|
    puts x
  end
end

fruits_of_kind(:apple, :orange)
fruits_of_kind()
fruits_of_kind(nil)

-佐助

于 2009-05-08T15:08:51.873 回答
1
def fruits_of_kind(kind)
    return nil if kind.nil?

    result = []

    ([] << kind).flatten.each{|k| result << basket.select{|f| f.fruit_type == k.to_s }}

    result
end

'splat' 运算符可能是最好的方法,但有两件事需要注意:传入 nil 或列表。要针对您想要的输入/输出修改 Pesto 的解决方案,您应该执行以下操作:

def fruits_of_kind(*kinds)
  return nil if kinds.compact.empty? 

  basket.select do |fruit|
    kinds.flatten.each do |kind|
      break true if fruit.fruit_type == kind.to_s
    end == true #if no match is found, each returns the whole array, so == true returns false
  end
end

如果传入 nil,* 会将其转换为 [nil]。如果要返回 nil 而不是空列表,则必须将其压缩(删除空值)为 [],然后如果为空则返回 nil。

如果您传入一个列表,例如 [:apple, 'banana'],* 会将其转换为 [[:apple, 'banana']]。这是一个细微的区别,但它是一个包含另一个列表的单元素列表,因此您需要在执行“每个”循环之前展平种类。展平会将其转换为 [:apple, 'banana'],就像您期望的那样,并为您提供您正在寻找的结果。

编辑:更好,感谢格雷格坎贝尔:

   def fruits_of_kind(basket, kind)
       return nil if kind.nil?

       kind_list = ([] << kind).flatten.map{|kind| kind.to_s}

       basket.select{|fruit| kind_list.include?(fruit) } 
   end

或(使用 splat)

   def fruits_of_kind(*kinds)
       return nil if kinds.compact.empty?

       kind_list = kinds.flatten.map{|kind| kind.to_s}

       basket.select{|fruit| kind_list.include?(fruit.fruit_type) } 
   end
于 2009-05-08T15:17:50.650 回答
0

有一个很好的表达使用 splat 作为数组创建的参数来处理你的最后一个例子:

def foo(may_or_may_not_be_enumerable_arg)
  arrayified = [*may_or_may_not_be_enumerable_arg]
  arrayified.each do |item|
    puts item
  end
end

obj = "one thing"
objs = ["multiple", "things", 1, 2, 3]

foo(obj)
# one thing
# => ["one thing"]

foo(objs)
# multiple
# things
# 1
# 2
# 3
# => ["multiple", "things", 1, 2, 3]
于 2014-01-07T23:15:33.250 回答