首页 文章

Ruby - 如果不是数组,则优雅地将变量转换为数组

提问于
浏览
108

给定一个数组,单个元素或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-ish方法,我会很惊讶)


这有什么应用?为什么甚至转换为阵列?

在Rails的ActiveRecord中,调用say, user.posts 将返回一个帖子数组,一个帖子或nil . 在编写处理结果的方法时,最简单的假设该方法将采用一个数组,该数组可能包含零个,一个或多个元素 . 示例方法:

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

10 回答

  • 17

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

    Array(whatever) 应该做的伎俩

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

    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)
    

    当然,没有数组,它会做奇怪的事情,而你“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
    

    虽然,如果你计划拥有大型数组,而不是非数组的东西,你可能想要改变它 - 上面的方法对于大型数组来说很慢,甚至可能导致你的Stack溢出(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]
    

    我也有一些benchmarks有和没有teneray操作员 .

  • 20

    最简单的解决方案是使用 [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'}]
    
  • 131

    由于存在陈述显而易见的风险,并且知道这不是地球和周围地区有史以来最美味的语法糖,因此这段代码似乎正如您所描述的那样:

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

    由于方法 #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
    # => []
    
  • 0

    你可以覆盖Object的数组方法

    class Object
        def to_a
            [self]
        end
    end
    

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

  • 2

    怎么样

    [].push(anything).flatten
    
  • -1

    我已经完成了所有的答案,而且大部分都不适用于红宝石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}]

    可悲的是,这对红宝石2也不起作用,因为你会收到一个错误

    undefined method `wrap' for Array:Class
    

    所以为了解决你需要的问题 .

    require'active_support / deprecation'require'active_support / core_ext / array / wrap'

  • 1

    使用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,则可以定义类似于the 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
    

相关问题