Reputation: 1524
Here's a function in Ruby to find if 2 unique number in an array add up to a sum:
def sum_eq_n? (arr, n)
return true if arr.empty? && n == 0
p "first part array:" + String(arr.product(arr).reject { |a,b| a == b })
puts "\n"
p "first part bool:" + String(arr.product(arr).reject { |a,b| a == b }.any?)
puts "\n"
p "second part:" + String(arr.product(arr).reject { |a,b| a + b == n } )
puts "\n"
result = arr.product(arr).reject { |a,b| a == b }.any? { |a,b| a + b == n }
return result
end
#define inputs
l1 = [1, 2, 3, 4, 5, 5]
n = 10
#run function
print "Result is: " + String(sum_eq_n?(l1, n))
I'm confused how the calculation works to produce result. As you can see I've broken the function down into a few parts to visualize this. I've researched and understand the .reject
and the .any?
methods individually.
However, I'm still confused on how it fits all together in the 1 liner. How are the 2 blocks evaluated in combination? I've only found examples with .reject
with 1 code block afterwards. Is .reject
applied to both? I also thought there might be an implicit AND in between the 2 code blocks, but I tried to add a 3rd dummy block and it failed, so at this point I'm just not really sure how it works at all.
Upvotes: 1
Views: 213
Reputation: 16737
You can interpret the expression via these equivalent substitutions:
# orig
arr.product(arr).reject { |a,b| a == b }.any? { |a,b| a + b == n }
# same as
pairs = arr.product(arr)
pairs.reject { |a,b| a == b }.any? { |a,b| a + b == n }
# same as
pairs = arr.product(arr)
different_pairs = pairs.reject { |a,b| a == b }
different_pairs.any? { |a,b| a + b == n }
Each block is an argument for the respective method -- one for reject
, and one for any?
. They are evaluated in order, and are not combined. The parts that make up the expression can be wrapped in parenthesis to show this:
((arr.product(arr)).reject { |a,b| a == b }).any? { |a,b| a + b == n }
# broken up lines:
(
(
arr.product(arr) # pairs
).reject { |a,b| a == b } # different_pairs
).any? { |a,b| a + b == n }
Blocks in Ruby Are Method Arguments
Blocks in Ruby are first-class syntax structures for passing closures as arguments to methods. If you're more familiar with object-oriented concepts than functional ones, here is an example of an object (kind of) acting as a closure:
class MultiplyPairStrategy
def perform(a, b)
a * b
end
end
def convert_using_strategy(pairs, strategy)
new_array = []
for pair in pairs do
new_array << strategy.perform(*pair)
end
new_array
end
pairs = [
[2, 3],
[5, 4],
]
multiply_pair = MultiplyPairStrategy.new
convert_using_strategy(pairs, multiply_pair) # => [6, 20]
Which is the same as:
multiply_pair = Proc.new { |a, b| a * b }
pairs.map(&multiply_pair)
Which is the same as the most idiomatic:
pairs.map { |a, b| a * b }
Upvotes: 2
Reputation: 211740
Each operation "chains" into the next, which visualized looks like:
arr.product(arr).reject { |a,b| a == b }.any? { |a,b| a + b == n }
|--|------A----->-----------B----------->-------------C----------|
Where part A, calling .product(arr)
, evaluates to an object. This object has a reject
method that's called subsequently, and this object has an any?
method that's called in turn. It's a fancy version of a.b.c.d
where one call is used to generate an object for a subsequent call.
What's not apparent from that is the fact that product
returns an Enumerator, which is an object that can be used to fetch the results, but is not the actual results per-se. It's more like an intent to return the results, and an ability to fetch them in a multitude of ways. These can be chained together to get the desired end product.
As a note this code can be reduced to:
arr.repeated_permutation(2).map(&:sum).include?(n)
Where the repeated_permutation
method gives you all 2-digit combinations of numbers without duplicate numbers. This can be easily scaled up to N digits by changing that parameter. include?
tests if the target is present.
If you're working with large arrays you may want to slightly optimize this:
arr.repeated_permutation(2).lazy.map(&:sum).include?(n)
Where that will stop on the first match found and avoid further sums. The lazy
call has the effect of propagating individual values through to the end of the chain instead of each stage of the chain running to completion before forwarding to the next.
The idea of lazy
is one of the interesting things about Enumerable. You can control how the values flow through those chains.
Upvotes: 2
Reputation: 132
The return result of the first method is returned and used by the second method.
This:
result = arr.product(arr).reject { |a,b| a == b }.any? { |a,b| a + b == n }
is functionality equivalent to:
results = arr.product(arr).reject { |a,b| a == b} # matrix of array pairs with identical values rejected
result = results.any? { |a,b| a + b == n } #true/false
This might be best visualized in pry
(comments mine)
[1] pry(main)> arr = [1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
[2] pry(main)> n = 10
=> 10
[3] pry(main)> result_reject = arr.product(arr).reject { |a,b| a == b } # all combinations of array elements, with identical ones removed
=> [[1, 2],
[1, 3],
[1, 4],
[1, 5],
[1, 5],
[2, 1],
[2, 3],
[2, 4],
[2, 5],
[2, 5],
[3, 1],
[3, 2],
[3, 4],
[3, 5],
[3, 5],
[4, 1],
[4, 2],
[4, 3],
[4, 5],
[4, 5],
[5, 1],
[5, 2],
[5, 3],
[5, 4],
[5, 1],
[5, 2],
[5, 3],
[5, 4]]
[4] pry(main)> result_reject.any? { |a,b| a + b == n } # do any of the pairs of elements add together to equal ` n` ?
=> false
[5] pry(main)> arr.product(arr).reject { |a,b| a == b }.any? { |a,b| a + b == n } # the one liner
=> false
Upvotes: 2