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


119

给定一个数组,则单个元素或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中,调用say user.posts会返回一组帖子,单个帖子或nil。当编写根据此结果工作的方法时,最容易假设该方法将采用一个数组,该数组可能具有零个,一个或多个元素。示例方法:

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

2
user.posts永远不要返回任何帖子。至少我没看过。
塞尔吉奥·图伦采夫

1
我认为在您的前两个代码块中,您的意思==不是=,对吧?
Patrick Oscity


3
顺便说一句,[1,2,3].to_a没有返回[[1,2,3]]!它返回[1,2,3]
Patrick Oscity

感谢桨,将更新问题... 自我的facepalms
xxjjnn

Answers:


152

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

2
而不是ensure_array延长to_a
Dan Grahn 2013年

9
@screenmutt这会影响依赖最初使用的方法to_a。例如,{a: 1, b: 2}.each ...将以不同的方式工作。
sawa 2013年

1
您能解释一下这种语法吗?在Ruby的许多年中,我从未遇到过此类调用。类名的括号是做什么的?我在文档中找不到这个。
mastaBlasta

1
@mastaBlasta Array(arg)尝试通过先调用to_ary,然后再调用to_a来创建新数组。这在正式的ruby文档中有记录。我从Avdi的“ Confident Ruby”一书中学到了这一点。
曼波舞

2
@mambo在发布问题后的某个时候,我确实找到了答案。困难的部分是它与Array类无关,但它是内核模块上的一种方法。ruby-doc.org/core-2.3.1/Kernel.html#method-i-Array
mastaBlasta

119

使用ActiveSupport(导轨): 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

12
class Array; singleton_class.send(:alias_method, :hug, :wrap); end更可爱。
rthbound 2015年

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'}]

不幸的是,与其他方法相比,该方法存在严重的性能问题。Kernel#ArrayArray()是所有这些中最快的。Ruby 2.5.1比较:Array():7936825.7 i / s。Array.wrap:4199036.2 i / s-慢1.89倍 换行:644030.4 i / s-慢12.32x
Wasif Hossain


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)

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

[*[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 meta 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运算符,我都有一些基准


不适用于大型阵列。SystemStackError: stack level too deep用于1M元素(红宝石2.2.3)。
denis.peplin 2015年

@ denis.peplin似乎您遇到了StackOverflow错误:D-老实说,我不确定发生了什么。抱歉。
本·奥宾2015年

我最近尝试Hash#values_at使用1M参数(使用splat),并引发了相同的错误。
denis.peplin,2015年

@ denis.peplin是否可以使用object.is_a? Array ? object : [*object]
本·奥宾

1
Array.wrap(nil)[]不返回nil:/
Aeramor

7

怎么样

[].push(anything).flatten

2
是的,我想我最终在自己的情况下使用了[anything] .flatten ...但对于一般情况,这也会使所有嵌套的数组结构平坦化
xxjjnn 2014年

1
[].push(anything).flatten(1)会工作!它不会展平嵌套数组!
xxjjnn '16

2

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

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

1

您可以覆盖Object的数组方法

class Object
    def to_a
        [self]
    end
end

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


3
亵渎猴子修补!pent悔!
xxjjnn

1

我已经仔细研究了所有的答案,但大多数情况下都无法在ruby中工作2+

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

使用ActiveSupport(导轨):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'


0

由于该方法#to_a已经存在于两个主要有问题的类(NilHash),因此只需扩展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
# => []

5
我确实认为应该避免猴子修补核心Ruby类,尤其是对象。我会给ActiveSupport通过,所以考虑我为伪君子。@sawa的上述解决方案比这更可行。
pho3nixf1re 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.