1
\$\begingroup\$

When there is a situation where I want to accumulate a value I see myself using inject/reduce with a conditional statement. E.g., when I want to add 1 to a value each time an element in a list has a certain value I do something like this:

puts names.reduce(0) {|acc, name| triangles.include?(name.points) ? acc+=1 : acc}

Here names is an array of names, triangles is a list of the first 30 triangle integers (1/2n(n+1)) and points is a method added to class String which calculates the integers value of each character in the name and adds them. So the point is to check how many names in the list are triangle values when you calculate the points in that way.

The issue I have is with the conditional area ([conditional] ? acc+=1 : acc). You can also rewrite the method as:

result = 0
names.each {|name| result +=1 if triangles.include?(name.points)}
puts result

But this method uses three lines and has to initialize the variable at first.

Is there a better way to keep track of the amount (e.g., result, or the acc in the inject/reduce) that does not use the conditional operator (?) or costs three lines of code?

\$\endgroup\$
3
  • \$\begingroup\$ triangles is an array? better a structure with O(1) inclusion predicates (i.e. a set). \$\endgroup\$ – tokland May 17 '14 at 18:11
  • \$\begingroup\$ triangles is an array of integers, yes. @tokland what do you mean with a O(1) inclusion predicate/set and how would I implement this? \$\endgroup\$ – Erwin Rooijakkers May 18 '14 at 21:35
  • 1
    \$\begingroup\$ array.include?(item) is O(n) on arrays but O(1) on set. set = array.to_set and then use it for inclussion tests. \$\endgroup\$ – tokland May 18 '14 at 21:49
3
\$\begingroup\$

There's a built-in count method which handles your example cleanly:

names.count {|name| triangles.include?(name.points) }

Also, the accumulator version should use +, not +=. reduce uses the block's return value; there's no need to modify acc.

In general, though, there's no simpler way to accumulate values than reduce. It's not very clear, but all of the alternatives are worse.

Don't be afraid of the conditional operator. It's just a different way to spell if...else.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks! The count method is a nicer notation in this case. It takes exactly the same time as reduce. \$\endgroup\$ – Erwin Rooijakkers May 17 '14 at 15:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.