Ruby-优雅地将变量转换为数组(如果还没有数组的话)

给定一个数组、单个元素或者 nil,获得一个数组——后两个分别是单个元素数组和空数组。

我误以为露比会这么做:

[1,2,3].to_a  #= [1,2,3]     # Already an array, so no change
1.to_a        #= [1]         # Creates an array and adds element
nil.to_a      #= []          # Creates empty array

但你真正得到的是:

[1,2,3].to_a  #= [1,2,3]         # Hooray
1.to_a        #= NoMethodError   # Do not want
nil.to_a      #= []              # Hooray

所以为了解决这个问题,我要么需要使用另一个方法,要么可以通过修改所有我打算使用的类的 to _ a 方法来元程序——这对我来说不是一个选项。

所以这是一种方法:

result = nums.class == "Array".constantize ? nums : (nums.class == "NilClass".constantize ? [] : ([]<<nums))

问题是这里有点乱。有没有一种优雅的方式来做这件事?(如果这是解决这个问题的 Ruby 式方法,我会感到惊讶)


它有什么应用程序? 为什么要转换成数组?

在 Rails 的 ActiveRecord 中,调用 say 时,user.posts将返回一个 post 数组、一个 post 或者 nil。当编写处理这个结果的方法时,最容易假设该方法将采用一个数组,该数组可能包含零个、一个或多个元素。示例方法:

current_user.posts.inject(true) {|result, element| result and (element.some_boolean_condition)}
97661 次浏览

Array(whatever)应该可以

Array([1,2,3]) # [1,2,3]
Array(nil) # []
Array(1337)   # [1337]

[*foo]Array(foo)在大多数情况下都能正常工作,但是在某些情况下,比如散列,它会把事情搞砸。

Array([1, 2, 3])    # => [1, 2, 3]
Array(1)            # => [1]
Array(nil)          # => []
Array({a: 1, b: 2}) # => [[:a, 1], [:b, 2]]


[*[1, 2, 3]]    # => [1, 2, 3]
[*1]            # => [1]
[*nil]          # => []
[*{a: 1, b: 2}] # => [[:a, 1], [:b, 2]]

我能想到的唯一可行的方法是定义一个方法,即使是散列也是如此。

class Object; def ensure_array; [self] end end
class Array; def ensure_array; to_a end end
class NilClass; def ensure_array; to_a end end


[1, 2, 3].ensure_array    # => [1, 2, 3]
1.ensure_array            # => [1]
nil.ensure_array          # => []
{a: 1, b: 2}.ensure_array # => [{a: 1, b: 2}]

由于两个主要问题类(NilHash)的方法 #to_a已经存在,所以只需通过扩展 Object为其余类定义一个方法:

class Object
def to_a
[self]
end
end

然后你可以很容易地在任何对象上调用这个方法:

"Hello world".to_a
# => ["Hello world"]
123.to_a
# => [123]
{a:1, b:2}.to_a
# => [[:a, 1], [:b, 2]]
nil.to_a
# => []

使用 ActiveSupport (Rails) : Array.wrap

Array.wrap([1, 2, 3])     # => [1, 2, 3]
Array.wrap(1)             # => [1]
Array.wrap(nil)           # => []
Array.wrap({a: 1, b: 2})  # => [{:a=>1, :b=>2}]

如果不使用 Rails,可以定义类似于 铁路来源的自己的方法。

class Array
def self.wrap(object)
if object.nil?
[]
elsif object.respond_to?(:to_ary)
object.to_ary || [object]
else
[object]
end
end
end

怎么样

[].push(anything).flatten

最简单的解决方案是使用 [foo].flatten(1)。与其他提议的解决方案不同,它可以很好地应用于(嵌套的)数组、散列和 nil:

def wrap(foo)
[foo].flatten(1)
end


wrap([1,2,3])         #= [1,2,3]
wrap([[1,2],[3,4]])   #= [[1,2],[3,4]]
wrap(1)               #= [1]
wrap(nil)             #= [nil]
wrap({key: 'value'})  #= [{key: 'value'}]

冒着陈述显而易见的事实的风险,并且知道这并不是地球上和周围地区所见过的最美味的语法糖,这段代码似乎完全符合您的描述:

foo = foo.is_a?(Array) ? foo : foo.nil? ? [] : [foo]

ActiveSupport (Rails)

ActiveSupport 有一个相当不错的方法来解决这个问题,它是用 Rails 加载的,所以大胆地说最好的方法是:

Array.wrap([1, 2, 3]) #=> [1, 2, 3]
Array.wrap(nil) #=> nil

Splat (Ruby 1.9 +)

Splat 操作符(*)取消数组,如果可以的话:

*[1,2,3] #=> 1, 2, 3 (notice how this DOES not have braces)

当然,如果没有数组,它会做一些奇怪的事情,并且需要将“打开”的对象放入数组中。有点奇怪,但它的意思是:

[*[1,2,3]] #=> [1, 2, 3]
[*5] #=> [5]
[*nil] #=> []
[*{meh: "meh"}] #=> [[:meh, "meh"], [:meh2, "lol"]]

如果没有 ActiveSupport,可以定义这个方法:

class Array
def self.wrap(object)
[*object]
end
end


Array.wrap([1, 2, 3]) #=> [1, 2, 3]
Array.wrap(nil) #=> nil

尽管如此,如果您计划使用大型数组,并减少非数组的东西,您可能想要更改它-上述方法对于大型数组来说速度很慢,甚至可能导致堆栈溢出(omg so meta)。不管怎样,你可能会想这样做:

class Array
def self.wrap(object)
object.is_a? Array ? object : [*object]
end
end


Array.wrap([1, 2, 3]) #=> [1, 2, 3]
Array.wrap(nil) #=> [nil]

我也有一些 基准有和没有特纳雷算符。

可以覆盖 Object 的 array 方法

class Object
def to_a
[self]
end
end

Everything 继承 Object,因此 to _ a 将为太阳下的所有内容定义

我已经看过了所有的答案,但大多数都不适用于 ruby 2 +

但 Elado 有最好的解决方案

使用 ActiveSupport (Rails) : Array.wrap

外壳([1,2,3]) # = > [1,2,3]

外观(1) # = > [1]

外壳(nil) # = > []

外包({ a: 1,b: 2}) # = > [{ : a = > 1,: b = > 2}]

遗憾的是,这也不适用于 ruby 2 + ,因为你会得到一个错误

undefined method `wrap' for Array:Class

所以为了解决这个问题。

要求‘ active _ support/depreation’

需要‘ active _ support/core _ ext/array/wrash’