2012-03-10 40 views
24

Câu hỏi này là nghịch đảo của this question.Chuyển đổi băm lồng vào băm phẳng

Cho một băm lồng nhau như

{ 
    :a => { 
     :b => {:c => 1, :d => 2}, 
     :e => 3, 
    }, 
    :f => 4, 
} 

cách tốt nhất để chuyển đổi nó thành một băm phẳng như

{ 
    [:a, :b, :c] => 1, 
    [:a, :b, :d] => 2, 
    [:a, :e] => 3, 
    [:f] => 4, 
} 
+3

Vì vậy, bạn muốn một mảng các phím để dẫn đến một giá trị? – Linuxios

Trả lời

15

Một cách khác:

def flat_hash(h,f=[],g={}) 
    return g.update({ f=>h }) unless h.is_a? Hash 
    h.each { |k,r| flat_hash(r,f+[k],g) } 
    g 
end 

h = { :a => { :b => { :c => 1, 
         :d => 2 }, 
       :e => 3 }, 
     :f => 4 } 

flat_hash(h) #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4} 
+0

Điều này rất nhanh. – sawa

+0

Tôi biết Ruby không nhất thiết phải hỗ trợ TCO ra khỏi hộp, nhưng nếu bạn không trả lại 'g' ở đây, liệu đây có phải là đuôi được tối ưu hóa không? – rusty

+0

Tôi đã nghe thuật ngữ "đuôi gọi tối ưu hóa" nhưng tôi không biết ý nghĩa của nó. Có lẽ một nhà khoa học máy tính có thể trả lời câu hỏi của bạn. –

5

là gì Đây không phải là một nỗ lực để cung cấp cho bạn tốt nhất cách để làm điều đó, nhưng nó là một cách: P

def flatten(hash) 
    return {[] => hash} if !hash.is_a?(Hash) 
    map = {} 
    hash.each_pair do |key1, value1| 
    flatten(value1).each_pair do |key2, value2| 
     map[[key1] + key2] = value2 
    end 
    end 
    return map 
end 

Nó hoạt động cho bạn ví dụ, tạo ra kết quả này:

{[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4} 

Nó không thể tạo ra kết quả bạn mong đợi nếu có băm rỗng.

15

Rất giống với giải pháp Adiel Mittmann của

def flat_hash(h, k = []) 
    new_hash = {} 
    h.each_pair do |key, val| 
    if val.is_a?(Hash) 
     new_hash.merge!(flat_hash(val, k + [key])) 
    else 
     new_hash[k + [key]] = val 
    end 
    end 
    new_hash 
end 

Edit: refactored cho sang trọng. Nên gần như là nhanh.

def flat_hash(hash, k = []) 
    return {k => hash} unless hash.is_a?(Hash) 
    hash.inject({}){ |h, v| h.merge! flat_hash(v[-1], k + [v[0]]) } 
end 
+0

Cái này chạy nhanh nhất. Cảm ơn. – sawa

+1

@sawa: Chỉ cần một mẹo cho tương lai: Nếu bạn muốn có giải pháp nhanh, hãy đề cập đến vấn đề này trong câu hỏi lần sau. Thông thường, tiêu chí chính trong các ngôn ngữ động như Python hay Ruby là sự thanh lịch và đồng nhất. Nếu bạn yêu cầu cụ thể cho hiệu suất là tốt, bạn có thể nhận được câu trả lời phù hợp hơn nhiều :) –

+1

@sawa: refactored cho sang trọng. – Kyle

8

nỗ lực của tôi:

def flatten_hash(h) 
    return { [] => h } unless h.is_a?(Hash) 
    Hash[h.map { |a,v1| flatten_hash(v1).map { |b,v2| [[a] + b, v2] } }.flatten(1)] 
end 

Xin lỗi vì những cái tên biến xấu, phải phù hợp với nó trong một dòng.

3

Một cách tiếp cận chức năng (xem history cho một triển khai thay thế):

def recursive_flatten(hash) 
    hash.flat_map do |key, value| 
    if value.is_a?(Hash) 
     recursive_flatten(value).map { |ks, v| [[key] + ks, v] } 
    else 
     [[[key], value]] 
    end 
    end.to_h 
end 
1

Lấy cảm hứng từ @ cary-swoveland bằng cách nào, nhưng trong lớp Hash:

class Hash 
    def deep_flatten(previous_key=[]) 
    flat_hash = {} 
    self.each do |key, value| 
     next_key = previous_key+[key] 
     flat_hash.update(value.is_a?(Hash) ? value.deep_flatten(next_key) : {next_key=>value}) 
    end 
    return flat_hash 
    end 
end 

h = { :a => { :b => { :c => 1, :d => 2 }, :e => 3 }, :f => 4 } 

h.deep_flatten #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4} 
0

Giải pháp khai báo sử dụng DeepEnumerable:

require 'deep_enumerable' 

h = { :a => { :b => { :c => 1, :d => 2 }, :e => 3 }, :f => 4 } 

h.deep_each.map do |k, v| 
    [DeepEnumerable.deep_key_to_array(k), v] 
end.to_h 

hoặc, đối với những người thích point-free style

h.deep_each.to_h.shallow_map_keys(&DeepEnumerable.method(:deep_key_to_array)) 
1

mảng hỗ trợ/tên có thể đọc/không cập nhật cho các phím tốc độ/kết quả chuyển đổi thành chuỗi

def flat_hash(input, base = nil, all = {}) 
    if input.is_a?(Array) 
    input = input.each_with_index.to_a.each(&:reverse!) 
    end 

    if input.is_a?(Hash) || input.is_a?(Array) 
    input.each do |k, v| 
     flat_hash(v, base ? "#{base}.#{k}" : k, all) 
    end 
    else 
    all[base] = input 
    end 

    all 
end 
Các vấn đề liên quan