Ruby – 优雅地将variables转换为数组,如果不是数组的话
给定一个数组,单个元素或零,获得一个数组 – 后两个分别是一个单一的元素数组和一个空的数组。
我错误地认为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方法来进行元程序 – 这对我来说不是一种select。
所以一个方法是:
result = nums.class == "Array".constantize ? nums : (nums.class == "NilClass".constantize ? [] : ([]<<nums))
问题是这是一个混乱。 有没有一个这样做的优雅方式? (如果这是Ruby-ish方法来解决这个问题,我会感到惊讶)
这有什么应用程序? 为什么甚至转换为数组?
在Rails的ActiveRecord中,调用说, user.posts
将返回一个post数组,单个post,或零。 在编写处理这个结果的方法时,最简单的方法就是假设这个方法需要一个可能有零个,一个或多个元素的数组。 示例方法:
current_user.posts.inject(true) {|result, element| result and (element.some_boolean_condition)}
[*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}]
使用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}]
Array(whatever)
应该做的伎俩
Array([1,2,3]) # [1,2,3] Array(nil) # [] Array(1337) # [1337]
最简单的解决scheme是使用[foo].flatten(1)
。 不同于其他提议的解决scheme,它将适用于(嵌套)数组,哈希和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'}]
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]
我也有一些基准和没有teneray操作符。
怎么样
[].push(anything).flatten
由于存在明显的风险,并且知道这不是地球上及周边地区所见过的最美味的语法糖,这段代码似乎完全符合您的描述:
foo = foo.is_a?(Array) ? foo : foo.nil? ? [] : [foo]
你可以覆盖Object的数组方法
class Object def to_a [self] end end
一切都inheritance了Object,所以to_a现在将被定义为sun的所有东西
我已经经历了所有的答案,并且大多不在Ruby +中工作
但是,埃多拥有最优雅的解决scheme,即
使用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}]
可悲的是,但这也不适用于ruby2+,因为你会得到一个错误
undefined method `wrap' for Array:Class
所以为了解决你需要的。
要求“active_support / deprecation”
需要'active_support / core_ext / array / wrap'
由于#to_a
方法已经存在于两个主要有问题的类( Nil
和Hash
)中,所以通过扩展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 # => []