Rubyで配列をハッシュに変換する最良の方法は何ですか
質問
Ruby では、次のいずれかの形式で配列が与えられます...
[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]
...これを次の形式のハッシュに変換する最良の方法は何ですか...
{apple => 1, banana => 2}
解決
注記:簡潔で効率的なソリューションについては、を参照してください。 マルク=アンドレ・ラフォルチュヌの答え 下に。
この回答はもともと、執筆時点で最も賛成票が高かった 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'])。さらに悪いことに、偶数の長さに平坦化された別の配列が、誤った値のハッシュを黙って提供してしまうことです。
配列のキーまたは値を使用したい場合は、次のように使用できます。 地図:
h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"
これにより、配列キーが保持されます。
h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}
他のヒント
単純に使用する 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
キーと値は期待どおりに機能します。
最良の方法は使用することです 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_h
Ruby 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
, 、これにより、配列自体の値に関して問題が発生する可能性があります。
アップデート
Ruby 2.1.0が本日リリースされました. 。そして私は付属します Array#to_h
(リリースノート そして ルビードキュメント)、これにより、変換の問題が解決されます。 Array
に Hash
.
Ruby ドキュメントの例:
[[:foo, :bar], [1, 2]].to_h # => {:foo => :bar, 1 => 2}
編集:私が書いている間に投稿された回答を見たところ、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 }
以下を使用して、単純に 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}
回答に追加しますが、匿名配列を使用し、注釈を付けます。
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}
概要&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)
使用する 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
(および a4 内の単なる 1 値の配列である他の配列要素):
Hash[a4] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
フラット化 - 新しい変数を使用する a5
そして a6
言及された他のいくつかの回答 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]`.)
素朴に見ると、これはうまくいくように見えますが、種のないオレンジで間違った方向に進んでしまい、 3
ある 鍵 そして durian
ある 価値.
そして、これも同様に、 a1
, 、うまくいきません:
a5.to_h # => TypeError: wrong element type String at 0 (expected array)
それで a4.flatten
私たちには役に立ちません、ただ使いたいだけです Hash[a4]
の flatten(1)
場合 (a6
):
しかし、部分的にのみ平坦化する場合はどうでしょうか?注目に値するのは、 Hash::[]
を使用して splat
部分的に平坦化された配列上 (a6
) は ない 電話するのと同じ Hash[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_print
さんの ap
ここで構造を示しやすくするためです。これには概念的な要件はありません。)
それで、 each_slice
アンバランス フラット入力に対する解決策は、アンバランス ビットが最後にある場合にのみ機能します。
要点:
- 可能な限り、これらへの入力を次のように設定します。
[key, value]
ペア (外側の配列内の各項目のサブ配列)。 - 本当にそれができるときは、
#to_h
またはHash::[]
どちらも機能します。 - それができない場合は、
Hash::[]
スプラットと組み合わせると(*
) 働くでしょう、 入力がバランスされている限り. - と アンバランスな そして フラット 配列を入力として使用する場合、これが合理的に機能する唯一の方法は、 最後
value
欠品しているのはアイテムだけです。
サイドノート:追加する価値があると感じたので、この回答を投稿しています。既存の回答の一部には誤った情報が含まれており、(私が読んだ)回答には、私がここでやろうとしているほど完全な回答はありませんでした。お役に立てば幸いです。それでもなお、私は私の先人たちに感謝しており、そのうちの何人かはこの回答の一部にインスピレーションを与えてくれました。
このような配列がある場合 -
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]}
それが最良の方法かどうかはわかりませんが、これはうまくいきます:
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
数値が 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}