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))

Проблема в том, что это немного беспорядок. Есть ли элегантный способ сделать это? (Я был бы поражен, если это рубино-иш-способ решения этой проблемы)


Какие приложения у этого есть? Почему даже преобразовать в массив?

В RRIDA ActiveRecord, вызывающий say, user.posts будет либо возвращать массив сообщений, один пост, либо nil. При написании методов, которые работают с результатами этого, проще всего предположить, что метод примет массив, который может иметь нулевой, один или несколько элементов. Пример метода:

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

Ответ 1

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

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

Ответ 3

Array(whatever) должен сделать трюк

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

Ответ 4

Самое простое решение - использовать [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'}]

Ответ 5

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

Хотя, если вы планируете иметь большие массивы и меньше не-массивные вещи, вы можете захотеть их изменить - вышеупомянутый метод медленный с большими массивами и даже может привести к переполнению стека (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.

Ответ 6

Как насчет

[].push(anything).flatten

Ответ 7

С риском заявить очевидное и зная, что это не самый вкусный синтаксический сахар, когда-либо виденный на планете и прилегающих территориях, этот код, похоже, делает именно то, что вы описываете:

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

Ответ 8

Я прошел все ответы и в основном не работал в ruby ​​2 +

Но у elado есть самое элегантное решение i.e

С 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"

Ответ 9

вы можете перезаписать метод массива Object

class Object
    def to_a
        [self]
    end
end

все наследует Object, поэтому to_a теперь будет определяться для всего, что находится под солнцем

Ответ 10

Так как метод #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
# => []