Home > Software engineering >  How to combine and print values of nested object structure?
How to combine and print values of nested object structure?

Time:09-25

I have an array of hashes and each hash needs to be printed out on the same line.

init_cards_j = [
  { init1: [{ num: 6, head: 1 }, { num: 10, head: 1 }] },
  { init2: [{ num: 53, head: 1 }, { num: 60, head: 1 }] },
  { init3: [{ num: 77, head: 5 }] },
  { init4: [{ num: 89, head: 1 }] }
]

I want it to be looked like this:

Card 6: 1 head / Card 10: 1 head
Card 53: 1 head / Card 60: 1 head
Card 77: 1 head
Card 89: 1 haed

I tried below:

init_cards_j.each_index do |index|
  init_cards_j[index].values[0].each do |pair|
    init_cards_j[index].each do |key, value|
      value.each do |pair|
        puts "Card #{pair[:num]}: #{pair[:head]} kettle head. / "
      end
    end
  end
end

Which outputs the data as:

Card 6: 1 kettle head. / 
Card 10: 1 kettle head. / 
Card 6: 1 kettle head. / 
Card 10: 1 kettle head. / 
Card 53: 1 kettle head. / 
Card 60: 1 kettle head. / 
Card 53: 1 kettle head. / 
Card 60: 1 kettle head. / 
Card 77: 5 kettle head. / 
Card 89: 1 kettle head. / 

I am sorry that my array is so nested as I need to sort the order by num for each hash, I put them in an array.

Thanks in advance!

CodePudding user response:

You can iterate the hashes in init_cards_j via each and for each hash call each_value to traverse its values. Then, instead of printing every card right away, you can generate an intermediate array via map and join the result using your separator string:

init_cards_j.each do |hash|
  hash.each_value do |values|
    puts values.map { |c| "Card #{c[:num]}: #{c[:head]} head" }.join(' / ')
  end
end

But it would be easier to convert the array of hashes to a single hash: (assuming that their keys are unique)

cards = {
  init1: [{ num: 6, head: 1 }, { num: 10, head: 1 }],
  init2: [{ num: 53, head: 1 }, { num: 60, head: 1 }],
  init3: [{ num: 77, head: 5 }],
  init4: [{ num: 89, head: 1 }]
}

If you must retain the given structure, you could convert it on the fly:

cards = init_cards_j.inject(&:merge)

This makes the loop a bit simpler:

cards.each_value do |values|
  puts values.map { |c| "Card #{c[:num]}: #{c[:head]} head" }.join(' / ')
end

CodePudding user response:

I would recommend @Stefan's first answer, but here is another way to achieve the same result.

We might begin with a helper method, which of course you can test separately:

def print_num_and_head(g)
  print "Card %d: %d head" % g.values_at(:num, :head)
end

If

g = {:num=>6, :head=>1})

then

print_num_and_head(g)

displays

Card 6: 1 head

without a line terminator.

We may then write:

init_cards_j.each do |h|
  _, (g, *arr) = h.flatten
  print_num_and_head(g)
  arr.each do |g|
    print ' / '
    print_num_and_head(g)
  end
  puts   
end

prints

Card 6: 1 head / Card 10: 1 head
Card 53: 1 head / Card 60: 1 head
Card 77: 5 head
Card 89: 1 head

Here I've used Ruby's powerful array#decomposition (a.k.a array disambiguation) syntax to advantage:

If

h = { init1: [{ num: 6, head: 1 }, { num: 10, head: 1 }] }

then

_, (g, *arr) = h.flatten
  #=> [:init1, [{:num=>6, :head=>1}, {:num=>10, :head=>1}]]
_ #=> :init1
g #=> {:num=>6, :head=>1}
arr
  #=> [{:num=>10, :head=>1}]

I've employed the common convention of representing a block variable with an underscore when it is not used in the block calculation.

See this article for a fuller explanation array decomposition.

  •  Tags:  
  • ruby
  • Related