3

I'm playing around with Ruby on Codecademy and not sure how to reduce this further. Current code is:

group_1 = [4.1, 5.5, 3.2, 3.3, 6.1, 3.9, 4.7]
group_2 = [7.0, 3.8, 6.2, 6.1, 4.4, 4.9, 3.0]
group_3 = [5.5, 5.1, 3.9, 4.3, 4.9, 3.2, 3.2]

over_4_feet = Proc.new { |height| height >= 4 }

can_ride_1 = group_1.select(&over_4_feet)
can_ride_2 = group_2.select(&over_4_feet)
can_ride_3 = group_3.select(&over_4_feet)

I wonder how I could get to something like this:

can_ride_(1..3).each {|x| group_(x).select(&over_4_feet)}

Is it possible, when objects hold repeatable patterns like these do, to use an Enumerable method in this way? I don't mind if it's a regex, but curious about what pattern might be recommended.

mahatmanich
  • 10,791
  • 5
  • 63
  • 82
mbb
  • 3,052
  • 1
  • 27
  • 28
  • have you looked at .send http://stackoverflow.com/questions/5349624/how-to-call-methods-dynamically-based-on-their-name – mahatmanich Feb 19 '17 at 21:42
  • I think dynamic dispatch is what you are looking for ... – mahatmanich Feb 19 '17 at 21:44
  • @mahatmanich doesn't `group_#{x}` work ? I remember seeing this `#` somewhere – niceman Feb 19 '17 at 21:49
  • Actually `.call` `.send` `.eval` are doing similar things => http://blog.khd.me/ruby/ruby-dynamic-method-calling/ – mahatmanich Feb 19 '17 at 21:51
  • @niceman no it does not, it will only do string interpolation, but will return a string. "group_#{x}" will return "group_1", "group_2" but will only return a string. `send("group_#{x}")` on the other hand should work! – mahatmanich Feb 19 '17 at 22:00

2 Answers2

1

Instead of having a variable for each group, you can have an array of groups, so you can map it to another array applying your select in each element.

groups = []

groups.push [4.1, 5.5, 3.2, 3.3, 6.1, 3.9, 4.7]
groups.push [7.0, 3.8, 6.2, 6.1, 4.4, 4.9, 3.0]
groups.push [5.5, 5.1, 3.9, 4.3, 4.9, 3.2, 3.2]

over_4_feet = Proc.new { |height| height >= 4 }

can_ride = groups.map { |group| group.select(&over_4_feet) }

puts can_ride
Gabriel
  • 1,922
  • 2
  • 19
  • 37
  • The variables `group_1`, `group_2`, `group_3` are givens in the question (check the link), so why not skip the creation of another variable and just write `[group_1, group_2, group_3].map ...`? – Cary Swoveland Feb 19 '17 at 22:30
  • @CarySwoveland See the first sentence wherein he states that you don't need to have “a variable for each group”. – coreyward Feb 19 '17 at 22:34
  • @coreyward, yes, but that changes the question, a no-no. Moreover, the solution should work with any value (array) for each of the three variables. This only works with three literal arrays. Yes, it's a poor question. – Cary Swoveland Feb 19 '17 at 22:48
  • @CarySwoveland, the code OP already has is correct (codecademy says it is OK), so I suppose OP does not want a code to pass the codecademy challenge (because he already nailed it). OP just want to know some way to generalize the code because he is seeing a lot of repetition and similar things. The fact this code is from codecademy is irrelevant. Look at the last sentence in the question: "Is it possible, when objects hold repeatable patterns like these do, to use an Enumerable method in this way?" – Gabriel Feb 19 '17 at 23:11
  • Even better is to just declare all the groups in one shot: `groups = [ [ ... ], [ ... ], .... ]` to avoid the repeated `push` calls. Also since that Proc is only used once it might as well be inlined. – tadman Feb 19 '17 at 23:48
  • Cool idea though not really why I was going for. Thanks for answering though @Gabriel – mbb Feb 20 '17 at 03:46
1

This is a really crappy question (the one on CodeAcademy) because the code blatantly doesn't represent the real world. It's contrived to the point that defining objects around the behavior is challenging. That said, here's another approach that's purely academic — don't ever do this in production code:

group_1 = …
group_2 = …
group_3 = …

can_ride_1 = can_ride_2 = can_ride_3 = nil

1.upto(3) do |i|
  group = binding.local_variable_get("group_#{i}")
  binding.local_variable_set("can_ride_#{i}", group.select { |v| v >= 4 })
end

Here's another exploitation of this:

eligible_riders = -> (group_num) do
  group = binding.local_variable_get("group_#{group_num}")
  group.select { |v| v >= 4 }
end

can_ride_1 = eligible_riders[1]
can_ride_2 = eligible_riders[2]
can_ride_3 = eligible_riders[3]

A more appropriate way of doing this would be to extract an object to represent each group:

class Group < Array
  def select_eligible
    select { |v| v >= 4 }
  end
end

group_1 = Group.new [1, 2, 3, 4, 5]
group_2 = Group.new [1, 2, 3, 4, 5]
group_3 = Group.new [1, 2, 3, 4, 5]

eligible = [group_1, group_2, group_3].map &:select_eligible
can_ride_1, can_ride_2, can_ride_3 = *eligible

Or you can take advantage of those splat enhancements using the proc you have:

can_ride_1, can_ride_2, can_ride_3 = *[group_1, group_2, group_3].map do |g|
  g.select &over_4_feet
end
coreyward
  • 77,547
  • 20
  • 137
  • 166
  • Yeah the amount of dancing needed to get to the string interpolation setup is a no go. Still a fun mental exercise in the context (and FWIW, the problem is great at isolating in on different block behaviors as silly as it is on its own). Thanks! – mbb Feb 20 '17 at 03:34