Ruby-配列がまだない場合は、変数を配列にエレガントに変換します


119

配列、単一の要素、またはnilを指定して、配列を取得します-後者の2つは、それぞれ単一の要素の配列と空の配列です。

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風の方法であるとしたら、私は驚きます)


これにはどのようなアプリケーションがありますか?なぜ配列に変換するのですか?

RailsのActiveRecordでは、sayを呼び出すと、user.posts投稿の配列、単一の投稿、またはnilが返されます。この結果を処理するメソッドを作成する場合、メソッドが0、1、または多くの要素を持つ可能性のある配列を取ると想定するのが最も簡単です。メソッドの例:

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

2
user.posts単一の投稿を返すことはありません。少なくとも、私はそれを見たことがない。
Sergio Tulentsev 2013

1
最初の2つのコードブロック==では=、の代わりに、と思いますよね?
Patrick Oscity 2013


3
ところで、戻り[1,2,3].to_aませ[[1,2,3]]!戻ります[1,2,3]
Patrick Oscity 2013

パドルに感謝、質問を更新します... 自分で
facepalms

回答:


152

[*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
の代わりにensure_array、延長to_a
Dan Grahn 2013

9
@screenmuttこれは、の元の使用に依存するメソッドに影響しますto_a。たとえば、{a: 1, b: 2}.each ...動作が異なります。
佐和2013

1
この構文を説明できますか?Rubyの長い年月の間、私はこのタイプの呼び出しに出くわしたことはありませんでした。クラス名の括弧は何をしますか?私はこれをドキュメントで見つけることができません。
mastaBlasta 2015年

1
@mastaBlasta Array(arg)は、引数にto_aryを呼び出し、次にto_aを呼び出して新しい配列を作成しようとします。これは公式のrubyドキュメントに文書化されています。それについては、Avdiの「Confident Ruby」の本から学びました。
マンボ

2
@mambo私が質問を投稿した後のある時点で、私は答えを見つけました。難しいのは、Arrayクラスとは何の関係もないことですが、カーネルモジュールのメソッドです。ruby-doc.org/core-2.3.1/Kernel.html#method-i-Array
mastaBlasta

119

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を使用していない場合は、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

12
class Array; singleton_class.send(:alias_method, :hug, :wrap); end余分な可愛さのために。
rthbound、2015

21

最も簡単な解決策は、を使用すること[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'}]

残念ながら、これは他のアプローチに比べて深刻なパフォーマンスの問題があります。Kernel#ArrayすなわちArray()、それらすべての中で最速です。Ruby 2.5.1の比較:Array():7936825.7 i / s。Array.wrap:4199036.2 i / s-1.89x遅い。ラップ:644030.4 i / s-12.32x遅い
Wasif Hossain

19

Array(whatever) トリックを行う必要があります

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

14
ハッシュでは機能しません。Array({a:1、b:2})は[[:a、1]、[:b、2]]になります
davispuh

13

ActiveSupport(レール)

ActiveSupportには、このための非常に優れたメソッドがあります。これにはRailsがロードされているので、これを行うための最も優れた方法です。

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

スプラット(Ruby 1.9以降)

スプラット演算子(*)は、可能であれば配列の配列を解除します。

*[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)。とにかく、あなたは代わりにこれをしたいかもしれません:

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オペレーターを使用した場合と使用しない場合のベンチマークもあります


大きな配列では機能しません。SystemStackError: stack level too deep100万要素(ルビー2.2.3)。
denis.peplin 2015年

@ denis.peplinはStackOverflowエラーが発生したようです:D-正直なところ、何が起こったのかわかりません。ごめんなさい。
Ben Aubin

最近Hash#values_at、1Mの引数(を使用splat)を試しましたが、同じエラーが発生します。
denis.peplin

@ denis.peplin動作しobject.is_a? Array ? object : [*object]ますか?
Ben Aubin

1
Array.wrap(nil)返さ[]ないnil:/
Aeramor

7

いかがですか

[].push(anything).flatten

2
ええ、私は私の場合は[anything] .flattenを使用してしまったと思います...しかし、一般的なケースでは、ネストされた配列構造もフラット化します
xxjjnn

1
[].push(anything).flatten(1)うまくいくでしょう!ネストされた配列をフラット化しません!
xxjjnn

2

明白なことを述べるリスクがあり、これがこれまでに惑星や周辺地域で見られた中で最も美味しい構文上の砂糖ではないことを知っている場合、このコードはあなたが説明したとおりに動作するようです:

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

1

Objectの配列メソッドを上書きできます

class Object
    def to_a
        [self]
    end
end

すべてがオブジェクトを継承するため、太陽の下ですべてに対してto_aが定義されます。


3
冒涜的なサルのパッチ!あなたがたを悔い改めなさい!
xxjjnn 2016

1

私はすべての答えを調べましたが、ほとんどの場合ruby 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}]

悲しいことに、これはエラーが発生するため、Ruby 2+でも機能しません。

undefined method `wrap' for Array:Class

だからあなたが必要とすることを修正するために。

「active_support / deprecation」が必要

'active_support / core_ext / array / wrap'が必要


0

メソッド#to_aは2つの主要な問題のあるクラス(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
# => []

5
コアのRubyクラス、特にオブジェクトにサルがパッチを適用することは避けるべきだと私は本当に思っています。私はActiveSupportにパスを与えるので、偽善者だと考えてください。@sawaによる上記の解決策は、これよりもはるかに実行可能です。
pho3nixf1re 2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.