I'm working through some online practice problems to learn Ruby, and while I was able to solve this one, I'm struggling to understand something about the placement of variable declaration.
Why does the following code work when I declare/define "pair" (sum of array indices) inside the idx2 while-loop, but not when I do so directly after declaring the indices themselves?
def two_sum(nums)
idx=0
idx2=0
while idx<nums.length-1
idx2=idx+1
while idx2<nums.length
pair=nums[idx]+nums[idx2]
if pair==0
return [idx, idx2]
else
idx2+=1
end
end
idx+=1
end
end
The version above works, but the structure below does not.
def two_sum(nums)
idx=0
idx2=0
pair=nums[idx]+nums[idx2]
while idx<nums.length-1
idx2=idx+1
while idx2<nums.length
if pair==0
return [idx, idx2]
else
idx2+=1
end
end
idx+=1
end
end
If anyone can provide an explanation or some entry-level resources about this, I'd greatly appreciate it. Thanks.
Edit:
Sorry for indentation and specificity issues regarding desired output. I'm entirely new to this and sometimes details slip by, I appreciate the feedback. Thanks for the step-by-step logical run through, that's exactly what I was looking for.
Let's look at the working code and the broken code one at a time.
Let's say nums = [1, 2, -1, 3]
Supposably Working Code
1 def two_sum(nums)
2 idx = 0
3 idx2 = 0
4
5 while idx < nums.length-1
6 idx2 = idx+1
7 while idx2 < nums.length
8 pair = nums[idx] + nums[idx2]
9 if pair == 0
10 return [idx, idx2]
11 else
12 idx2 += 1
13 end
14 end
15 idx += 1
16 end
17 end
When we start off
idx = 0, idx2 = 0
Now we are in the first while loop (line 6)
idx = 0, idx2 = 1
2nd while loop (line 8)
idx = 0, idx2 = 1, pair = nums[0] + nums[1] = 3
pair != 0, so we are now in the else statement. (line 12)
idx = 0, idx2 = 2, pair is still 3
We now loop back to the 2nd while loop (line 8 again)
idx = 0, idx2 = 2, pair = nums[0] + nums[2] = 0
pair == 0, so we return [0, 2] (line 10)
This is how the code is suppose to run.
Broken Code
1 def two_sum(nums)
2 idx = 0
3 idx2 = 0
4 pair = nums[idx] + nums[idx2]
5 while idx < nums.length-1
6 idx2 = idx+1
7 while idx2 < nums.length
8
9 if pair == 0
10 return [idx, idx2]
11 else
12 idx2 += 1
13 end
14 end
15 idx += 1
16 end
17 end
We will run through the same logic again.
Before we hit the first while loop (line 2 - 4)
idx = 0, idx2 = 0, pair = nums[0] + nums[0] = 2
Now we are in the first while loop, (line 6)
idx = 0, idx2 = 1, pair = 2
And now in second while loop, pair != 0, so we hit the else statement, (line 12)
idx = 0, idx2 = 2, pair = 2
Now we loop back to the start of the second while loop. pair != 2, so we hit the else statement.
idx = 0, idx2 = 3, pair = 2
pair != 0, so we hit the else statement.
idx = 0, idx2 = 4, pair = 2
So now, idx2 < nums.length is false, so we exit the second while loop, and nowidx = 1`, and we continue to cycle.
As you can see, because you declared pair outside of the while loop, it never recalculates to different values, so you will never hit the if statement, because pair will never equal zero, unless of course, you get 0 on the initial sum.
while loops are meant for cases where you are not sure how many loops you need. An example is checking a user's entry, where you may need to loop once, or ten times, depending on when the user gets the entry correct. In this case, you know exactly how many times you need to run. So, an iterator is better for the job here.
Here's an example of how that can be written. Keep in mind there's probably better ways to do this, I just want to show an example.
def two_sum(nums)
nums.each_with_index do |num1, idx1|
nums.each_with_index do |num2, idx2|
next if idx1 == idx2
return [idx1, idx2] if num1 + num2 == 0
end
end
end
Related
I'm learning ruby and practicing with codewars, and I've come to a challenge that I feel I mainly understand (rudimentarily) but I'm unable to figure out how to continue looping over the method until I reach the result I'm looking for.
The challenge is asking to reduce a number, by multiplying its digits, until the multiplication results in a single digit. In the end it wants you to return the number of times you had to multiply the number until you arrived at a single digit. Example -> given -> 39; 3 * 9 = 27, 2 * 7 = 14, 1 * 4 = 4; answer -> 3
Here's my code :
def persistence(n)
if n < 10
return 0
end
arr = n.to_s.split("")
sum = 1
count = 0
arr.each do |num|
sum *= num.to_i
if num == arr[-1]
count += 1
end
end
if sum < 10
return count
else
persistence(sum)
end
end
Thanks for your help!
Your function is looking great with recursion but you are reseting the count variable to 0 each time the loop runs, I think if you use an auxiliar method it should run ok:
this is in base of your code with minor improvements:
def persistence(n)
return 0 if n < 10
count = 0
multiply_values(n, count)
end
def multiply_values(n, count)
arr = n.to_s.chars
sum = 1
arr.each do |num|
sum *= num.to_i
if num == arr[-1]
count += 1
end
end
if sum < 10
return count
else
multiply_values(sum, count)
end
end
a shorter solution could be to do:
def persistence(n)
return 0 if n < 10
multiply_values(n, 1)
end
def multiply_values(n, count)
sum = n.to_s.chars.map(&:to_i).reduce(&:*)
return count if sum < 10
multiply_values(sum, count + 1)
end
and without recursion:
def persistence(n)
return 0 if n < 10
count = 0
while n > 10
n = n.to_s.chars.map(&:to_i).reduce(&:*)
count += 1
end
count
end
Let's look at a nicer way to do this once:
num = 1234
product = num.to_s.split("").map(&:to_i).reduce(&:*)
Breaking it down:
num.to_s.split("")
As you know, this gets us ["1", "2", "3", "4"]. We can easily get back to [1, 2, 3, 4] by mapping the #to_i method to each string in that array.
num.to_s.split("").map(&:to_i)
We then need to multiply them together. #reduce is a handy method. We can pass it a block:
num.to_s.split("").map(&:to_i).reduce { |a, b| a * b }
Or take a shortcut:
num.to_s.split("").map(&:to_i).reduce(&:*)
As for looping, you could employ recursion, and create product_of_digits as a new method for Integer.
class Integer
def product_of_digits
if self < 10
self
else
self.to_s.split("").map(&:to_i).reduce(&:*).product_of_digits
end
end
end
We can now simply call this method on any integer.
1344.product_of_digits # => 6
FizzBuzz, a classic problem, returns all the numbers up to N with a slight twist. If a number is divisible by 3, it is replaced with "fizz". If it's divisible by 5, it's replaced with "buzz". If it's divisible by both, it's replaced with "fizzbuzz"
I keep getting this Error message:
comparison of Fixnum with nil failed
Can someone explain this error message to me please? Also why is the code not working?
def fizz_buzz(n)
arr = (1..n).to_a
i = 0
while arr[i] < arr[n]
if i % 3 == 0 && i % 5 == 0
arr[i] = 'fizzbuzz'
elsif i % 3 == 0
arr[i] = 'fizz'
elsif i % 5 == 0
arr[i] = 'buzz'
else
arr[i] = i
i += 1
end
end
return arr
end
fizz_buzz(12)
Your conditions are just a bit off, give this a try:
def fizz_buzz(n)
arr = (1..n).to_a
i = 0
while i < n
if arr[i] % 3 == 0 && arr[i] % 5 == 0
arr[i] = 'fizzbuzz'
elsif arr[i] % 3 == 0
arr[i] = 'fizz'
elsif arr[i] % 5 == 0
arr[i] = 'buzz'
end
i+=1
end
return arr
end
Trying to access arr[n] puts you outside the bounds of the array which returns nil in Ruby.
You can update the code the ruby way, by using blocks and guards, I don't remember last time I used a while loop in ruby :)
Also, Array.new accepts a block as an argument which you can exploit to build your Array in a single step:
def fizz_buzz(n)
Array.new(n) do |index|
x = index + 1
case
when x % 3 == 0 && x % 5 == 0 then "fizzbuzz"
when x % 3 == 0 then "fizz"
when x % 5 == 0 then "buzz"
else x
end
end
end
Notice I used 1 as a base index and not 0, you can just remove x = index + 1 and replace x with index to have it working in a zero index base
A solution with a block instead of the while loop, and guards
def fizz_buzz(n)
arr = (1..n).to_a
0.upto(n - 1) do |i|
arr[i] = "fizzbuzz" and next if i % 3 == 0 && i % 5 == 0
arr[i] = "fizz" and next if i % 3 == 0
arr[i] = "buzz" if i % 5 == 0
end
arr
end
#brad-melanson beat me to the straight-forward answer to your question, so I'll share an answer which uses some common Ruby idioms (passing a range to the Array constructor and map), which simplify things, prevent you from having to do any iteration bookkeeping and prevent the possibility of off-by-one errors, out-of-bounds errors, etc.
def fizz_buzz(n)
Array(1..12).map do |n|
if n % 3 == 0 && n % 5 == 0
'fizzbuzz'
elsif n % 3 == 0
'fizz'
elsif n % 5 == 0
'buzz'
else
n
end
end
end
result = fizz_buzz 12
# result => [1, 2, "fizz", 4, "buzz", "fizz", 7, 8, "fizz", "buzz", 11, "fizz"]
I wrote a function which works. There is one part in there and it can be written in an alternative manner, but the alternative way of writing it doesn't work, although it should represent the same thing as the original code I wrote. I'd like to know why the alternative way of writing it does not work. When I ran it with the alternative codes, I think it became an infinite loop because there were no outputs.
This is a snippet of the function (The code in question is commented as "#ALTERNATIVE", there are four lines in total):
if idx2 > idx
idx3 = idx2 - 1
while idx3 >= idx #ALTERNATIVE 1: idx3 > idx
if arr[idx3] > arr[idx]
return idx3
elsif idx3 == idx #ALTERNATIVE 1: idx3 == idx + 1 or idx3 - 1 == idx
return idx2
end
idx3 -= 1
end
elsif idx > idx2
idx4 = idx2 + 1
while idx4 <= idx #ALTERNATIVE 2: idx4 < idx
if arr[idx4] > arr[idx]
return idx4
elsif idx4 == idx #ALTERNATIVE 2: idx4 == idx - 1 or idx4 + 1 == idx
return idx2
end
idx4 += 1
end
end
Your alternate code doesn't work in the cases where idx and idx2 are within 1 of each other.
Imagine idx == 1 and idx2 == 2.
if idx2 > idx
idx3 = idx2 - 1 # idx3 is equal to 1 and therefore equal to `idx`
while idx3 > idx # this is false, the while loop is not run
And nothing is returned. The same type of situation would be true for the elsif clause if idx == 2 and idx2 == 1 where the while loop would never run.
I don't know why the alternative code doesn't work, perhaps you've put in an idx2 equal to idx and it exited.
However, I thought it might be prudent to show you how to write this code more idiomatically:
def furthest_number_higher_than_base(arr, base, start)
range = start < base ? start..base : (base..start).to_a.reverse
range.each { |i| return i if arr[i] > arr[base] }
start
end
The error is on line 12 and I'm not sure why I can't add the numbers. Any help is much appreciated.
Instructions: Write a method that takes an array of numbers. If a pair of numbers in the array sums to zero, return the positions of those two numbers. If no pair of numbers sums to zero, return nil.
def two_sum(nums)
idx1 = 0
idx2 = 1
while idx1 < nums.length
if nums[idx1] + nums[idx2] == 0
return [idx1, idx2]
end
idx2 += 1
if idx2 == nums.length
idx1 += 1
idx2 = idx1 + 1
end
end
return nil
end
puts("two_sum([1, 3, 5, -3]) == [1, 3]: #{two_sum([1, 3, 5, -3]) == [1, 3]}")
puts("two_sum([1, 3, 5]) == nil: #{two_sum([1, 3, 5]) == nil}")
idx2 could overflow capacity of your array:
Imagine. nums = [1,2,3], so nums.length is 3, idx1 = 1, idx2 = 2
idx2 += 1 # ok now idx2 is 3
if idx2 == nums.length # ok true, idx2 == 3
idx1 += 1 # mmm, cool idx1 now 2
idx2 = idx1 + 1 # idx2 is 3
end
So in next iteration you will call
nums[idx2]
# same as
nums[3]
# ERROR! there is only 3 numbers in nums
And try to understand this code
def two_sums(nums)
nums[0..-2].each.with_index do |n,i|
nums[i+1..-1].each.with_index do |m,j|
return [i, i+j+1] if m + n == 0
end
end
nil
end
Can anybody please explain this code? I don't understand specifically:
elsif idx2 > idx1
is_repeat = true
end
Why are we comparing indices to determine if a letter has been
repeated?
Also what does the 'next' term do inside of the if statement?
The full code is shown below:
# Write a method that takes in a string and returns the number of
# letters that appear more than once in the string. You may assume
# the string contains only lowercase letters. Count the number of
# letters that repeat, not the number of times they repeat in the
# string.
#
# Difficulty: hard.
def num_repeats(array)
repeats = 0
idx1 = 0
while idx1 < array.length
is_repeat = false
idx2 = 0
while idx2 < array.length
if array[idx1] != array[idx2]
idx2 += 1
next
elsif idx2 < idx1
# will have previously counted this repeat
break
elsif idx2 > idx1
is_repeat = true
end
idx2 += 1
end
if is_repeat
repeats += 1
end
idx1 += 1
end
return repeats
end
If both conditions before (if idx2 < array.length and elsif idx2 < idx1) are false but idx2 > idx1 is true, than set the local variable is_repeat to true...