About the inject method: How does it work? - ruby

This part of code calculates the value of each arithmetic series up to and including the number that the user put in:
print "enter a number: "
num = gets.to_i
(1..num).inject(0) do |res, e|
res += e
p res
end
I think that (1..num) is the range, with num being the user input. I know that inject combines all elements of enum by applying a binary operation specified by a block or a symbol that names a method or operator.
I don't understand what each element in this line does:
(1..num).inject(0) do |res, e|
What does |res, e| mean? It must be the block that defines what inject does, but what does for instance res and e stand for? (e is probably element?)
What does (0) stand for?
What does the command do do?
what is its connection in regard to (1..num) and inject(0)?
Am I right to assume that p at the end just stands for puts or print?

inject takes an optional start value, and a block taking an intermediate value and element and returning a new intermediate value.
So:
What does (0) stand for?
The start value parameter to inject.
What does the command "do" do?
It is not a command; it marks the start of the block (terminated by end). .inject(0) do ... end is almost (except for some syntactic issues) the same as .inject(0) { ... }. Usually, do ... end is used for multi-line blocks and { ... } for single-line blocks, but it is not a rule.
What does |res, e| mean?
Those are the block parameters (intermediate value and current element), here probably called after "result" and "element", respectively.
Let's see on a simplified example: (1..3).inject(0) do |res, e| res + e end will set the intermediate result to 0. Then it will pass this intermediate result and the first element of the enumerable being injected: res is 0 and e is 1. The value of the block is the value of its last expression, which is 1 (result of 0 + 1). This 1 now becomes the new intermediate value, and 2 becomes the next current element. The value of the block is 3 (result of 1 + 2). In the next iteration, intermediate value is 3, and the current element also 3, resulting in 6 (3 + 3). The range will stop yielding elements now that we reached its upper boundary, and inject returns with the last intermediate result calculated, 6.
Also, the last question am I right to assume that "p" at the end just stands for puts or print?
Almost. p is its own beast. p x is roughly synonymous with puts x.inspect; x - i.e. it prints the value in a bit different format, and unlike puts which always returns nil, p returns the value unchanged. Thus, p res at the end of your block will not destroy the code by making it return nil, but transparently return res.

inject is a method that boils a collection (eg an array or range) down to a single value. It does this by executing the block once for each element in the collection. The block takes two arguments: the current value being worked on, and the single value that will eventually be returned. inject itself takes one argument (aside from the block), which is its initial starting value.
Take this example.
x = [1, 2, 3, 4, 5].inject(0) do |result, current|
result + current
end
We have a list of numbers, [1, 2, 3, 4, 5]. We're going to boil them down into one single number.
We start with 0, because that's inject's argument. That means that the first time the block runs, result will be 0.
So the block runs for the first time. result is 0, current is 1, the first element. We say result + current (which is 1). It's the last expression inside the block, so it's what that block 'returns'.
At the end of the block, inject says "Okay, do we have more elements in the collection?" Yeah. So the block runs again. This time, result is whatever the last block returned, which was 1, and current is the second element, 2.
The block runs, and finishes with result + current, or 1 + 2, which is 3. There are still elements left, so we run again. This time, result is 3, and current is 3. result + current, 6. Still more values to go, on the next run result is 6 and current is 4. 6 + 4 = 10. Still more values to go, on the next run result is 10 and current is 5. 10 + 5 = 15.
Then the block finishes, and there are no more elements left. So inject itself returns with the final value, 15. So in the end, x = 15. We boiled down our list into one number by adding things up.
res in your example stands for result, and e for element. You can call them anything you want. You might call them sum while adding, or product if multiplying. But they don't have to be numbers. You could use inject to boil an array of strings, a range of characters, an array of arrays, whatever collection you want. The block just tells it how.

Excited to see that you're learning Ruby, welcome!
At your level of expertise best learn from a book.
Get yourself a copy of the "Pickaxe Book" — this is by far the best book.
The first edition fir Ruby 1.9 is available online, http://ruby-doc.com/docs/ProgrammingRuby
And here is a popular online tutorial, http://poignant.guide
Some quick answers
1..num is a range object
.inject(0) do ... end is a method call with TWO parameters, the value 0 and a code block
do |a, b| ... end is a code block with two parameters
res and e are VERY bad variable names, maybe better use sum and each?
p is a global method that prints debug information, similar to puts but not the same
Hope that helps to unblock you.

Related

Why do I get a 'typeerror' when using inject in Ruby?

I am using this inject method to make a running total of values into an array. I am trying to figure out why I am getting an error.
def running_totals(myarray)
results = []
myarray.inject([]) do |sum,n|
results << sum + n
end
results
end
p running_totals([1,2,3,4,5])
I am getting the error
in `+': no implicit conversion of Fixnum into Array (TypeError)
When breaking this method down, isn't this the same as adding two integers and adding that into an array? I'm a bit confused here. Thanks for the help.
In the first iteration sum will be an array (as you specified an array as the default when calling inject([])) and you try to add a number to it. in the results << sum + n statement
Instead, set the initial value to 0, then add, then add the result to the array, then make sure you let sum get passed into the next iteration of inject.
def running_totals(myarray)
results = []
myarray.inject(0) do |sum,n| # First iteration sum will be 0.
sum += n # Add value to sum.
results << sum # Push to the result array.
sum # Make sure sum is passed to next iteration.
end
results
end
p running_totals([1,2,3,4,5]) #=> [1, 3, 6, 10, 15]
The result of results << sum + n is an array results and it's this that's replacing the sum value and so the next iteration you're trying to add a fixnum n into an array sum ... plus it doesn't help that you're initializing the value of sum to be an array.
Make sure that the last executed statement in your inject block is what you want the accumulated value to be.
def running_totals(myarray)
results = []
results << myarray.inject do |sum, n|
results << sum
sum + n
end
results
end
p running_totals([1,2,3,4,5])
=> [1, 3, 6, 10, 15]
Note that I moved the result of the inject into results array as well, so that the final value is also included, otherwise you'd only have the four values and would be missing the final (15) value.
The return value of the inject block is passed as the first argument the next time the block is called, so those have to match. In your code, you're passing an array as an intital value, and then returning an array; so far, so good. But inside the code block you treat that array parameter (sum) as a number, which won't work. Try this:
def running_totals(myarray)
myarray.inject([]) do |results,n|
results << n + (results.last || 0)
end
end
The [] passed as an argument to inject becomes the first value of results; the first array element (1 in your example) becomes the first value of n. Since results is empty, results.last is nil and the result of (results.last || 0) is 0, which we add to n to get 1, which we push onto results and then return that newly-modified array value from the block.
The second time into the block, results is the array we just returned from the first pass, [1], and n is 2. This time results.last is 1 instead of nil, so we add 1 to 2 to get 3 and push that onto the array, returning [1,3].
The third time into the block, results is [1,3], and n is 3, so it returns [1,3,6]. And so on.
According to ri, you have to return result of the computation from inject's block.
From: enum.c (C Method):
Owner: Enumerable
Visibility: public
Signature: inject(*arg1)
Number of lines: 31
Combines all elements of enum by applying a binary
operation, specified by a block or a symbol that names a
method or operator.
If you specify a block, then for each element in enum
the block is passed an accumulator value (memo) and the element.
If you specify a symbol instead, then each element in the collection
will be passed to the named method of memo.
In either case, the result becomes the new value for memo.
At the end of the iteration, the final value of memo is the
return value for the method.
If you do not explicitly specify an initial value for memo,
then uses the first element of collection is used as the initial value
of memo.
Examples:
# Sum some numbers
(5..10).reduce(:+) #=> 45
# Same using a block and inject
(5..10).inject {|sum, n| sum + n } #=> 45
# Multiply some numbers
(5..10).reduce(1, :*) #=> 151200
# Same using a block
(5..10).inject(1) {|product, n| product * n } #=> 151200
# find the longest word
longest = %w{ cat sheep bear }.inject do |memo,word|
memo.length > word.length ? memo : word
end
longest
So your sample would work if you return computation result for each iteration, something like this:
def running_totals(myarray)
results = []
myarray.inject do |sum,n|
results << sum + n
results.last # return computation result back to Array's inject
end
results
end
Hope it helps.

Passing "yielder" objects to Ruby's Enumerator

I was going through the documentation for the Enumerator class, and found a Fibonacci implementation that confuses me. Here's the code:
fib = Enumerator.new do |y|
a = b = 1
loop do
y << a
a, b = b, a + b
end
end
p fib.take(10) #=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
What is going on here? The two lines in the loop block especially confuse me.
Link: http://ruby-doc.org/core-2.1.5/Enumerator.html#method-c-new
As the documentation you linked to mentions, << is an alias for the yield method on the yielder object so the first line in the loop is equivalent to:
y.yield a
This is the mechanism by which the Enumerator returns its next value when the next method is called.
a, b = b, a + b is a parallel assignment, assigning the new values to a and b, but the way this works is that all the values of the expressions on the right hand side are calculated first and then assigned to the variables listed on the left hand side so a becomes the previous value of b and b becomes the value of the previous a + b, as required to generate the Fibonacci sequence.
What happens when the Enumerator is asked for its next value is that the block executes until it reaches the yield then the execution of the block stops and that value is returned as the next value. Then when the next value after that is requested the block continues from where it left off (so in this example it will be calculating the new a and b) and then carries on until it hits the yield to return the next value.

Please walk me through this code from ruby monk

def random_select(array, n)
result = []
n.times do
# I do not fully understand how this line below works or why. Thank you
result.push array[rand(array.length)]
end
result
end
You are probably confused by this part:
n.times do
result.push(array[rand(array.length)])
end
n.times says it should loop n times.
result.push says to basically "push" or "put" something in the array. For example:
a = []
a.push(1)
p a #=> [1]
In array[rand(array.length)] , rand(array.length) will produce a random number as an index for the array. Why? rand(n) produces a number from 0 to n-1. rand(5) will produce either 0,1,2,3 or 4, for example.
Arrays use 0-based indexing, so if you have an array, say a = ['x', 'y', 'z'], to access 'x' you do a[0], to access y you do a[1] and so on. If you want to access a random element from a, you do a[rand(array.length)], because a.length in this case is 3, and rand(3) will produce a number that is either 0, 1 or 2. 0 is the smallest index and 2 is the largest index of our example array.
So suppose we call this method:
random_select([6,3,1,4], 2)
Try to see this code from the inside out. When the code reaches this part:
result.push(array[rand(array.length)])
it will first execute array.length which will produce 4. It will then execute rand(array.length) or rand(4) which will get a number between 0 and 3. Then, it will execute array[rand(array.length)] or array(some_random_number_between_0_and_3) which will get you a random element from the array. Finally, result.push(all_of_that_code_inside_that_got_us_a_random_array_element) will put the random element from the array in the method (in our example, it will be either 6, 3, 1 or 4) in the results array. Then it will repeat this same process once again (remember, we told it to go 2 times through the iteration).
The code can be rewritten to be much simpler, using the block-form Array constructor:
def random_select(array, n)
Array.new(n) {array.sample}
end
This creates a new array of size n and fills it with random samples from the array.
Note that the above solution, like your sample code, selects from the entire array each time which allows duplicate selections. If you don't want any duplicate selections, it's even simpler, since it is the default behavior of Array#sample:
def random_select(array, n)
array.sample(n)
end

Simple ruby loop to get sum of cubes

I was given this problem to solve with Ruby:
Compute the sum of cubes for a given range a through b. Write a method called sum_of_cubes to accomplish this task.
I wrote this:
def sum_of_cubes(a, b)
sum = 0
for x in a..b
c = x ** 3
end
sum += c
end
I got the value of the cube of b. What is wrong with this code? How can I solve this problem with a simple loop?
Thanks!
I would use Enumerable#reduce
def sum_of_cubes min, max
(min..max).reduce(0) { |a, b| a + b ** 3 }
end
A little explanation of what's happening here
We start with range (min..max) which is an Enumerable
irb> (1..3).is_a? Enumerable
=> true
Using the reduce instance method we get from Enumerable, we can use a block of code that gets called for each item in our (enumerable) range, and ultimately returns a single value.
The function name makes sense if you think "take my group of items and reduce them to a single value."
Here's our block
{ |a, b| a + b ** 3 }
We called reduce with 0 which is the initial value given to the block's a param
The return value of the block is passed to the block's a param on subsequent calls
Each item in the range will be passed to the block's b param
Let's step through and see how it works
(1..3).reduce(0) { |a, b| a + b ** 3 }
the first block call gets a=0 (initial value) and b=1 (first item in our range)
the return value of our block is 0 + 1 ** 3 or 1
the second block call gets a=1 (return value from the last call) and b=2 (the second item in our range)
the return value of our block is 1 + 2 ** 3 or 9
the third block call gets a=9 (return value from the last call) and b=3 (the third and last item in our range)
the return value of our block is 9 + 3 ** 3 or 36
the final return value of reduce is the last-called block's return value
in this case 36
You need to have sum += c inside the loop. And then return sum when done.
Here’s another way to calculate this. It doesn’t address your problems with your loop but I think it’s worth mentioning.
The sum of cubes of integers 13 + 23 + 33 + ... + n3 is given by the formula (n(n + 1)/2)2, so the sum of cubes of a given range min..max is therefore given by:
(max(max + 1)/2)2 - ((min-1)((min-1) + 1)/2)2
In code this could look like:
def sum_of_cubes_fixed min, max
lower = min - 1
(((max * (max + 1))/2) ** 2) - (((lower * (lower + 1))/2) ** 2)
end
This code avoids the loop, and so is O(1) rather than O(n) (almost – I’m hand waving a bit here, the time complexity of the multiplications and exponentiations will depend on the size of the numbers). For small sized ranges you won’t notice this, but for larger sizes the difference between this and the loop version becomes increasingly obvious. I haven’t done any strict benchmarks, but a quick test on my machine with the range 1 to 10,000,000 takes several seconds with the reduce method but is almost instantaneous with this method.
Normally I would just use reduce for something like this, but the structure of the task suggested that there might be a better way. With the help of Google I found the formula and came up with a more efficient solution (at least for large ranges).
(a..b).map{|i| i**3 }.inject(&:+) map and inject does the job, elegantly.
EDIT: Although it walks through the list twice ;)

Ruby Exercise: Count the numbers in an array between a given range

So working through the above exercise and found this solution on GitHub.
def count_between arr, lower, upper
return 0 if arr.length == 0 || lower > upper
return arr.length if lower == upper
range = (lower..upper).to_a
arr.select { |value| range.include?(value) }.length
end
I understand what the first three lines mean and why they return the values they do. What I'd like to understand are the following lines of code.
Line 4 (below) is defining "range" as a variable and uses the lower...upper as the range variables (just discovered you don't need to put an integer value in a range. What does '.to_a' mean, can't seem to find it in the ruby docs, and what does it do?
range = (lower..upper).to_a
Line 5 (below) is using an Array#select method and its saying select this value if the value is included in this range and then give me the Array#length of all selected values, but I don't quite understand A. what |value| is doing and what it means. B. range.include?(value) means is this value included in this range I am assuming.
arr.select { |value| range.include?(value) }.length
Actually, I'd simplify to this:
def count_between arr, lower, upper
return 0 if lower > upper
arr.count{|v| (lower..upper).include?(v)}
end
to_a is documented here; it returns an Array containing each element in the Range. However, there's no reason to call to_a on the Range before calling include?.
There's also no reason to special-case the empty array.
Returning the length of the array when lower equals upper makes no sense.
value is the name given to the value the block is called with. I think a simple v is better for such a trivial case.
select calls the block for each value in arr and returns a new Array containing the elements for which the block returns true, so the length of that new Array is the number of matching values. However, count exists, and makes more sense to use, since the count is all we care about.
Update: As #steenslag points out in the comments, Comparable#between? can be used instead of creating a Range on which to call include?, and this eliminates the need to ensure that lower is less than or equal to upper:
def count_between arr, lower, upper
arr.count{|v| v.between?(lower, upper)}
end
to_a means convert to array
irb(main):001:0> (1..5).to_a
=> [1, 2, 3, 4, 5]
select method passes each element to the block and Returns a new array containing all elements of ary for which the given block returns a true value.. In your case it simply checks if the value is contained in the range array. range is an array not a range.
## if arr is [1,5] for eg:
irb(main):005:0> [1,5].select {|value| range.include?(value)}
=> [1, 5]
irb(main):006:0> [1,5].select {|value| range.include?(value)}.length
=> 2
so the elements of arr are contained in the |value| variable inside the block.
It's a block.
As the documentation says: select "Returns a new array containing all elements of ary for which the given block returns a true value."
So for each object in arr it is passed to the block in which you provide whatever code you want to that returns true or false, and the select statement uses this result to add the value to the the array that it returns. And after that, length is called on the array.
So you have an array, you filter the array to contain only the numbers that are in the range, and then you take the length - effectively counting the number of elements.

Resources