Ruby with Rspec - How is this test passing? - ruby

This test passes validations, but how?
#Rspec
it "handles a case with an answer > 1 distance to the left" do
nearest_larger([8,2,4,3], 2).should == 0
end
#Function
def nearest_larger(arr, idx)
diff = 1
loop do
left = idx - diff
right = idx + diff
if (left >= 0) && (arr[left] > arr[idx])
return left
elsif (right < arr.length) && (arr[right] > arr[idx])
return right
elsif (left > 0) && (right >= arr.length)
return nil
end
diff += 1
end
end
I'm working through some ruby examples and I can't understand how this test passes validation. Where is 0 being returned from?

When idx is 2 and diff is 2 (that is, on your second loop), left is 2 - 2 = 0, and arr[0] == 8, which is greater than arr[2] == 4. The function then returns left, which is zero.

Related

Comparison of Integer with nil failed (ArgumentError) while solving Insertion Sort with Ruby

Below is the updated code:
def insertion_sort(list)
num = list.length
for i in (0..(num-2))
if list[i] > list[i+1] && i == 0
list[i], list[i+1] = list[i+1], list[i]
i+=1
elsif list[i] == list[i+1]
i+=1
elsif list[i] > list[i+1] && i > 0
len = (list[0..(i+1)].length)
list2 = list[0..(i+1)]
list = list - list2
count = 0
while count <= len+1
if list2[len-1] < list2[len-2]
list2[len-2],list2[len-1]= list2[len-1],list2[len-2]
elsif list2[len-1] == list2[len-2]
count+=1
len-=len
else
count+=1
len-=1
end
end
list = list2 + list
end
end
list
end
p insertion_sort([2,1,4,8,7,3,100,99,8])
p insertion_sort([2,1,4,8,8,7,3,100,99])
p insertion_sort([3790,780,780,1,55])
Summary:
code works if two identical integers are right next to each other :[2,1,4,8,8,7,3,100,99] and the array size > 5.
if the two identical integer are at random positions: [2,1,4,8,7,3,100,99,8]. Below error would occur
aaa.rb:4:in `>': comparison of Integer with nil failed (ArgumentError)
with Line 4 code being: if list[i] > list[i+1] && i == 0
To solve 1. I changed the while loop to "while count <= len+1",
so when array size is smaller than 5 the code would work. But not when identical integers are at random positions.
Does anyone know how to solve this?
Thanks in advance!
Thanks for the clarification in the comments. I see the problem now.
In the swap algorithm here
elsif list[i] > list[i+1] && i > 0
len = (list[0..(i+1)].length)
list2 = list[0..(i+1)]
list = list - list2
count = 0
while count <= len+1
...
you are trying to split the array in two. You get list2 which is the first half of the array and then try and get the second half by subtracting list2 from list.
The problem with using subtract here is that if you have duplicates it will remove them and make your lists too short.
In the example [3790,1,780,55,23,50,1111,60,50] you should have a 50 in the first array and a 50 in the second half.
But using subtract removes one of those 50.
When you add the two temporary lists back together you are now one element short (the missing 50) and you get an out of bounds error when you get to the end of the array and try and access this 9th element which no longer exists.
Instead of using subtract here simply use the same method you used to make list2.
list2 = list[0..(i+1)] # All elements in list from position 0 to i+1
list = list[(i+2)..-1] # All elements in list from position i+2 to end of list
Now list and list2 are just the original list split, and when you add them back together are the end they should be the same length
def insertion_sort(list)
num = list.length
for i in (0..(num-2))
if list[i] > list[i+1] && i == 0
list[i], list[i+1] = list[i+1], list[i]
i+=1
elsif list[i] == list[i+1]
i+=1
elsif list[i] > list[i+1] && i > 0
len = (list[0..(i+1)].length)
list2 = list[0..(i+1)]
list = list[(i+2)..-1]
count = 0
while count <= len+1
if list2[len-1] < list2[len-2]
list2[len-2],list2[len-1]= list2[len-1],list2[len-2]
elsif list2[len-1] == list2[len-2]
count+=1
len-=len
else
count+=1
len-=1
end
end
list = list2 + list
end
end
list
end
p insertion_sort([2,1,4,8,7,3,100,99,8])
p insertion_sort([2,1,4,8,8,7,3,100,99])
p insertion_sort([3790,780,780,1,55])

Coding Challenge 2

Find the element in an array which has consecutive plus or minus 1. I can achieve in O(n) time by doing a linear search. Is there any efficient way to achieve in less than O(n) time.
What I mean by plus or minus 1 is 2 consecutive number in an array will have either -1 or 1 as the difference
For example sample array inputs
Consecutive element have +1 of difference
arr = [1,2,3,4,5]
Consecutive elements have -1 of difference
arr = [ -1,-2, -3, -4 , -5, ]
Elements have either +1 or -1 of difference
arr = [-5,-4,-3,-4, -3,-2,-1,0,1,2,3,4,3,2,1]
Note: Array is unsorted
Example
arr = [-5,-4,-3,-4,-3,-2,-1,0,1,2,3,2,1,2,3,4,5,6,5,4,3,2,1,0,-1,-2,-3,-4,-3,-2,-1]
while i <= arr.length do
if num == a[i]
puts "found"
break
end
i += 1
end
2nd approach which in my mind is instead of starting from 0 index I have done some custom logic
num = gets.to_i
i = 0
if (num < 0 && arr[0] < 0) || (num > 0 && arr[0] < 0)
i = (num + arr[0].abs).abs
elsif num < 0 && arr[0] > 0
i = num.abs + arr[0]
elsif num > 0 && arr[0] > 0
i = (num > arr[0] ? (num - arr[0]) : (arr[0] - num))
end
while i <= arr.length do
if num == a[i]
puts "found"
break
end
i += 1
end
As Nico Schertler said, O(n) is the best you can do. This is a find operation, and find operations on unsorted data are always O(n), because in the worst case they must check every element. This would be different if your data were sorted. See binary search.
Note also that your example code does not do what you're asking - it simply finds some number num in the list. Your example code is equivalent to:
arr.find { |i| i == num }
What you appear to be asking for is
arr.find { |i| i == num - 1 or i == num + 1 }

Nuances of where to define a variable in ruby code

I've just started learning ruby, and the position of where variables are defined somewhat elude me. For example, why does this code work:
def two_sum(nums)
result = nil
i = 0
while i < nums.length
k = (nums.length - 1)
if nums[i] + nums[k] == 0
result = [i,k]
end
i += 1
k -= 1
end
return result
end
And why does this code not work:
def two_sum(nums)
result = nil
i = 0
k = (nums.length - 1)
while i < nums.length
if nums[i] + nums[k] == 0
result = [i,k]
end
i += 1
k -= 1
end
return result
end
Thank you in advance!
I think you code might just have a bug
while i < nums.length
k = (nums.length - 1)
...
k -= 1 # this statement has no effect!
end
Above, the value if k is always (nums.length - 1) because you reassign it at the begin of each iteration. The other statement has no effect.
k = (nums.length - 1)
while i < nums.length
...
k -= 1
end
Above, the value of k starts at (nums.length - 1) in the first iteration and is then reduced by 1 for each iteration.
Pro tipp —
It is very unusual in Ruby to use a for/while/until loop. If you want to loop over all elements use each or each_with_index instead
array.each { |each| ... }
array.each_with_index { |each, n| ... }

using the case statement to find an index and return it

After playing about with this all morning(i am still new to this), I have decided to ask the experts.
This is the quest:
You are going to be given a word.
Your job is to return the middle character of the word.
If the word's length is odd, return the middle character.
If the word's length is even, return the middle 2 characters.
This is what I have so far:
def median(string)
array = string.split(//)
case array
when array.length == 1
return array[0]
when array.length == 2
return array[0] + array[1]
when array.length.odd? && array.length >= 3
return array[(array.length - 1) / 2]
when array.length.even? && array.length >= 4
return array[((array.length / 2 ) - 1)] + array[(array.length / 2)]
else nil
end
end
puts median("testing")
what is wrong with my code. It runs but delivers nothing. Any help hugely appreciated.
You need to remove array from the line case array. I only did this change to your code and it works just fine:
def median(string)
array = string.split(//)
case
when array.length == 1
return array[0]
when array.length == 2
return array[0] + array[1]
when array.length.odd? && array.length >= 3
return array[(array.length - 1) / 2]
when array.length.even? && array.length >= 4
return array[((array.length / 2 ) - 1)] + array[(array.length / 2)]
else nil
end
end
In the initial version of the code, having case array meant that on each branch Ruby would compare the "when" value (which is always a boolean value) to array, which is never boolean, therefore never equal to any of the branches. this is why the else nil branch was always the exit point of the case block.
Changing case array to case will tell Ruby that it does not need to performa comparison, but to evaluate each branch condition and execute the first one that is true.
This being said, your code can be simplified in a few ways:
there's no need to convert the string to an array; string indexing would work just fine in this case
the conditions && array.length >= 3 and && array.length >= 4 are superfluous; a lower value would hit the array.length == 1 or array.length == 2 branches
the array.length == 1 or array.length == 2 branches are really not that special; you can just treat them in the odd/even length cases.
Applying these considerations I ended up with this code, which also works OK:
def word_median(word)
half = word.length / 2
case
when word == '' then ''
when word.length.odd? then word[half]
else word[half - 1..half]
end
end

Ruby longest palindrome - why does a slight modification in a while loop break my code?

I'm trying to solve this Ruby problem and I can't figure out why having a minor while loop difference renders one test false: longest_palindrome("abba") outputs "bb" instead of "abba", which is false. I can only solve it with for and while loops, so please no advanced methods. It's easier to highlight the difference in the code block (first one is the working solution, second is mine. Also assume the palindrome? method is already defined):
def longest_palindrome(string)
best_palindrome = nil
idx1 = 0
***while idx1 < string.length
length = 1
while (idx1 + length) <= string.length
substring = string.slice(idx1, length)***
if palindrome?(substring) && (best_palindrome == nil || substring.length > best_palindrome.length)
best_palindrome = substring
end
length += 1
end
idx1 += 1
end
return best_palindrome
end
def longest_palindrome(string)
longest = nil
i = 0
***while i < string.length
i2 = 1
while i2 < string.length***
if palindrome?(string.slice(i, i2)) == true && (longest == nil || string.slice(i, i2).length > longest.length)
longest = string.slice(i, i2)
end
i2 += 1
end
i += 1
end
return longest
end
This part of your code...
while i2 < string.length
... means you're never checking the maximum possible length.
"abba".slice(0,4) is the entire string, but you only ever go up to "abba".slice(0,3) which is "abb".
So you never test the entire string.
Change the line to...
while i2 <= string.length
...and it should be ok.

Resources