Cách tốt nhất để chuyển đổi một mảng thành hàm băm trong Ruby là gì


123

Trong Ruby, được cung cấp một mảng trong một trong các hình thức sau ...

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

... cách tốt nhất để chuyển đổi nó thành hàm băm dưới dạng ...

{apple => 1, banana => 2}

Câu trả lời:


91

LƯU Ý : Để có giải pháp ngắn gọn và hiệu quả, vui lòng xem câu trả lời của Marc-André Lafortune bên dưới.

Câu trả lời này ban đầu được đưa ra như là một giải pháp thay thế cho phương pháp sử dụng flatten, vốn được đánh giá cao nhất tại thời điểm viết. Tôi nên làm rõ rằng tôi không có ý định trình bày ví dụ này như một cách thực hành tốt nhất hoặc một cách tiếp cận hiệu quả. Câu trả lời gốc sau đây.


Cảnh báo! Các giải pháp sử dụng flatten sẽ không bảo toàn các khóa hoặc giá trị Array!

Dựa trên câu trả lời phổ biến của @John Topley, hãy thử:

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

Điều này đưa ra một lỗi:

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

Hàm tạo đã mong đợi một Mảng có độ dài chẵn (ví dụ: ['k1', 'v1,' k2 ',' v2 ']). Điều tồi tệ hơn là một Mảng khác được làm phẳng đến một độ dài chẵn sẽ chỉ âm thầm cung cấp cho chúng ta một Hash với các giá trị không chính xác.

Nếu bạn muốn sử dụng các khóa hoặc giá trị mảng, bạn có thể sử dụng bản đồ :

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

Điều này bảo tồn khóa Array:

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

15
Điều này giống với Hash [a3], vì a3 == a3.map {| k, v | [k, v]} là đúng, nó thực sự tương đương với a3.dup.
Cụm

2
Thay vì sử dụng bản đồ, tại sao không chỉ định độ sâu của flatten? Ví dụ: h3 = Hash[*a3.flatten(1)]thay vì h3 = Hash[*a3.flatten]đó sẽ ném một lỗi.
Jeff McCune

3
Câu trả lời này không hiệu quả. Nó cũng đã lỗi thời Xem câu trả lời của tôi.
Marc-André Lafortune

1
Vâng, tôi nghĩ Marc-André to_hlà tốt hơn.
B Bảy

1
@ Marc-André Lafortune cảm ơn bạn, tôi đã cập nhật câu trả lời của mình cho người dùng trực tiếp đến bạn.
Hầm

145

Đơn giản chỉ cần sử dụng Hash[*array_variable.flatten]

Ví dụ:

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

Sử dụng Array#flatten(1)giới hạn đệ quy để Arraycác khóa và giá trị hoạt động như mong đợi.


4
Ôi, tài hùng biện! Đây là lý do tại sao tôi yêu Ruby
iGbanam

11
CẢNH BÁO: câu trả lời bằng cách sử dụng flatten sẽ gây ra vấn đề nếu bạn muốn các khóa hoặc giá trị mảng.
Hầm

Tôi đã đăng một giải pháp thay thế bên dưới để tránh các vấn đề với khóa hoặc giá trị Array.
Hầm

5
Tốt hơn hết là đừng thử và làm một giải pháp tất cả cho việc này. Nếu các khóa và giá trị của bạn được ghép nối như trong [[key1, value1], [key2, value2]] thì chỉ cần chuyển nó vào Hash [] mà không cần vỗ béo. Hash [a2] == Hash [* a2.flatten]. Nếu mảng đã được làm phẳng như trong, [key1, value1, key2, value2] thì chỉ cần tiền tố var với *, Hash [* a1]
Cluster

8
FWIW, nếu bạn thực sự muốn (nhiều hơn một) phiên bản phù hợp với một kích thước, bạn cũng có thể sử dụng Hash[*ary.flatten(1)], điều này sẽ bảo tồn các khóa và giá trị mảng. Đó là đệ quy flattenđang phá hủy những thứ đó, đủ dễ để tránh.
brymck

79

Cách tốt nhất là sử dụng Array#to_h:

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

Lưu ý rằng to_hcũng chấp nhận một khối:

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

Lưu ý : to_hchấp nhận một khối trong Ruby 2.6.0+; cho những viên ruby ​​sớm bạn có thể sử dụng backportsđá quý của tôi vàrequire 'backports/2.6.0/enumerable/to_h'

to_h không có khối được giới thiệu trong Ruby 2.1.0.

Trước Ruby 2.1, người ta có thể sử dụng ít dễ đọc hơn Hash[]:

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

Cuối cùng, hãy cảnh giác với bất kỳ giải pháp nào bằng cách sử dụng flatten, điều này có thể tạo ra vấn đề với các giá trị là chính mảng.


4
Cảm ơn sự đơn giản của phương thức .to_h mới!
mã hóa nghiện

3
Tôi thích to_hphương pháp này tốt hơn các câu trả lời ở trên vì nó thể hiện ý định chuyển đổi sau khi hoạt động trên mảng.
B Bảy

1
@BSeven Không Array#to_hphải Enumerable#to_htrong ruby ​​lõi 1.9.
Cứu tinh sắt

Điều gì xảy ra nếu tôi có một mảng như [[apple, 1], [banana, 2], [apple, 3], [banana, 4]]và tôi muốn đầu ra là {"apple" =>[1,3], "banana"=>[2,4]}?
Nishant

@NishantKumar đó là một câu hỏi khác.
Marc-André Lafortune


9

Chỉnh sửa: Nhìn thấy các phản hồi được đăng trong khi tôi đang viết, Hash [a.flatten] dường như là cách để đi. Phải bỏ lỡ bit đó trong tài liệu khi tôi đang suy nghĩ thông qua phản hồi. Nghĩ rằng các giải pháp mà tôi đã viết có thể được sử dụng thay thế nếu cần.

Hình thức thứ hai đơn giản hơn:

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

a = mảng, h = hash, r = return-value hash (cái chúng ta tích lũy trong), i = item trong mảng

Cách gọn gàng nhất mà tôi có thể nghĩ đến khi thực hiện mẫu đầu tiên là như thế này:

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

2
+1 cho a.inject({})một lớp lót cho phép gán giá trị linh hoạt hơn.
Chris Bloom

Cũng có thể loại bỏ h = {}từ ví dụ thứ hai thông qua việc sử dụng chích, kết thúc bằnga.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
lindes

Bạn có thể làma.each_slice(2).to_h
Conor O'Brien

6

Bạn cũng có thể chỉ cần chuyển đổi một mảng 2D thành hàm băm bằng cách sử dụng:

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

Tóm tắt & TL; DR:

Câu trả lời này hy vọng sẽ là một thông tin tổng hợp toàn diện từ các câu trả lời khác.

Phiên bản rất ngắn, được cung cấp dữ liệu từ câu hỏi cộng với một vài bổ sung:

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

Thảo luận và chi tiết theo dõi.


Thiết lập: biến

Để hiển thị dữ liệu chúng tôi sẽ sử dụng trước, tôi sẽ tạo một số biến để thể hiện các khả năng khác nhau cho dữ liệu. Chúng phù hợp với các loại sau:

Dựa trên những gì trực tiếp trong câu hỏi, như a1a2:

(Lưu ý: Tôi cho rằng applebananacó nghĩa là đại diện cho các biến. Như những người khác đã làm, tôi sẽ sử dụng các chuỗi từ đây để đầu vào và kết quả có thể khớp.)

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

Khóa đa giá trị và / hoặc giá trị, như a3:

Trong một số câu trả lời khác, một khả năng khác đã được trình bày (mà tôi mở rộng ở đây) - các khóa và / hoặc giá trị có thể tự là mảng:

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

Mảng không cân bằng, như a4:

Để có biện pháp tốt, tôi nghĩ tôi nên thêm một cái cho trường hợp chúng ta có thể có đầu vào không đầy đủ:

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

Bây giờ, để làm việc:

Bắt đầu với một mảng phẳng ban đầu , a1:

Một số người đề nghị sử dụng #to_h(trong đó xuất hiện trong Ruby 2.1.0, và có thể được backported với các phiên bản trước đó). Đối với một mảng ban đầu phẳng, điều này không hoạt động:

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

Sử dụng Hash::[]kết hợp với toán tử splat nào:

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

Vì vậy, đó là giải pháp cho trường hợp đơn giản được đại diện bởi a1.

Với một mảng các mảng cặp khóa / giá trị , a2:

Với một mảng các mảng [key,value]kiểu, có hai cách để đi.

Đầu tiên, Hash::[]vẫn hoạt động (như đã làm với *a1):

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

Và sau đó cũng #to_hhoạt động ngay bây giờ:

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

Vì vậy, hai câu trả lời dễ dàng cho trường hợp mảng lồng đơn giản.

Điều này vẫn đúng ngay cả với các mảng con là khóa hoặc giá trị, như với 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]}

Nhưng sầu riêng có gai (cấu trúc dị thường cho vấn đề):

Nếu chúng tôi nhận được dữ liệu đầu vào không cân bằng, chúng tôi sẽ gặp vấn đề với #to_h:

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

Nhưng Hash::[]vẫn hoạt động, chỉ cần đặt nillàm giá trị cho durian(và bất kỳ phần tử mảng nào khác trong a4 chỉ là mảng 1 giá trị):

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

Làm phẳng - sử dụng các biến mới a5a6

Một vài câu trả lời khác được đề cập flatten, có hoặc không có 1đối số, vì vậy hãy tạo một số biến mới:

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

Tôi đã chọn sử dụng a4làm dữ liệu cơ sở vì vấn đề cân bằng chúng tôi gặp phải, đã xuất hiện a4.to_h. Tôi nghĩ rằng gọi điện thoại flattencó thể là một cách tiếp cận mà ai đó có thể sử dụng để cố gắng giải quyết vấn đề đó, có thể giống như sau.

flattenkhông có đối số ( a5):

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

Trong nháy mắt ngây thơ, điều này dường như công việc - nhưng nó đã đẩy chúng ta ra trên chân sai với cam không hạt, do đó cũng làm cho 3một chìa khóadurianmột giá trị .

Và điều này, cũng như a1, không hoạt động:

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

Vì vậy, a4.flattenkhông hữu ích cho chúng tôi, chúng tôi chỉ muốn sử dụngHash[a4]

Các flatten(1)trường hợp ( a6):

Nhưng những gì về chỉ làm phẳng một phần? Điều đáng chú ý là việc gọi Hash::[]bằng cách sử dụng splatmảng được làm phẳng một phần ( a6) không giống như gọi Hash[a4]:

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

Mảng được làm phẳng trước, vẫn được lồng (cách thay thế khác a6):

Nhưng nếu đây là cách chúng ta đã nhận được mảng ở vị trí đầu tiên thì sao? (Đó là, tương tự như a1, đó là dữ liệu đầu vào của chúng tôi - chỉ lần này một số dữ liệu có thể là mảng hoặc các đối tượng khác.) Chúng tôi đã thấy rằng Hash[*a6]nó không hoạt động, nhưng nếu chúng tôi vẫn muốn có hành vi trong đó yếu tố cuối cùng (quan trọng! xem bên dưới) đóng vai trò là chìa khóa cho một nilgiá trị?

Trong tình huống như vậy, vẫn có một cách để làm điều này, sử dụng Enumerable#each_sliceđể đưa chúng ta trở lại các cặp khóa / giá trị như các phần tử trong mảng bên ngoài:

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

Lưu ý rằng điều này cuối cùng sẽ cho chúng ta một mảng mới không " giống hệt " a4, nhưng có cùng các giá trị :

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

Và do đó chúng ta có thể sử dụng lại Hash::[]:

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

Nhưng có một vấn đề!

Điều quan trọng cần lưu ý là each_slice(2)giải pháp chỉ khiến mọi thứ trở lại bình thường nếu khóa cuối cùng là khóa bị thiếu. Nếu sau này chúng tôi đã thêm một cặp khóa / giá trị bổ sung:

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

Và hai băm mà chúng ta nhận được từ điều này là khác nhau theo những cách quan trọng:

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
}

(Lưu ý: Tôi đang sử dụng awesome_printap. Chỉ để làm cho nó dễ dàng hơn để hiển thị cấu trúc ở đây; không có yêu cầu về khái niệm cho việc này)

Vì vậy, each_slicegiải pháp cho đầu vào phẳng không cân bằng chỉ hoạt động nếu bit không cân bằng ở cuối.


Đi đường:

  1. Bất cứ khi nào có thể, hãy thiết lập đầu vào cho những thứ này dưới dạng [key, value]cặp (một mảng con cho mỗi mục trong mảng bên ngoài).
  2. Khi bạn thực sự có thể làm điều đó, #to_hhoặc Hash::[]cả hai sẽ làm việc.
  3. Nếu bạn không thể, Hash::[]kết hợp với splat ( *) sẽ hoạt động, miễn là đầu vào được cân bằng .
  4. Với một mảng không cân bằngphẳng làm đầu vào, cách duy nhất điều này sẽ hoạt động hợp lý là nếu mục cuối cùng value là thứ duy nhất còn thiếu.

Lưu ý bên lề: Tôi đang đăng câu trả lời này vì tôi cảm thấy có giá trị được thêm vào - một số câu trả lời hiện có thông tin không chính xác và không có câu trả lời nào tôi hoàn thành như tôi đang nỗ lực làm ở đây. Tôi hy vọng rằng nó hữu ích. Tuy nhiên, tôi cảm ơn những người đến trước tôi, một vài người đã truyền cảm hứng cho những phần của câu trả lời này.


3

Áp dụng cho câu trả lời nhưng sử dụng mảng ẩn danh và chú thích:

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

Lấy câu trả lời đó ra, bắt đầu từ bên trong:

  • "a,b,c,d" thực sự là một chuỗi.
  • split trên dấu phẩy thành một mảng.
  • zip mà cùng với các mảng sau.
  • [1,2,3,4] là một mảng thực tế.

Kết quả trung gian là:

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

làm phẳng sau đó biến đổi nó thành:

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

và sau đó:

*["a",1,"b",2,"c",3,"d",4] hủy đăng ký mà vào "a",1,"b",2,"c",3,"d",4

mà chúng ta có thể sử dụng làm đối số cho Hash[]phương thức:

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

mang lại:

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

Điều này cũng hoạt động mà không có splat ( *) và flatten: Hash[("a,b,c,d".split(',').zip([1,2,3,4]))]=> {"a"=>1, "b"=>2, "c"=>3, "d"=>4}. Chi tiết hơn trong một câu trả lời tôi đã thêm.
lindes

0

nếu bạn có mảng trông như thế này -

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

và bạn muốn các phần tử đầu tiên của mỗi mảng trở thành khóa cho hàm băm và phần tử còn lại trở thành mảng giá trị, sau đó bạn có thể làm một cái gì đó như thế này -

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

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

0

Không chắc chắn nếu đó là cách tốt nhất, nhưng điều này hoạt động:

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

Nếu các giá trị số là các chỉ mục seq, thì chúng ta có thể có những cách đơn giản hơn ... Đây là cách gửi mã của tôi, My Ruby hơi bị rỉ sét

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