简体   繁体   中英

Ruby - iterate over array of irregularly repeating hashes

Example input array of hashes ( with some regular and irregular repeating pattern ) :

[{ key1 => value1 }, { key2 => value2 }, { key3 => value3 }, 
 { key4 => value4 }, { key3 => value5 }, { key4 => value6 }, 
 { key1 => value7 }, { key2 => value8 }, { key3 => value9 }, 
 { key4 => value10 }, { key3 => value11 }, { key4 =>  value12 }, { key3  => value13 }, { key4 => value14 },
 { key1 => value15 }, { key2 => value16 }, { key3 =>   value17 }, { key4 => value18 } ]

Requirement is to convert the above to this :

 [ [{ key1 => value1 }, { key2 => value2 },
    { key3key4 => value3value4value5value6 } ]
   [ { key1 => value7 }, { key2 => value8 }, 
     { key3key4 =>       value9value10value11value12value13value14 }]
   [ { key1 => value15 }, { key2 => value16 }, { key3key4 =>   value17value18 }] ]

( note, eg, key3key4 and value5value6value7value8 etc, are just concatenations of the keys and values )

The input structure could be of any number of repeating hashes with key3 and key4 appearing irregularly, sometimes just two hashes, sometimes 3 etc..

I understand array.each and array.map etc, and I think to achieve the above it may need counters or similar employed, but so far my efforts have quickly ended up in a tangled mess, hence posting here for some advice on how best to tackle this.

Edit: a little more rationale, key3 and key4 ( and their values ) get concatenated as they are the keys where the hashes repeat ( irregularly ) in each loop of the pattern, and there is desire to collapse that down into one key:value pair hash in each 'repeat' of the pattern in the output. The values are strings in real problem and it makes sense when they are concatenated

If the hashes repeat after all the key3 and key4 , then key1 will always be the first key, so we can use that and the slice_before method on arrays to split the array into the proper groupings:

output = input.slice_before { |hash| hash.has_key?('key1') }

If the condition for a grouping isn't as simple as just 'always start with key1 , you can use slice_when to split a group when 2 adjacent hashes occur where the first contains a concatenate-able key and the second does not:

output = input.slice_when do |current_hash, next_hash|
  concatenate_keys.any? { |key| current_hash.has_key?(key) } &&
    concatenate_keys.none? { |key| next_hash.has_key?(key) }
end 

From there, we just need to go through and collapse all key3 and key4 hashes into a single hash, which we can do with map , select and reject :

concatenate_keys = %w[key3 key4]
contains_concatenate_key = -> hash { concatenate_keys.any? { |key| hash.has_key?(key) } }

output = output.map do |grouping|
  concatenatable_hashes = grouping.select &contains_concatenate_key

  grouping.reject(&contains_concatenate_key) + [
    { concatenate_keys.join => concatenatable_hashes.flat_map(&:values).join }
  ]
end

p output
# => [[{"key1"=>"value1"}, {"key2"=>"value2"}, {"key3key4"=>"value3value4value5value6"}],
#     [{"key1"=>"value7"}, {"key2"=>"value8"}, {"key3key4"=>"value9value10value11value12value13value14"}],
#     [{"key1"=>"value15"}, {"key2"=>"value16"}, {"key3key4"=>"value17value18"}]]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM