web-dev-qa-db-fra.com

Ruby - convertit élégamment une variable en un tableau, si ce n'est déjà un tableau

À partir d'un tableau, d'un seul élément ou de nil, obtenez un tableau - les deux derniers étant respectivement un tableau à élément unique et un tableau vide.

J'ai pensé à tort que Ruby fonctionnerait de cette façon:

[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

Mais ce que vous obtenez vraiment, c'est:

[1,2,3].to_a  #= [1,2,3]         # Hooray
1.to_a        #= NoMethodError   # Do not want
nil.to_a      #= []              # Hooray

Donc, pour résoudre ce problème, je dois soit utiliser une autre méthode, soit méta-programme en modifiant la méthode to_a de toutes les classes que je compte utiliser - ce qui n'est pas une option pour moi.

Donc une méthode c'est:

result = nums.class == "Array".constantize ? nums : (nums.class == "NilClass".constantize ? [] : ([]<<nums))

Le problème est que c'est un peu le bordel. Y a-t-il une manière élégante de faire ceci? (Je serais étonné s'il s'agit de la manière la plus rubis de résoudre ce problème)


Quelles applications cela a-t-il? Pourquoi même convertir en tableau?

Dans ActiveRecord de Rails, appeler par exemple, user.posts renverra un tableau de messages, un message unique ou nil. Lors de l'écriture de méthodes qui travaillent sur les résultats, il est plus facile de supposer que la méthode utilisera un tableau pouvant comporter zéro, un ou plusieurs éléments. Exemple de méthode:

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

[*foo] Ou Array(foo) fonctionnera la plupart du temps, mais dans certains cas, comme un hachage, cela gâche tout.

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

La seule façon pour moi de penser que cela fonctionne même pour un hash est de définir une méthode.

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}]
139
sawa

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

Si vous n'utilisez pas Rails, vous pouvez définir votre propre méthode similaire à 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
105
elado

La solution la plus simple consiste à utiliser [foo].flatten(1). Contrairement aux autres solutions proposées, cela fonctionnera bien pour les tableaux (imbriqués), les hachages et 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'}]
20
olito

Array(whatever) devrait faire l'affaire

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

ActiveSupport (Rails)

ActiveSupport a une jolie méthode pour cela. Il est chargé avec Rails, donc le meilleur moyen de le faire:

Array.wrap([1, 2, 3]) #=> [1, 2, 3]
Array.wrap(nil) #=> nil

Splat (Ruby 1.9+)

L'opérateur splat (*) décompresse un tableau s'il peut:

*[1,2,3] #=> 1, 2, 3 (notice how this DOES not have braces)

Bien sûr, sans tableau, cela fait des choses étranges, et les objets que vous "splat" doivent être placés dans des tableaux. C'est un peu bizarre, mais cela signifie:

[*[1,2,3]] #=> [1, 2, 3]
[*5] #=> [5]
[*nil] #=> []
[*{meh: "meh"}] #=> [[:meh, "meh"], [:meh2, "lol"]]

Si vous n'avez pas ActiveSupport, vous pouvez définir la méthode:

class Array
    def self.wrap(object)
        [*object]
    end
end

Array.wrap([1, 2, 3]) #=> [1, 2, 3]
Array.wrap(nil) #=> nil

Bien que, si vous envisagez de disposer de grands tableaux et de moins d'éléments autres que des tableaux, vous voudrez peut-être le modifier - la méthode ci-dessus est lente avec les grands tableaux et peut même causer le débordement de votre pile (omg so méta). Quoi qu'il en soit, vous voudrez peut-être faire ceci à la place:

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]

J'ai aussi quelques benchmarks avec et sans l'opérateur teneray.

13
Ben Aubin

Que diriez-vous

[].Push(anything).flatten
6
Bruno Meira

Avec le risque de dire l'évidence, et sachant que ce n'est pas le sucre syntaxique le plus savoureux jamais vu sur la planète et ses environs, ce code semble faire exactement ce que vous décrivez:

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

J'ai parcouru toutes les réponses et la plupart du temps, je ne travaille pas dans Ruby 2+

Mais Elado a la solution la plus élégante c'est-à-dire

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

Malheureusement, cela ne fonctionne pas non plus pour Ruby 2+ car vous obtiendrez une erreur

undefined method `wrap' for Array:Class

Donc, pour résoudre ce problème, vous devez en avoir besoin.

nécessite 'active_support/deprecation'

require'support_actif/core_ext/array/wrap '

1
Malware Skiddie

vous pouvez écraser la méthode array de Object

class Object
    def to_a
        [self]
    end
end

tout hérite de Object, donc to_a sera maintenant défini pour tout sous le soleil

1
runub

Depuis la méthode #to_a existe déjà pour les deux principales classes problématiques (Nil et Hash), définissez simplement une méthode pour le reste en développant Object:

class Object
    def to_a
        [self]
    end
end

et alors vous pouvez facilement appeler cette méthode sur n’importe quel objet:

"Hello world".to_a
# => ["Hello world"]
123.to_a
# => [123]
{a:1, b:2}.to_a
# => [[:a, 1], [:b, 2]] 
nil.to_a
# => []
0
Shoe