Array to Hash Ruby


192

ตกลงดังนั้นนี่คือข้อตกลงฉันได้รับ googling สำหรับทุกเพศทุกวัยเพื่อหาวิธีการแก้ปัญหานี้และในขณะที่มีจำนวนมากออกมีพวกเขาดูเหมือนจะไม่ทำงานที่ฉันกำลังมองหา

โดยทั่วไปฉันมีโครงสร้างแบบนี้

["item 1", "item 2", "item 3", "item 4"] 

ฉันต้องการแปลงมันให้เป็นแฮชดังนั้นมันจึงเป็นแบบนี้

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

นั่นคือรายการที่อยู่ในดัชนี 'คู่' คือกุญแจและรายการในดัชนี 'แปลก' คือค่า

แนวคิดใดที่จะทำสิ่งนี้อย่างหมดจด? ฉันคิดว่าวิธีการเดรัจฉานกำลังจะดึงดัชนีแม้ทั้งหมดลงในอาร์เรย์ที่แยกต่างหากแล้ววนรอบพวกเขาเพื่อเพิ่มค่า

คำตอบ:


358
a = ["item 1", "item 2", "item 3", "item 4"]
h = Hash[*a] # => { "item 1" => "item 2", "item 3" => "item 4" }

แค่นั้นแหละ. *เรียกว่าเครื่องหมายประกอบ

หนึ่งข้อแม้ต่อ @Mike Lewis (ในความคิดเห็น): "ระวังให้ดีสิ่งนี้ Ruby ขยาย splats บนสแต็กหากคุณทำเช่นนี้ด้วยชุดข้อมูลขนาดใหญ่

ดังนั้นสำหรับกรณีการใช้งานทั่วไปส่วนใหญ่วิธีนี้เป็นวิธีที่ยอดเยี่ยม แต่ใช้วิธีการอื่นหากคุณต้องการแปลงข้อมูลจำนวนมาก ตัวอย่างเช่น @ Łukasz Niemier (ในความคิดเห็น) เสนอวิธีนี้สำหรับชุดข้อมูลขนาดใหญ่:

h = Hash[a.each_slice(2).to_a]

10
@tester *เรียกว่าตัวดำเนินการเครื่องหมาย มันใช้เวลาอาร์เรย์และแปลงเป็นรายการตามตัวอักษร ดังนั้น=>*[1,2,3,4] ในตัวอย่างนี้ดังกล่าวข้างต้นจะเทียบเท่ากับการทำ1, 2, 3, 4 Hash["item 1", "item 2", "item 3", "item 4"]และHashมี[]วิธีการที่ยอมรับรายการของอาร์กิวเมนต์ (ทำให้แม้กระทั่งคีย์ดัชนีและค่าดัชนีคี่) แต่ไม่ยอมรับอาร์เรย์ดังนั้นเราจึงแดงอาร์เรย์ใช้Hash[] *
Ben Lee

15
ระวังให้มากด้วยสิ่งนี้ Ruby ขยาย splats บนสแต็ก หากคุณทำเช่นนี้ด้วยชุดข้อมูลขนาดใหญ่คาดว่าจะทำให้สแต็กของคุณระเบิด
Mike Lewis

9
Hash[a.each_slice(2).to_a]ในตารางข้อมูลขนาดใหญ่ที่คุณสามารถใช้
Hauleth

4
"ระเบิดกองของคุณ" หมายความว่าอะไร
Kevin

6
@Kevin สแต็กใช้พื้นที่หน่วยความจำขนาดเล็กที่โปรแกรมจัดสรรและสำรองสำหรับการดำเนินการเฉพาะอย่าง โดยทั่วไปจะใช้เพื่อเก็บสแต็คของวิธีการที่ได้รับการเรียกจนถึง นี่คือจุดเริ่มต้นของการติดตามสแต็กคำและนี่คือเหตุผลที่วิธีการเรียกซ้ำแบบไม่สิ้นสุดสามารถทำให้เกิดการล้นสแต็ก วิธีการในคำตอบนี้ยังใช้สแต็ก แต่เนื่องจากสแต็คเป็นเพียงพื้นที่เล็ก ๆ ของหน่วยความจำหากคุณลองใช้วิธีนี้ด้วยอาเรย์ขนาดใหญ่มันจะเติมสแต็กให้เต็มและทำให้เกิดข้อผิดพลาด สแต็กล้น)
Ben Lee

103

Ruby 2.1.0 แนะนำto_hวิธีการใน Array ที่ทำในสิ่งที่คุณต้องการถ้าอาร์เรย์ดั้งเดิมของคุณประกอบด้วยอาร์เรย์ของคู่ของคีย์ - ค่า: http://www.ruby-doc.org/core-2.1.0/Array.html#method -i-to_h

[[:foo, :bar], [1, 2]].to_h
# => {:foo => :bar, 1 => 2}

1
สวย! ดีกว่าโซลูชันอื่น ๆ ที่นี่มาก
Dennis

3
สำหรับรุ่นทับทิม 2.1.0 ก่อนคุณสามารถใช้วิธี Hash :: [] เพื่อให้ได้ผลลัพธ์ที่คล้ายกันตราบใดที่คุณมีอาร์เรย์ที่ซ้อนกันหลายคู่ ดังนั้น a = [[: foo,: 1], [bar, 2]] --- Hash [a] => {: foo => 1
,:

@AfDev แน่นอนขอบคุณ คุณถูกต้อง (เมื่อละเว้นการพิมพ์เล็ก: barจะต้องเป็นสัญลักษณ์และสัญลักษณ์:2ควรเป็นจำนวนเต็มดังนั้นนิพจน์ของคุณจะถูกแก้ไขa = [[:foo, 1], [:bar, 2]])
Jochem Schulenklopper

28

เพียงใช้Hash.[]กับค่าในอาร์เรย์ ตัวอย่างเช่น:

arr = [1,2,3,4]
Hash[*arr] #=> gives {1 => 2, 3 => 4}

1
[* arr] หมายถึงอะไร
Alan Coromano

1
@Marius: *arrแปลงarrเป็นรายการอาร์กิวเมนต์ดังนั้นนี่คือการเรียก[]วิธีการแฮชที่มีเนื้อหาของ arr เป็นอาร์กิวเมนต์
Chuck

26

หรือถ้าคุณมี[key, value]อาร์เรย์คุณสามารถทำได้:

[[1, 2], [3, 4]].inject({}) do |r, s|
  r.merge!({s[0] => s[1]})
end # => { 1 => 2, 3 => 4 }

2
คำตอบของคุณไม่เกี่ยวข้องกับคำถามและในกรณีของคุณยังคงใช้งานได้ง่ายกว่าเดิมมากHash[*arr]
Yossi

2
Nope { [1, 2] => [3, 4] }มันจะกลับมา และเนื่องจากชื่อคำถามนั้นบอกว่า "Array to Hash" และวิธีการ "Hash to Array" ในตัวนั้นทำ: { 1 => 2, 3 => 4}.to_a # => [[1, 2], [3, 4]]ฉันคิดว่ามีมากกว่าหนึ่งคนที่จะจบที่นี่เพื่อพยายามกลับด้านของวิธี "Hash to Array" ในตัว จริงๆแล้วนั่นคือวิธีที่ฉันสิ้นสุดที่นี่ต่อไป
Erik Escobedo

1
ขออภัยฉันได้ใส่เครื่องหมายดอกจันสำรองไว้ Hash[arr]จะทำงานให้คุณ
Yossi

9
วิธีที่ดีกว่า IMHO: แฮช [* array.flatten (1)]
ผู้เยี่ยมชม

2
Yossi: ขออภัยที่เลี้ยงคนตาย แต่มีปัญหาหนึ่งที่ใหญ่กว่ากับคำตอบของเขาและนั่นคือการใช้#injectวิธีการ ด้วย#merge!, #each_with_objectควรจะได้ถูกนำมาใช้ หาก#injectมีการยืนยัน, #mergeมากกว่า#merge!ที่ควรจะได้ถูกนำมาใช้
Boris Stitnicky

12

นี่คือสิ่งที่ฉันกำลังมองหาเมื่อ googling นี้:

[{a: 1}, {b: 2}].reduce({}) { |h, v| h.merge v } => {:a=>1, :b=>2}


คุณไม่ต้องการใช้mergeมันสร้างและละทิ้งแฮชใหม่ต่อการวนซ้ำของวงและช้ามาก หากคุณมีแฮ็ชหลายชุดลอง[{a:1},{b:2}].reduce({}, :merge!)แทน - มันจะรวมทุกอย่างไว้ในแฮช (ใหม่)

ขอบคุณนี่คือสิ่งที่ฉันต้องการเช่นกัน! :)
Thanasis Petsas

คุณยังสามารถทำได้.reduce(&:merge!)
เบ็นลี

1
[{a: 1}, {b: 2}].reduce(&:merge!)ประเมินถึง{:a=>1, :b=>2}
Ben Lee

สิ่งนี้ทำงานได้เนื่องจากการฉีด / ลดมีคุณสมบัติที่คุณสามารถละเว้นอาร์กิวเมนต์ในกรณีนี้มันใช้อาร์กิวเมนต์แรกของอาร์เรย์เพื่อทำงานเป็นอาร์กิวเมนต์อินพุตและส่วนที่เหลือของอาร์เรย์เป็นอาร์เรย์ เมื่อรวมเข้ากับ symbol-to-proc แล้วคุณจะได้โครงสร้างที่กระชับ ในคำอื่น ๆ[{a: 1}, {b: 2}].reduce(&:merge!)เป็นเช่นเดียวกับที่เป็นเช่นเดียวกับ[{a: 1}, {b: 2}].reduce { |m, x| m.merge(x) } [{b: 2}].reduce({a: 1}) { |m, x| m.merge(x) }
Ben Lee

10

EnumeratorEnumerableรวมถึง ตั้งแต่2.1, นอกจากนี้ยังมีวิธีการEnumerable #to_hนั่นเป็นเหตุผลที่เราสามารถเขียน: -

a = ["item 1", "item 2", "item 3", "item 4"]
a.each_slice(2).to_h
# => {"item 1"=>"item 2", "item 3"=>"item 4"}

เพราะ#each_sliceไม่มีบล็อกให้เราEnumeratorและตามคำอธิบายข้างต้นเราสามารถเรียก#to_hวิธีการบนEnumeratorวัตถุ


7

คุณสามารถลองแบบนี้สำหรับอาเรย์เดี่ยว

irb(main):019:0> a = ["item 1", "item 2", "item 3", "item 4"]
  => ["item 1", "item 2", "item 3", "item 4"]
irb(main):020:0> Hash[*a]
  => {"item 1"=>"item 2", "item 3"=>"item 4"}

สำหรับอาร์เรย์ของอาร์เรย์

irb(main):022:0> a = [[1, 2], [3, 4]]
  => [[1, 2], [3, 4]]
irb(main):023:0> Hash[*a.flatten]
  => {1=>2, 3=>4}

6
a = ["item 1", "item 2", "item 3", "item 4"]
Hash[ a.each_slice( 2 ).map { |e| e } ]

หรือถ้าคุณเกลียดHash[ ... ]:

a.each_slice( 2 ).each_with_object Hash.new do |(k, v), h| h[k] = v end

หรือถ้าคุณเป็นแฟนขี้เกียจของการเขียนโปรแกรมที่ใช้งานไม่ได้:

h = a.lazy.each_slice( 2 ).tap { |a|
  break Hash.new { |h, k| h[k] = a.find { |e, _| e == k }[1] }
}
#=> {}
h["item 1"] #=> "item 2"
h["item 3"] #=> "item 4"

หากคุณไม่ได้อย่างเต็มที่เกลียดHash[ ... ]แต่ต้องการที่จะใช้มันเป็นวิธีการที่ถูกล่ามโซ่ (เช่นคุณสามารถทำได้ด้วยto_h) คุณสามารถรวมข้อเสนอแนะและบอริสเขียน:arr.each_slice( 2 ).map { |e| e }.tap { |a| break Hash[a] }
B-สตูดิโอ

ในการทำให้ซีแมนทิกส์ของรหัสข้างบนชัดเจนขึ้น: นี่จะสร้าง"lazy hash" hซึ่งจะว่างเปล่าในตอนแรกและจะดึงองค์ประกอบออกจากอาร์เรย์ดั้งเดิมเมื่อจำเป็น พวกเขาจะถูกจัดเก็บใน h!
Daniel Werner

1

คำตอบทั้งหมดจะถือว่าอาร์เรย์เริ่มต้นนั้นไม่ซ้ำกัน OP ไม่ได้ระบุวิธีจัดการอาร์เรย์ด้วยรายการที่ซ้ำกันซึ่งส่งผลให้เกิดคีย์ซ้ำกัน

ลองดูที่:

a = ["item 1", "item 2", "item 3", "item 4", "item 1", "item 5"]

คุณจะเสียitem 1 => item 2คู่เนื่องจากมันถูกแทนที่ bij item 1 => item 5:

Hash[*a]
=> {"item 1"=>"item 5", "item 3"=>"item 4"}

วิธีการทั้งหมดรวมถึงreduce(&:merge!)ผลลัพธ์ในการลบแบบเดียวกัน

อาจเป็นได้ว่านี่เป็นสิ่งที่คุณคาดหวัง แต่ในกรณีอื่น ๆ คุณอาจต้องการผลลัพธ์ด้วยArrayค่า for แทน:

{"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

วิธีไร้เดียงสาคือการสร้างตัวแปรตัวช่วยแฮชที่มีค่าเริ่มต้นจากนั้นเติมลงในลูป:

result = Hash.new {|hash, k| hash[k] = [] } # Hash.new with block defines unique defaults.
a.each_slice(2) {|k,v| result[k] << v }
a
=> {"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

อาจเป็นไปได้ที่จะใช้assocและreduceทำเหนือสิ่งใดสิ่งหนึ่งในบรรทัดเดียว แต่มันยากที่จะให้เหตุผลและอ่าน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.