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
# => []