Mảng để Hash Ruby


192

Được rồi, đây là thỏa thuận, tôi đã googling từ lâu để tìm giải pháp cho vấn đề này và trong khi có rất nhiều ngoài kia, họ dường như không làm công việc mà tôi đang tìm kiếm.

Về cơ bản tôi có một mảng có cấu trúc như thế này

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

Tôi muốn chuyển đổi nó thành Hash để nó trông như thế này

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

tức là các mục trong chỉ mục 'chẵn' là các khóa và các mục trên chỉ mục 'lẻ' là các giá trị.

Bất kỳ ý tưởng làm thế nào để làm điều này sạch sẽ? Tôi cho rằng một phương pháp vũ phu sẽ chỉ là kéo tất cả các chỉ mục chẵn vào một mảng riêng biệt và sau đó lặp xung quanh chúng để thêm các giá trị.

Câu trả lời:


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

Đó là nó. Các *được gọi là splat điều hành.

Một cảnh báo cho mỗi @Mike Lewis (trong các bình luận): "Hãy cẩn thận với điều này. Ruby mở rộng các biểu tượng trên ngăn xếp. Nếu bạn làm điều này với một tập dữ liệu lớn, hãy hy vọng sẽ thổi bay ngăn xếp của bạn."

Vì vậy, đối với hầu hết các trường hợp sử dụng chung, phương pháp này rất tuyệt, nhưng sử dụng một phương pháp khác nếu bạn muốn thực hiện chuyển đổi trên nhiều dữ liệu. Ví dụ: @ ukasz Niemier (cũng trong các bình luận) cung cấp phương pháp này cho các tập dữ liệu lớn:

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

10
@tester, *được gọi là toán tử splat . Nó lấy một mảng và chuyển đổi nó thành một danh sách các mục. Vậy *[1,2,3,4]=> 1, 2, 3, 4. Trong ví dụ này, ở trên là tương đương với làm Hash["item 1", "item 2", "item 3", "item 4"]. Và Hashcó một []phương thức chấp nhận một danh sách các đối số (tạo các khóa chỉ mục chẵn và các giá trị chỉ mục lẻ), nhưng Hash[]không chấp nhận một mảng, vì vậy chúng tôi chia mảng bằng cách sử dụng *.
Ben Lee

15
Hãy rất cẩn thận với điều này. Ruby mở rộng splats trên stack. Nếu bạn làm điều này với một tập dữ liệu lớn, hy vọng sẽ thổi bay ngăn xếp của bạn.
Mike Lewis

9
Trên các bảng dữ liệu lớn, bạn có thể sử dụng Hash[a.each_slice(2).to_a].
Hauleth

4
"Thổi bay ngăn xếp của bạn" có nghĩa là gì?
Kevin

6
@Kevin, ngăn xếp sử dụng một vùng bộ nhớ nhỏ mà chương trình phân bổ và dự trữ cho các hoạt động cụ thể nhất định. Thông thường nhất, nó được sử dụng để giữ một chồng các phương thức đã được gọi cho đến nay. Đây là nguồn gốc của dấu vết ngăn xếp hạn và đây cũng là lý do tại sao một phương thức đệ quy vô hạn có thể gây ra tràn ngăn xếp . Phương thức trong câu trả lời này cũng sử dụng ngăn xếp, nhưng vì ngăn xếp chỉ là một vùng nhớ nhỏ, nếu bạn thử phương thức này với một mảng lớn, nó sẽ lấp đầy ngăn xếp và gây ra lỗi (một lỗi dọc theo cùng một dòng như một chồng tràn).
Ben Lee

103

Ruby 2.1.0 đã giới thiệu một to_h phương thức trên Array thực hiện những gì bạn yêu cầu nếu mảng ban đầu của bạn bao gồm các mảng của các cặp khóa-giá trị: 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
Xinh đẹp! Tốt hơn nhiều so với một số giải pháp khác ở đây.
Dennis

3
đối với các phiên bản ruby ​​trước 2.1.0, bạn có thể sử dụng phương thức Hash :: [] để có kết quả tương tự, miễn là bạn có các cặp của một mảng được lồng. vì vậy a = [[: foo ,: 1], [bar, 2]] --- Hash [a] => {: foo => 1 ,: bar => 2}
AfDev

@AfDev, thực sự, cảm ơn. Bạn đã đúng (khi bỏ qua các lỗi chính tả nhỏ: barcần phải là một ký hiệu và ký hiệu :2phải là một số nguyên. Vì vậy, biểu thức của bạn được sửa là a = [[:foo, 1], [:bar, 2]]).
Joool Schulenklopper

28

Chỉ cần sử dụng Hash.[]với các giá trị trong mảng. Ví dụ:

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

1
[* mảng] có nghĩa là gì?
Alan Coromano

1
@Marius: *arrchuyển đổi arrthành một danh sách đối số, vì vậy đây là cách gọi []phương thức Hash với nội dung của mảng là đối số.
Chuck

26

Hoặc nếu bạn có một mảng các [key, value]mảng, bạn có thể làm:

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

2
Bạn trả lời không liên quan đến câu hỏi và trong trường hợp của bạn, việc sử dụng tương tự vẫn dễ dàng hơn nhiềuHash[*arr]
Yossi

2
Không. Nó sẽ trở lại { [1, 2] => [3, 4] }. Và vì tiêu đề của câu hỏi cho biết "Array to Hash" và phương thức "Hash to Array" tích hợp sẵn có:, { 1 => 2, 3 => 4}.to_a # => [[1, 2], [3, 4]]tôi nghĩ rằng nhiều người có thể kết thúc ở đây khi cố gắng nghịch đảo phương thức "Hash to Array" tích hợp. Thật ra, đó là cách tôi kết thúc ở đây.
Erik Escobedo

1
Xin lỗi, tôi đã thêm một dấu sao dự phòng. Hash[arr]sẽ làm công việc cho bạn.
Yossi

9
Giải pháp tốt hơn của IMHO: Hash [* mảng.flatten (1)]
khách

2
Yossi: Xin lỗi vì đã nuôi sống người chết, nhưng có một vấn đề lớn hơn với câu trả lời của anh ấy, và đó là việc sử dụng #injectphương pháp. Với #merge!, #each_with_objectnên đã được sử dụng. Nếu #injectđược nhấn mạnh, #mergethay vì #merge!nên được sử dụng.
Boris Stitnicky

12

Đây là những gì tôi đang tìm kiếm khi googling này:

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


Bạn không muốn sử dụng merge, nó xây dựng và loại bỏ hàm băm mới trên mỗi vòng lặp và rất chậm. [{a:1},{b:2}].reduce({}, :merge!)Thay vào đó, nếu bạn có một mảng băm, hãy thử kết hợp mọi thứ vào cùng một hàm băm (mới).

Cảm ơn, đây là những gì tôi muốn quá! :)
Thanocation Thú cưng

Bạn cũng có thể làm.reduce(&:merge!)
Ben Lee

1
[{a: 1}, {b: 2}].reduce(&:merge!)ước tính{:a=>1, :b=>2}
Ben Lee

Điều này hoạt động vì tiêm / giảm có một tính năng trong đó bạn có thể bỏ qua đối số, trong trường hợp đó, nó sử dụng đối số đầu tiên của mảng để hoạt động như đối số đầu vào và phần còn lại của mảng làm mảng. Kết hợp điều đó với biểu tượng-to-Proc và bạn có được công trình ngắn gọn này. Nói cách khác [{a: 1}, {b: 2}].reduce(&:merge!)là giống như [{a: 1}, {b: 2}].reduce { |m, x| m.merge(x) }đó là giống như [{b: 2}].reduce({a: 1}) { |m, x| m.merge(x) }.
Ben Lee

10

Enumeratorbao gồm Enumerable. Vì 2.1, Enumerablecũng có một phương pháp #to_h. Đó là lý do tại sao, chúng ta có thể viết: -

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

Bởi vì #each_slicekhông có khối cho chúng ta Enumerator, và theo lời giải thích ở trên, chúng ta có thể gọi #to_hphương thức trên Enumeratorđối tượng.


7

Bạn có thể thử như thế này, cho mảng đơn

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"}

cho mảng của mảng

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 } ]

hoặc, nếu bạn ghét Hash[ ... ]:

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

hoặc, nếu bạn là một người hâm mộ lười biếng của lập trình chức năng bị hỏng:

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"

Nếu bạn không hoàn toàn ghét Hash[ ... ]nhưng muốn sử dụng nó như một phương pháp xích (như bạn có thể làm với to_h), bạn có thể kết hợp các đề xuất của Boris và viết:arr.each_slice( 2 ).map { |e| e }.tap { |a| break Hash[a] }
b-studios

Để làm cho ngữ nghĩa của mã ở trên rõ ràng hơn: Điều này sẽ tạo ra một "hàm băm lười biếng" , ban đầu trống và sẽ lấy ra các phần tử từ mảng ban đầu a khi cần. Chỉ sau đó chúng sẽ thực sự được lưu trữ trong h!
Daniel Werner

1

Tất cả các câu trả lời giả định mảng bắt đầu là duy nhất. OP đã không chỉ định cách xử lý các mảng với các mục trùng lặp, dẫn đến các khóa trùng lặp.

Chúng ta hãy nhìn vào:

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

Bạn sẽ mất item 1 => item 2cặp vì nó bị ghi đè bij item 1 => item 5:

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

Tất cả các phương pháp, bao gồm cả reduce(&:merge!)kết quả trong cùng một loại bỏ.

Nó có thể là chính xác những gì bạn mong đợi, mặc dù. Nhưng trong các trường hợp khác, có lẽ bạn muốn nhận được kết quả với Arraygiá trị thay thế:

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

Cách ngây thơ sẽ là tạo một biến trợ giúp, hàm băm có giá trị mặc định và sau đó điền vào đó trong một vòng lặp:

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"]}

Có thể có thể sử dụng assocreducethực hiện ở trên trong một dòng, nhưng điều đó trở nên khó khăn hơn nhiều để lý luận và đọc.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.