136

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

我错误地认为 Ruby 会这样工作:

[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 中,调用 sayuser.posts将返回一个帖子数组、单个帖子或 nil。在编写处理其结果的方法时,最容易假设该方法将采用一个数组,该数组可能有零个、一个或多个元素。示例方法:

current_user.posts.inject(true) {|result, element| result and (element.some_boolean_condition)}
4

10 回答 10

170

[*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}]
于 2013-08-21T13:35:28.417 回答
136

使用 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,您可以定义自己的方法,类似于rails source

class Array
  def self.wrap(object)
    if object.nil?
      []
    elsif object.respond_to?(:to_ary)
      object.to_ary || [object]
    else
      [object]
    end
  end
end
于 2014-03-16T04:59:06.333 回答
21

最简单的解决方案是使用[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'}]
于 2015-04-03T11:38:14.637 回答
20

Array(whatever)应该做的伎俩

Array([1,2,3]) # [1,2,3]
Array(nil) # []
Array(1337)   # [1337]
于 2013-08-21T13:28:21.740 回答
13

ActiveSupport(导轨)

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)

当然,如果没有数组,它会做一些奇怪的事情,并且你“splat”的对象需要放入数组中。这有点奇怪,但这意味着:

[*[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]

我也有一些带有和不带有 Teneray 运算符的基准。

于 2015-11-17T00:09:26.193 回答
7

怎么样

[].push(anything).flatten
于 2014-11-09T19:12:33.123 回答
2

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

foo = foo.is_a?(Array) ? foo : foo.nil? ? [] : [foo]
于 2015-08-11T13:54:36.257 回答
1

你可以覆盖对象的数组方法

class Object
    def to_a
        [self]
    end
end

一切都继承 Object,因此 to_a 现在将为阳光下的一切定义

于 2016-02-08T15:57:35.767 回答
1

我已经完成了所有的答案,而且大部分都不适用于 ruby​​ 2+

但是 elado 有最优雅的解决方案,即

使用 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}]

可悲的是,但这也不适用于 ruby​​ 2+,因为你会得到一个错误

undefined method `wrap' for Array:Class

因此,为了解决您需要的问题。

需要'active_support/deprecation'

需要'active_support/core_ext/array/wrap'

于 2017-06-06T10:17:59.327 回答
0

由于两个主要有问题的类(和)#to_a已经存在该方法,因此只需通过扩展为其余类定义一个方法:NilHashObject

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
# => []
于 2013-08-21T14:04:16.567 回答