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}
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:
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}
h3 = Hash[*a3.flatten(1)]
thay vì h3 = Hash[*a3.flatten]
đó sẽ ném một lỗi.
to_h
là tốt hơn.
Đơ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 để Array
các khóa và giá trị hoạt động như mong đợi.
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.
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_h
cũ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_h
chấ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.
to_h
phươ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.
Array#to_h
phải Enumerable#to_h
trong ruby lõi 1.9.
[[apple, 1], [banana, 2], [apple, 3], [banana, 4]]
và tôi muốn đầu ra là {"apple" =>[1,3], "banana"=>[2,4]}
?
Cập nhật
Ruby 2.1.0 được phát hành ngày hôm nay . Và tôi đi kèm với Array#to_h
( ghi chú phát hành và ruby-doc ), giải quyết vấn đề chuyển đổi một Array
thành a Hash
.
Ví dụ về tài liệu Ruby:
[[:foo, :bar], [1, 2]].to_h # => {:foo => :bar, 1 => 2}
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 }
a.inject({})
một lớp lót cho phép gán giá trị linh hoạt hơn.
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 }
a.each_slice(2).to_h
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.
Để 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:
a1
và a2
:(Lưu ý: Tôi cho rằng apple
và banana
có 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
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] ],
]
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!
]
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
.
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_h
hoạ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.
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]}
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 nil
là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}
a5
vàa6
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 a4
là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 flatten
có 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.
flatten
khô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 3
một chìa khóa và durian
mộ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.flatten
không hữu ích cho chúng tôi, chúng tôi chỉ muốn sử dụngHash[a4]
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 splat
mả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
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 nil
giá 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]
Đ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_print
là ap
. 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_slice
giả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.
[key, value]
cặp (một mảng con cho mỗi mục trong mảng bên ngoài).#to_h
hoặc Hash::[]
cả hai sẽ làm việc.Hash::[]
kết hợp với splat ( *
) sẽ hoạt động, miễn là đầu vào được cân bằ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.
Á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}
*
) 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.
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]}
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
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}