Rubyで配列をハッシュに変換する最良の方法は何ですか


123

Rubyでは、次のいずれかの形式の配列が与えられます...

[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]

...これを次の形式のハッシュに変換する最良の方法は...

{apple => 1, banana => 2}

回答:


91

:簡潔で効率的なソリューションについては、以下のMarc-AndréLafortuneの回答を参照してください。

この回答は、元々、執筆時点で最も高く支持されていたflattenを使用するアプローチの代替として提供されました。この例をベストプラクティスまたは効率的なアプローチとして提示するつもりはなかったことを明確にしておく必要がありました。元の答えは次のとおりです。


警告!フラット化を使用したソリューションをは、配列のキーまたは値を保持しません!

@John Topleyの人気の答えに基づいて、試してみましょう。

a3 = [ ['apple', 1], ['banana', 2], [['orange','seedless'], 3] ]
h3 = Hash[*a3.flatten]

これはエラーをスローします:

ArgumentError: odd number of arguments for Hash
        from (irb):10:in `[]'
        from (irb):10

コンストラクターは、偶数の長さの配列を期待していました(たとえば、['k1'、 'v1、' k2 '、' v2 '])。さらに悪いことに、偶数の長さにフラット化された別の配列は、誤った値のハッシュを静かに提供するだけです。

配列のキーまたは値を使用する場合は、mapを使用できます。

h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"

これにより、配列キーが保持されます。

h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}

15
これは、a3 == a3.map {| k、v |であるため、Hash [a3]と同じです。[k、v]}はtrueであり、実際にはa3.dupと同等です。
クラスタ

2
マップを使用する代わりに、平坦化の深さを指定しないのはなぜですか?たとえば、h3 = Hash[*a3.flatten(1)]代わりにh3 = Hash[*a3.flatten]エラーがスローされます。
Jeff McCune 2013年

3
この答えは効率的ではありません。また、古くなっています。私の答えを見てください。
マルク=アンドレ・Lafortune

1
はい、Marc-Andréのto_hほうがいいと思います。
Bセブン

1
@Marc-AndréLafortuneありがとうございます。ユーザーをあなたに案内するように私の回答を更新しました。
シチュー、

145

単に使う Hash[*array_variable.flatten]

例えば:

a1 = ['apple', 1, 'banana', 2]
h1 = Hash[*a1.flatten(1)]
puts "h1: #{h1.inspect}"

a2 = [['apple', 1], ['banana', 2]]
h2 = Hash[*a2.flatten(1)]
puts "h2: #{h2.inspect}"

キーと値が期待どおりに機能するArray#flatten(1)ように、再帰を制限して使用しますArray


4
ああ、雄弁!Rubyが大好きな理由
iGbanam '25

11
警告:配列のキーまたは値が必要な場合、flattenを使用して回答すると問題が発生します。
シチュー

配列のキーまたは値の問題を回避する代替ソリューションを以下に投稿しました。
シチュー

5
このため、包括的な解決策を試さないでください。キーと値が[[key1、value1]、[key2、value2]]のようにペアになっている場合は、肥大化することなくHash []に渡すだけです。Hash [a2] == Hash [* a2.flatten]。配列が[key1、value1、key2、value2]のように既にフラット化されている場合は、varの前に*、Hash [* a1]を付けるだけ
クラスター

8
FWIW、本当に(1つ以上の)万能のバージョンが必要な場合はHash[*ary.flatten(1)]、配列のキーと値を保持するを使用することもできます。flattenそれらを破壊しているのは再帰的であり、回避するのは簡単です。
brymck 2013年

79

最良の方法は、使用することArray#to_hです:

[ [:apple,1],[:banana,2] ].to_h  #=> {apple: 1, banana: 2}

to_hまた、ブロックを受け付けます。

[:apple, :banana].to_h { |fruit| [fruit, "I like #{fruit}s"] } 
  # => {apple: "I like apples", banana: "I like bananas"}

to_hRuby 2.6.0以降のブロックを受け入れます。初期のルビーには、私のbackports宝石を使用できますrequire 'backports/2.6.0/enumerable/to_h'

to_h Ruby 2.1.0でブロックなしが導入されました。

Ruby 2.1以前は、読みにくいものを使用できましたHash[]

array = [ [:apple,1],[:banana,2] ]
Hash[ array ]  #= > {:apple => 1, :banana => 2}

最後に、を使用した解決策に注意してくださいflatten。これにより、配列自体の値で問題が発生する可能性があります。


4
新しい.to_hメソッドのシンプルさをありがとう!
コーディング中毒

3
配列を操作したto_hに変換する意図を表すため、上記の回答よりも優れた方法が好きです。
Bセブン

1
@BSevenどちらArray#to_hもコアルビーEnumerable#to_h1.9にはありません。
アイアンセイバー

配列が[[apple, 1], [banana, 2], [apple, 3], [banana, 4]]あり、出力が必要な場合はどうなり{"apple" =>[1,3], "banana"=>[2,4]}ますか?
ニシャント2017

@NishantKumarそれは別の質問です。
マルク=アンドレ・Lafortune


9

編集:私が書いている間に投稿された応答を見た、Hash [a.flatten]は進むべき道のようです。私が応答を通して考えていたとき、ドキュメントのそのビットを逃したに違いありません。私が書いたソリューションは、必要に応じて代替案として使用できると思いました。

2番目の形式はより単純です。

a = [[:apple, 1], [:banana, 2]]
h = a.inject({}) { |r, i| r[i.first] = i.last; r }

a =配列、h =ハッシュ、r =戻り値のハッシュ(蓄積されるもの)、i =配列内のアイテム

私が最初のフォームを実行するのを考えることができる最も最近の方法は次のようなものです:

a = [:apple, 1, :banana, 2]
h = {}
a.each_slice(2) { |i| h[i.first] = i.last }

2
a.inject({})より柔軟な値の割り当てを可能にするワンライナーの+1 。
Chris Bloom

h = {}最後にa.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
lindes

あなたができることa.each_slice(2).to_h
Conor O'Brien

6

以下を使用して、2D配列をハッシュに単純に変換することもできます。

1.9.3p362 :005 > a= [[1,2],[3,4]]

 => [[1, 2], [3, 4]]

1.9.3p362 :006 > h = Hash[a]

 => {1=>2, 3=>4} 

4

概要とTL; DR:

この回答は、他の回答からの情報を包括的にまとめたものになることを期待しています。

質問のデータといくつかの追加を考慮した非常に短いバージョン:

flat_array   = [  apple, 1,   banana, 2  ] # count=4
nested_array = [ [apple, 1], [banana, 2] ] # count=2 of count=2 k,v arrays
incomplete_f = [  apple, 1,   banana     ] # count=3 - missing last value
incomplete_n = [ [apple, 1], [banana   ] ] # count=2 of either k or k,v arrays


# there's one option for flat_array:
h1  = Hash[*flat_array]                     # => {apple=>1, banana=>2}

# two options for nested_array:
h2a = nested_array.to_h # since ruby 2.1.0    => {apple=>1, banana=>2}
h2b = Hash[nested_array]                    # => {apple=>1, banana=>2}

# ok if *only* the last value is missing:
h3  = Hash[incomplete_f.each_slice(2).to_a] # => {apple=>1, banana=>nil}
# always ok for k without v in nested array:
h4  = Hash[incomplete_n] # or .to_h           => {apple=>1, banana=>nil}

# as one might expect:
h1 == h2a # => true
h1 == h2b # => true
h1 == h3  # => false
h3 == h4  # => true

議論と詳細は以下の通りです。


セットアップ:変数

最初に使用するデータを示すために、データのさまざまな可能性を表す変数をいくつか作成します。これらは次のカテゴリに分類されます。

質問に直接何が含まれていたかに基づいて、a1およびa2

(注:私はそれを想定してappleおり、banana変数を表すためのものでした。他の人が行ったように、入力と結果が一致するように、ここから文字列を使用します。)

a1 = [  'apple', 1 ,  'banana', 2  ] # flat input
a2 = [ ['apple', 1], ['banana', 2] ] # key/value paired input

複数値のキーまたは値、あるいはその両方a3

他のいくつかの回答では、別の可能性が提示されました(ここで詳しく説明します)。キーまたは値、あるいはその両方が独自の配列である場合があります。

a3 = [ [ 'apple',                   1   ],
       [ 'banana',                  2   ],
       [ ['orange','seedless'],     3   ],
       [ 'pear',                 [4, 5] ],
     ]

アンバランス配列a4

目安として、入力が不完全な場合に備えて1つ追加すると思います。

a4 = [ [ 'apple',                   1],
       [ 'banana',                  2],
       [ ['orange','seedless'],     3],
       [ 'durian'                    ], # a spiky fruit pricks us: no value!
     ]

今、働くために:

最初はフラットな配列から始めa1ます。

一部のユーザーは、使用を提案しています#to_h(これはRuby 2.1.0で表示され、以前のバージョンにバックポートできます)。最初はフラットな配列の場合、これは機能しません。

a1.to_h   # => TypeError: wrong element type String at 0 (expected array)

splatオペレーターHash::[]と組み合わせて使用すると、次のことが行われます。

Hash[*a1] # => {"apple"=>1, "banana"=>2}

これが、で表される単純なケースの解決策ですa1

キーと値のペア配列の配列では、a2次のようになります。

配列と[key,value]型の配列、行くには2つの方法があります。

まず、Hash::[]まだ機能します(と同じように*a1):

Hash[a2] # => {"apple"=>1, "banana"=>2}

そして#to_h今も動作します:

a2.to_h  # => {"apple"=>1, "banana"=>2}

したがって、単純なネストされた配列の場合の2つの簡単な答えです。

これは、次のように、キーまたは値としてサブ配列を使用しても当てはまりますa3

Hash[a3] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "pear"=>[4, 5]} 
a3.to_h  # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "pear"=>[4, 5]}

しかし、ドリアンにはスパイクがあります(異常な構造が問題を引き起こします)。

バランスが取れていない入力データを取得した場合、次の問題が発生し#to_hます。

a4.to_h  # => ArgumentError: wrong array length at 3 (expected 2, was 1)

しかし、Hash::[]それでも機能nilし、値として設定するだけですdurian(そして、1値の配列であるa4の他の配列要素):

Hash[a4] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}

フラット化-新しい変数の使用a5a6

引数のflatten有無にかかわらず、他のいくつかの回答が言及されている1ので、新しい変数をいくつか作成してみましょう。

a5 = a4.flatten
# => ["apple", 1, "banana", 2,  "orange", "seedless" , 3, "durian"] 
a6 = a4.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian"] 

a4で示されたバランスの問題があったため、ベースデータとして使用することにしましたa4.to_h。呼び出しflattenは、誰かがそれを解決するために使用する1つのアプローチであると考えられます。これは次のようになります。

flatten引数なし(a5):

Hash[*a5]       # => {"apple"=>1, "banana"=>2, "orange"=>"seedless", 3=>"durian"}
# (This is the same as calling `Hash[*a4.flatten]`.)

ナイーブ一見、仕事にこれが表示されますが-しかし、それはこのようにもなって、種なしオレンジと間違って足で私たちを降り、キー値を3durian

そして、これはとa1同様に機能しません:

a5.to_h # => TypeError: wrong element type String at 0 (expected array)

だからa4.flatten私たちには役に立たない、ただ使用したいだけHash[a4]

flatten(1)ケース(a6):

しかし、部分的にしか平坦化しないのはどうですか?部分的に平坦化された配列()をHash::[]使用して呼び出すことは、を呼び出すことと同じではないことに注意してください。splata6Hash[a4]

Hash[*a6] # => ArgumentError: odd number of arguments for Hash

事前にフラット化された配列、まだネストされている(取得の代替方法a6):

しかし、これが最初に配列を取得する方法だったとしたらどうでしょうか。(つまり、と比較するとa1、それは入力データでした。今回は一部のデータが配列または他のオブジェクトである可能性があります。)これHash[*a6]は機能しないことがわかりましたが、最後の要素(重要!以下を参照)がnil値のキーとして機能しましたか?

そのような状況でも、これを行う方法があり、外部配列の要素としてEnumerable#each_sliceキー/値のペアに戻るために使用します。

a7 = a6.each_slice(2).to_a
# => [["apple", 1], ["banana", 2], [["orange", "seedless"], 3], ["durian"]] 

これにより、同一」ではないa4同じ値を持つ新しい配列が取得されることに注意してください。

a4.equal?(a7) # => false
a4 == a7      # => true

したがって、再び使用できますHash::[]

Hash[a7] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
# or Hash[a6.each_slice(2).to_a]

しかし、問題があります!

最後のキーが値のないものであったeach_slice(2)場合にのみ、解決策は物事を正気に戻すことに注意することが重要です。後でキーと値のペアを追加した場合:

a4_plus = a4.dup # just to have a new-but-related variable name
a4_plus.push(['lychee', 4])
# => [["apple",                1],
#     ["banana",               2],
#     [["orange", "seedless"], 3], # multi-value key
#     ["durian"],              # missing value
#     ["lychee", 4]]           # new well-formed item

a6_plus = a4_plus.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian", "lychee", 4]

a7_plus = a6_plus.each_slice(2).to_a
# => [["apple",                1],
#     ["banana",               2],
#     [["orange", "seedless"], 3], # so far so good
#     ["durian",               "lychee"], # oops! key became value!
#     [4]]                     # and we still have a key without a value

a4_plus == a7_plus # => false, unlike a4 == a7

そして、これから得られる2つのハッシュは、重要な点で異なります。

ap Hash[a4_plus] # prints:
{
                     "apple" => 1,
                    "banana" => 2,
    [ "orange", "seedless" ] => 3,
                    "durian" => nil, # correct
                    "lychee" => 4    # correct
}

ap Hash[a7_plus] # prints:
{
                     "apple" => 1,
                    "banana" => 2,
    [ "orange", "seedless" ] => 3,
                    "durian" => "lychee", # incorrect
                           4 => nil       # incorrect
}

(注:私が使用しているawesome_printapちょうどそれが簡単にここ構造を示すために作るために、このための概念的な要件はありません。)

したがって、each_slice不平衡フラット入力の解決策は、不平衡ビットが最後にある場合にのみ機能します。


持ち帰り:

  1. 可能な限り、これらのものへの入力を[key, value]ペア(外部配列の各項目のサブ配列)としてセットアップします。
  2. 実際にそれができる場合、どちらか#to_hまたはHash::[]両方が機能します。
  3. できない場合Hash::[]は、splat(*)と組み合わせると、入力のバランスが取れている限り機能します。
  4. アンバランスフラットな場合は、入力として、配列、これがすべてで動作する唯一の方法は、合理的である最後の value項目が欠けている唯一のものです。

補足:追加する価値があると感じたため、この回答を投稿しています。既存の回答の一部には不正確な情報があり、私がここでやろうとしているほど完全な回答はありません(私が読んだもの)。お役に立てれば幸いです。それでも、私の前に来てくれた人々に感謝します。そのうちのいくつかは、この答えの一部にインスピレーションを与えました。


3

答えに追加しますが、匿名配列と注釈を使用します:

Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]

内側から始めて、その答えを分解します。

  • "a,b,c,d" 実際には文字列です。
  • split 配列にコンマで。
  • zip 次の配列と一緒にそれ。
  • [1,2,3,4] 実際の配列です。

中間結果は次のとおりです。

[[a,1],[b,2],[c,3],[d,4]]

その後、flattenはそれを次のように変換します。

["a",1,"b",2,"c",3,"d",4]

その後:

*["a",1,"b",2,"c",3,"d",4] それを展開する "a",1,"b",2,"c",3,"d",4

これをHash[]メソッドの引数として使用できます。

Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]

これにより、

{"a"=>1, "b"=>2, "c"=>3, "d"=>4}

これはsplat(*)なしでも機能し、flatten:Hash[("a,b,c,d".split(',').zip([1,2,3,4]))]=> {"a"=>1, "b"=>2, "c"=>3, "d"=>4}です。私が追加した回答の詳細。
lindes

0

このような配列がある場合-

data = [["foo",1,2,3,4],["bar",1,2],["foobar",1,"*",3,5,:foo]]

そして、各配列の最初の要素をハッシュのキーにし、残りの要素を値の配列にする場合、次のようにします-

data_hash = Hash[data.map { |key| [key.shift, key] }]

#=>{"foo"=>[1, 2, 3, 4], "bar"=>[1, 2], "foobar"=>[1, "*", 3, 5, :foo]}

0

それが最善の方法かどうかはわかりませんが、これはうまくいきます:

a = ["apple", 1, "banana", 2]
m1 = {}
for x in (a.length / 2).times
  m1[a[x*2]] = a[x*2 + 1]
end

b = [["apple", 1], ["banana", 2]]
m2 = {}
for x,y in b
  m2[x] = y
end

-1

数値がseqインデックスの場合、もっと簡単な方法が考えられます...これが私のコードの提出です。私のRubyは少し錆びています

   input = ["cat", 1, "dog", 2, "wombat", 3]
   hash = Hash.new
   input.each_with_index {|item, index|
     if (index%2 == 0) hash[item] = input[index+1]
   }
   hash   #=> {"cat"=>1, "wombat"=>3, "dog"=>2}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.