ใน Ruby ได้รับอาร์เรย์ในรูปแบบใดรูปแบบหนึ่งต่อไปนี้ ...
[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]
... อะไรคือวิธีที่ดีที่สุดในการแปลงสิ่งนี้เป็นแฮชในรูปแบบของ ...
{apple => 1, banana => 2}
ใน Ruby ได้รับอาร์เรย์ในรูปแบบใดรูปแบบหนึ่งต่อไปนี้ ...
[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]
... อะไรคือวิธีที่ดีที่สุดในการแปลงสิ่งนี้เป็นแฮชในรูปแบบของ ...
{apple => 1, banana => 2}
คำตอบ:
หมายเหตุ : สำหรับวิธีแก้ปัญหาที่รัดกุมและมีประสิทธิภาพโปรดดูคำตอบของ Marc-André Lafortuneด้านล่าง
เดิมทีคำตอบนี้ถูกเสนอให้เป็นอีกทางเลือกหนึ่งของแนวทางที่ใช้การแบนซึ่งได้รับการโหวตสูงที่สุดในขณะที่เขียน ฉันควรชี้แจงว่าไม่ได้ตั้งใจจะนำเสนอตัวอย่างนี้เป็นแนวทางปฏิบัติที่ดีที่สุดหรือแนวทางที่มีประสิทธิภาพ คำตอบเดิมมีดังนี้
คำเตือน! การแก้ปัญหาโดยใช้การแบนจะไม่เก็บคีย์หรือค่า Array ไว้!
จากคำตอบยอดนิยมของ @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
ตัวสร้างคาดว่าจะมี Array ที่มีความยาวเท่ากัน (เช่น ['k1', 'v1,' k2 ',' v2 ']) สิ่งที่แย่กว่านั้นคือ Array ที่แตกต่างกันซึ่งแบนจนมีความยาวเท่ากันจะทำให้เรามีค่า Hash ที่ไม่ถูกต้อง
หากคุณต้องการใช้คีย์หรือค่า Array คุณสามารถใช้map :
h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"
สิ่งนี้จะเก็บรักษาคีย์ Array:
h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}
h3 = Hash[*a3.flatten(1)]
แทนที่จะh3 = Hash[*a3.flatten]
เป็นข้อผิดพลาด
to_h
ดีกว่า
เพียงแค่ใช้ 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
คีย์และค่าทำงานตามที่คาดไว้
Hash[*ary.flatten(1)]
ซึ่งจะเก็บคีย์อาร์เรย์และค่าไว้ มันซ้ำซากflatten
ที่ทำลายสิ่งเหล่านั้นซึ่งง่ายพอที่จะหลีกเลี่ยง
วิธีที่ดีที่สุดคือใช้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
ซึ่งอาจสร้างปัญหากับค่าที่เป็นอาร์เรย์เอง
to_h
วิธีการนี้ดีกว่าคำตอบข้างต้นเพราะมันแสดงถึงเจตนาของการแปลงหลังจากใช้งานอาร์เรย์
Array#to_h
มิได้Enumerable#to_h
อยู่ในหลักทับทิม 1.9
[[apple, 1], [banana, 2], [apple, 3], [banana, 4]]
และฉันต้องการให้เอาต์พุตเป็น{"apple" =>[1,3], "banana"=>[2,4]}
?
ปรับปรุง
ทับทิม 2.1.0 ถูกปล่อยออกมาในวันนี้ และฉันมาพร้อมกับArray#to_h
( บันทึกประจำรุ่นและruby-doc ) ซึ่งช่วยแก้ปัญหาการแปลงไฟล์เป็นArray
ไฟล์Hash
.
ตัวอย่างเอกสาร Ruby:
[[:foo, :bar], [1, 2]].to_h # => {:foo => :bar, 1 => 2}
แก้ไข: เห็นคำตอบที่โพสต์ในขณะที่ฉันเขียน Hash [a.flatten] ดูเหมือนจะเป็นหนทางไป ต้องพลาดบิตนั้นในเอกสารเมื่อฉันคิดถึงการตอบสนอง คิดว่าโซลูชันที่ฉันเขียนสามารถใช้เป็นทางเลือกอื่นได้หากจำเป็น
รูปแบบที่สองง่ายกว่า:
a = [[:apple, 1], [:banana, 2]]
h = a.inject({}) { |r, i| r[i.first] = i.last; r }
a = array, h = hash, r = return-value hash (อันที่เราสะสม), i = item ในอาร์เรย์
วิธีที่เรียบง่ายที่สุดที่ฉันคิดได้ในการทำรูปแบบแรกมีดังนี้:
a = [:apple, 1, :banana, 2]
h = {}
a.each_slice(2) { |i| h[i.first] = i.last }
a.inject({})
ซับเดียวที่ช่วยให้การกำหนดค่ามีความยืดหยุ่นมากขึ้น
h = {}
จากตัวอย่างที่สองโดยใช้การฉีดลงท้ายด้วยa.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
a.each_slice(2).to_h
คุณยังสามารถแปลงอาร์เรย์ 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}
คำตอบนี้หวังว่าจะเป็นข้อมูลสรุปที่ครอบคลุมจากคำตอบอื่น ๆ
เวอร์ชันที่สั้นมากโดยให้ข้อมูลจากคำถามพร้อมสิ่งพิเศษอีกสองสามข้อ:
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
:เพื่อการวัดที่ดีฉันคิดว่าฉันจะเพิ่มหนึ่งรายการสำหรับกรณีที่เราอาจมีข้อมูลที่ป้อนไม่ครบถ้วน:
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::[]
ร่วมกับตัวดำเนินการ Splatจะ:
Hash[*a1] # => {"apple"=>1, "banana"=>2}
a1
เพื่อให้เป็นทางออกสำหรับกรณีที่เรียบง่ายที่แสดงโดย
a2
:ด้วยอาร์เรย์[key,value]
ประเภทอาร์เรย์มีสองวิธีที่จะไป
ขั้นแรกHash::[]
ยังคงใช้งานได้ (เช่นเดียวกับที่ทำกับ*a1
):
Hash[a2] # => {"apple"=>1, "banana"=>2}
จากนั้นก็ใช้#to_h
งานได้ในขณะนี้:
a2.to_h # => {"apple"=>1, "banana"=>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
อาจเป็นแนวทางหนึ่งที่ใครบางคนอาจใช้เพื่อพยายามแก้ปัญหานั้นซึ่งอาจมีลักษณะดังต่อไปนี้
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
และสองแฮชที่เราได้รับจากสิ่งนี้มีความแตกต่างกันในลักษณะสำคัญ:
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
'sap
. เพียงเพื่อให้ง่ายต่อการแสดงโครงสร้างที่นี่ไม่มีความต้องการแนวคิดสำหรับการนี้)
ดังนั้นeach_slice
วิธีแก้ปัญหาสำหรับอินพุตแบบแบนที่ไม่สมดุลจะใช้งานได้ก็ต่อเมื่อบิตที่ไม่สมดุลอยู่ที่ส่วนท้ายสุด
[key, value]
คู่ (อาร์เรย์ย่อยสำหรับแต่ละรายการในอาร์เรย์ภายนอก)#to_h
หรือHash::[]
ทั้งสองอย่างก็ได้ผลHash::[]
รวมกับเครื่องหมาย ( *
) จะทำงานตราบใดที่ปัจจัยการผลิตที่มีความสมดุลvalue
เป็นเพียงรายการเดียวที่ขาดหายไปหมายเหตุด้านข้าง: ฉันโพสต์คำตอบนี้เพราะฉันรู้สึกว่ามีค่าที่จะเพิ่ม - คำตอบที่มีอยู่บางคำมีข้อมูลที่ไม่ถูกต้องและไม่มี (ที่ฉันอ่าน) ให้คำตอบที่สมบูรณ์เท่าที่ฉันพยายามทำที่นี่ ฉันหวังว่ามันจะเป็นประโยชน์ อย่างไรก็ตามฉันขอขอบคุณผู้ที่มาก่อนฉันซึ่งหลายคนให้แรงบันดาลใจสำหรับบางส่วนของคำตอบนี้
ต่อท้ายคำตอบ แต่ใช้อาร์เรย์ที่ไม่ระบุชื่อและการใส่คำอธิบายประกอบ:
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]]
แบนแล้วแปลงเป็น:
["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}
*
) และแผ่: =>Hash[("a,b,c,d".split(',').zip([1,2,3,4]))]
{"a"=>1, "b"=>2, "c"=>3, "d"=>4}
รายละเอียดเพิ่มเติมในคำตอบที่ฉันเพิ่ม
ถ้าคุณมีอาร์เรย์ที่มีลักษณะเช่นนี้ -
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 เราอาจมีวิธีที่ง่ายกว่านี้ ... นี่คือการส่งรหัสของฉัน My 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}