0

I have a complex data structure that looks as follows:

ds1 =
{
  'item1' =>
  {
    'value' => '1024',
    'flavor' => %w(s m l xl),
    'platform_version' => %w(7),
  },
  'item2' =>
  [{
    'value' => '2000000',
    'flavor' => %w(l xl),
    'platform_version' => %w(7),
  },
  {
    'value' => '1000000',
    'flavor' => %w(s m),
    'platform_version' => %w(6),
  },],
}

I'm currently looping over this as follows:

ds1.each do |name, obj|
  if obj.is_a?(Array)
    # Found that the data structure has multiple scenarios for the same key and need to loop over each element
    obj.each do |sub_obj|
      next unless flavor_check?(sub_obj, s_lit, 'flavor') # calls flavor_check? function
      obj_func(name, sub_obj) # call a function here
    end
  else
    # hash only has one element so treat normally, no need for another loop
    next unless flavor_check?(sub_obj, s_lit, 'flavor') # calls flavor_check? function
    obj_func(name, sub_obj) # call a function here
  end
end

Sometimes I have much more code that I need to execute and having to repeat it twice depending on whether the "obj" is an array or not just makes my code look busy/ugly. Is there a more elegant way to handle this?

sconicelli
  • 101
  • 8

1 Answers1

2

You could just wrap it in an array all the time:

ds1.each do |name, obj|
  [obj].flatten(1).each do |sub_obj|
    next unless flavor_check?(sub_obj, s_lit, 'flavor') # calls flavor_check? function
    obj_func(name, sub_obj) # call a function here
  end
end
spickermann
  • 100,941
  • 9
  • 101
  • 131
  • I tried this before but since "obj" is a hash, I think I am running into something similar to https://stackoverflow.com/questions/18358717/ruby-elegantly-convert-variable-to-an-array-if-not-an-array-already – sconicelli Mar 25 '19 at 14:07
  • @sconicelli You are right. I wasn't aware of this edge case. I changed my answer to that suggested version from the other question which I liked best. – spickermann Mar 25 '19 at 14:23
  • Thanks! made sense for me to just extend Array like rails does... not sure why Ruby itself doesn't add this feature. Problem solved though... – sconicelli Mar 25 '19 at 14:34
  • You can replace `[obj].flatten(1)` with `Array(obj)` or `[*obj]`. – Cary Swoveland Mar 31 '19 at 02:52
  • @CarySwoveland: Unfortunately, `Array(obj)` or `[*obj]` doesn't work in this example, because here the single element would be a hash and the expected the response would be an array with one hash inside. But instead, an array of arrays is returned. See: https://stackoverflow.com/q/18358717/2483313 – spickermann Mar 31 '19 at 05:22
  • I didn't know that! (I guess that's obvious.) I'll leave my comment for its educational value, unless you prefer that I remove it. – Cary Swoveland Mar 31 '19 at 05:42
  • Is `(1)` needed? – Cary Swoveland Mar 31 '19 at 05:47
  • Yes `(1)` is needed to cover another edge case: When obj is already a nested array, `flatten` (without the parameter) would remove the nesting. – spickermann Mar 31 '19 at 05:56
  • @CarySwoveland Seems like we both learned the same things and asked the same questions today while thinking about an answer. Please leave your comment! – spickermann Mar 31 '19 at 05:57
  • Re `(1)`, I was going by the nesting in the example, but you have a good point--better to cover the general case. – Cary Swoveland Mar 31 '19 at 06:45