Ruby中如何扁平化嵌套哈希?动态键名场景下的快速实现
Hey there! Let's work through this nested hash flattening problem you're facing—since you need to skip specific keys and handle dynamic key names, a recursive approach is going to be your best bet here. It's flexible enough to adapt to changing key names and lets you easily exclude any keys you don't want in the final flattened hash.
Step 1: Basic Recursive Flattener with Key Exclusion
First, let's start with a core method that recursively traverses your nested hash, skips the specified keys, and flattens the structure. This works for most cases where you don't have duplicate key names across nested levels.
Example Nested Hash
Let's use a sample hash to test with:
nested_hash = { us: { name: "USA", population: 331_000_000 }, au: { name: "Australia", population: 25_700_000 }, eu: { de: { name: "Germany", population: 83_200_000 }, ca: { name: "Canada", population: 38_000_000 }, fr: { name: "France", population: 67_400_000 } } }
The Flattening Method
def flatten_hash(hash, exclude_keys = []) hash.each_with_object({}) do |(key, value), result| # Skip any keys in our exclusion list next if exclude_keys.include?(key) if value.is_a?(Hash) # Recursively flatten nested hashes and merge into the result result.merge!(flatten_hash(value, exclude_keys)) else # Add non-hash values directly to the result result[key] = value end end end
How to Use It
Call the method with your hash and the list of keys to exclude:
flattened_result = flatten_hash(nested_hash, [:au, :ca]) puts flattened_result.inspect # Output: {:name=>"USA", :population=>331000000, :name=>"Germany", :population=>83200000, :name=>"France", :population=>67400000}
Step 2: Handling Duplicate Keys (Optional)
Notice in the output above that duplicate keys like :name get overwritten (only the last one stays). If you want to preserve context from nested levels, you can modify the method to prepend parent keys as a prefix:
Updated Method with Key Prefixes
def flatten_hash(hash, exclude_keys = [], parent_prefix = "") hash.each_with_object({}) do |(key, value), result| next if exclude_keys.include?(key) # Build a unique key by combining parent prefix and current key current_key = parent_prefix.empty? ? key : "#{parent_prefix}_#{key}".to_sym if value.is_a?(Hash) # Pass the current key as the new parent prefix for recursion result.merge!(flatten_hash(value, exclude_keys, current_key)) else result[current_key] = value end end end
Test the Updated Version
flattened_result = flatten_hash(nested_hash, [:au, :ca]) puts flattened_result.inspect # Output: {:us_name=>"USA", :us_population=>331000000, :eu_de_name=>"Germany", :eu_de_population=>83200000, :eu_fr_name=>"France", :eu_fr_population=>67400000}
Why This Works for Dynamic Key Names
Since we're passing the exclusion list as a parameter, you can easily update which keys to skip without modifying the method itself. For example, if next week you need to exclude :us and :fr instead, just call:
flatten_hash(nested_hash, [:us, :fr])
This approach is scalable for deeply nested hashes and keeps your code clean and maintainable.
内容的提问来源于stack exchange,提问作者Tintin81




