Having trouble with insertion sort algorithm in ruby - ruby

So I'm trying to learn how to sort an array without using the .sort method, this is what I have so far, but the middle number is dropping out.
def my_sort(num)
for j in 1...num.length
key = num[j]
i = j - 1
while i > 0 and num[i] = key
num[i+1] = num[i]
i = i - 1
end
num[i+1] = key
end
end
then I run the method
my_sort([3,1,2])
I get
=> 1...3
but I want
=> 1,2,3
What am I doing wrong?

You're not returning the modified array, but instead the object fed into your for iterator.
What you're missing is simply leaving the array as the last thing in the method:
def my_sort(num)
# ...
num
end
As a note it's usually bad form to wreck the arguments you're given. A more polite function would return a copy.

Actual implementation of insertion sort must look like:
def my_sort(arr)
for j in 1...arr.length
key = arr[j]
i = j-1
while i>=0 and arr[i] > key # You used arr[i] = key here instead
arr[i+1] = arr[i]
i = i-1
end
arr[i+1] = key
end
arr #return the array
end

Related

In ruby, Is it better to receive *array or to duplicate and array inside a method?

I was playing around with some implementations of Quicksort in Ruby. After implementing some of the inlace algorithms, I felt that using Ruby's partition method, even though it would not provide an in-place solution, it would provide a very nice readable solution.
My first solution was this, which other than always using the last element of the array as the pivot, seemed pretty nice.
def quick_sort3(ary)
return ary if ary.size <= 1
left,right = ary.partition { |v| v < ary.last }
pivot_value = right.pop
quick_sort3(left) + [pivot_value] + quick_sort3(right)
end
After some searching I found this answer which had a very similar solution with a better choice of the initial pivot, reproduced here using the same variable names and block passed to partition.
def quick_sort6(*ary)
return ary if ary.empty?
pivot_value = ary.delete_at(rand(ary.size))
left,right = ary.partition { |v| v < pivot_value }
return *quick_sort6(*left), pivot_value, *quick_sort6(*right)
end
I felt I could improve my solution by using the same method to select a random pivot.
def quick_sort4(ary)
return ary if ary.size <= 1
pivot_value = ary.delete_at(rand(ary.size))
left,right = ary.partition { |v| v < pivot_value }
quick_sort4(left) + [pivot_value] + quick_sort4(right)
end
The down side to this version quick_sort4 vs the linked answer quick_sort6, is that quick_sort4 changes the input array, while quick_sort6 does not. I am assuming this is why Jorg chose to receive the splat array vs array?
My fix for this was to simply duplicate the passed in array and then perform the delete_at on the copied array rather than the original array.
def quick_sort5(ary_in)
return ary_in if ary_in.size <= 1
ary = ary_in.dup
pivot_value = ary.delete_at(rand(ary.size))
left,right = ary.partition { |v| v < pivot_value }
quick_sort5(left) + [pivot_value] + quick_sort5(right)
end
My question is there any significant differences between quick_sort6 which uses the splats and quick_sort5 which uses dup? I am assuming the use of the splats was to avoid changing the input array, but is there something else I am missing?
In terms of performance, quick_sort6 is your best bet. Using some random data:
require 'benchmark'
def quick_sort3(ary)
return ary if ary.size <= 1
left,right = ary.partition { |v| v < ary.last }
pivot_value = right.pop
quick_sort3(left) + [pivot_value] + quick_sort3(right)
end
def quick_sort6(*ary)
return ary if ary.empty?
pivot_value = ary.delete_at(rand(ary.size))
left,right = ary.partition { |v| v < pivot_value }
return *quick_sort6(*left), pivot_value, *quick_sort6(*right)
end
def quick_sort4(ary)
return ary if ary.size <= 1
pivot_value = ary.delete_at(rand(ary.size))
left,right = ary.partition { |v| v < pivot_value }
quick_sort4(left) + [pivot_value] + quick_sort4(right)
end
def quick_sort5(ary_in)
return ary_in if ary_in.size <= 1
ary = ary_in.dup
pivot_value = ary.delete_at(rand(ary.size))
left,right = ary.partition { |v| v < pivot_value }
quick_sort5(left) + [pivot_value] + quick_sort5(right)
end
random_arrays = Array.new(5000) do
Array.new(500) { rand(1...500) }.uniq
end
Benchmark.bm do |benchmark|
benchmark.report("quick_sort3") do
random_arrays.each do |ra|
quick_sort3(ra.dup)
end
end
benchmark.report("quick_sort6") do
random_arrays.each do |ra|
quick_sort6(ra.dup)
end
end
benchmark.report("quick_sort4") do
random_arrays.each do |ra|
quick_sort4(ra.dup)
end
end
benchmark.report("quick_sort5") do
random_arrays.each do |ra|
quick_sort5(ra.dup)
end
end
end
Gives as result
user system total real
quick_sort3 1.389173 0.019380 1.408553 ( 1.411771)
quick_sort6 0.004399 0.000022 0.004421 ( 0.004487)
quick_sort4 1.208003 0.002573 1.210576 ( 1.214131)
quick_sort5 1.458327 0.000867 1.459194 ( 1.459882)
The problem with splat style in this case is that it would create an awkward API.
Most times the consumer code would have an array of things that need to be sorted:
stuff = [1, 2, 3]
sort(stuff)
The splat style makes the consumers do this instead:
stuff = [1, 2, 3]
sort(*stuff)
The two calls might end up doing the same thing, but as a user I am sorting an array, therefore I expect to pass the array to the method, not pass each array element individually to the method.
Another label for this phenomenon in abstraction leakage - you are allowing the implementation of the sort method define its interface. Usually in Ruby this is frowned upon.

NoMethodError with .chr.to_i

I'm trying to create a recursive method sum_of_digits(i) that takes the sum of integers, i.e. '456' = 4+5+6 = 15
However, I receive a NoMethodError for chr.to_i in the following code:
def sum_of_digits(i)
input = i.to_s
if i == 0
return 0
elsif input.length == 1
return i
else
for n in 1..input.length
sum += input[i].chr.to_i % 10^(n-1)
end
end
return sum
end
Thank you!
String indexes are zero-based in ruby. The problem is here:
for n in 1..input.length
it should be written as
for n in 0..input.length-1
BTW, call to chr is superfluous as well, since you already have a string representation of a digit there. As well, sum must be declared in advance and set to zero.
Also, the whole code is not ruby idiomatic: one should avoid using unnecessary returns and for-loop. The modified version (just in case) would be:
def sum_of_digits(i)
input = i.to_s
case
when i == 0 then 0 # return zero
when input.length == 1 then i # return i
else
sum = 0
input.length.times do |index|
sum += input[index].to_i % 10^index
end
sum
end
end
or, even better, instead of
sum = 0
input.length.times do |index|
sum += input[index].to_i % 10^index
end
sum
one might use inject:
input.length.times.inject(0) do |sum, index|
sum += input[index].to_i % 10^index
end

How to use a block as method input

I am trying to create a method that uses bubble-sort to sort a small array into numerical order. This method accepts two arguments, an array and a method:
def bubble_sort_by(arr)
while(true)
counter = 0
for i in 0...(arr.size-1)
if yield (arr[i], arr[i+1]) > 0
saved = arr[i]
arr[i] = arr[i+1]
arr[i+1] = saved
counter += 1
end
end
if (counter == 0)
break
end
end
print arr
end
bubble_sort_by([4,3,78,2,0,2]) do |left,right|
return left - right
end
The sorted array should be
[0,2,2,3,4,78]
Currently I am using Ruby version 2.3.0p0.
I keep getting a syntax error when I try to run this code.
This is the fixed version:
def bubble_sort_by(arr)
while(true)
counter = 0
for i in 0...(arr.size-1)
if yield(arr[i], arr[i+1]) > 0
saved = arr[i]
arr[i] = arr[i+1]
arr[i+1] = saved
counter += 1
end
end
if (counter == 0)
break
end
end
print arr
end
bubble_sort_by([4,3,78,2,0,2]) do |left,right|
left - right
end
As you can see, it had two issues:
The space a yield - basically the space was extra because it wasn't passing the numbers in the parenthesis as arguments;
The return in the block - you do not want the block to explicitly return the value, you need it to be evaluated in bubble_sort_by and then its result used in the context there.

Both tests are returning false even though in my mind the code executes perfectly

# Write a method that takes in a string. Your method should return the
# most common letter in the array, and a count of how many times it
# appears.
#
# Difficulty: medium.
def most_common_letter(string)
letter = 0
letter_count = 0
idx1 = 0
mostfreq_letter = 0
largest_letter_count = 0
while idx1 < string.length
letter = string[idx1]
idx2 = 0
while idx2 < string.length
if letter == string[idx2]
letter_count += 1
end
idx2 += 1
end
if letter_count > largest_letter_count
largest_letter_count = letter_count
mostfreq_letter = letter
end
idx1 += 1
end
return [mostfreq_letter, largest_letter_count]
end
# These are tests to check that your code is working. After writing
# your solution, they should all print true.
puts(
'most_common_letter("abca") == ["a", 2]: ' +
(most_common_letter('abca') == ['a', 2]).to_s
)
puts(
'most_common_letter("abbab") == ["b", 3]: ' +
(most_common_letter('abbab') == ['b', 3]).to_s
)
So in my mind the program should set a letter and then once that is set cycle through the string looking for letters that are the same, and then once there is one it adds to letter count and then it judges if its the largest letter count and if it is those values are stored to the eventual return value that should be correct once the while loop ends. However I keep getting false false. Where am I going wrong?
Your code does not return [false, false] to me; but it does return incorrect results. The hint by samgak should lead you to the bug.
However, for a bit shorter and more Rubyish alternative:
def most_common_letter(string)
Hash.new(0).tap { |h|
string.each_char { |c| h[c] += 1 }
}.max_by { |k, v| v }
end
Create a new Hash that has a default value of 0 for each entry; iterate over characters and count the frequency for each of them in the hash; then find which hash entry is the largest. When a hash is iterated, it produces pairs, just like what you want for your function output, so that's nice, too.

check whether any number in one array is less than some number in the other array

This seems like a pretty common question. Sadly I could not find it on SO. If this is a duplicate question; I apologize for that.
Say I have two integer arrays A and B:
A = [17, 3, 9, 11, 11, 15, 2]
B = [1, 13]
I need to return a true or a false if any element of array A is less than any element of array B.
The trivial way to do this was use 2 each loops (O(n^2) complexity)
def is_greater?(a,b)
retVal = false
b.each { |element|
a.each { |value|
if (value < element)
retVal = true
break
end
}
}
return retVal
end
is_greater?(A,B) => true
I also sorted out the elements in both the arrays and then used a single while loop to determine whether the element in A is less than that in B.
A.sort!
B.sort!
def is_greater?(a,b)
retVal = false
i = 0
j = 0
while (i < a.length && j < b.length)
if (a[i] < b[j])
retVal = true
break
elsif (a[i] == b[j])
i = i + 1
j = j + 1
else
j = j + 1
end
end
return retVal
end
is_greater?(A,B) => true
I was wondering whether there is an efficient, precise way to do it in terms of lines of code. I was trying to figure out how to use the any? block, but it did not make any sense to me.
Yes, you can use Enumerable methods #any? and #min
For each item in a, return true if it is less than max:
max = b.max
a.any?{|x| x < max}
It should be enough to just check the minimum of the first array against the maximum of the second.
a.min < b.max
The only way this conditional returns false is if every element is b is less than every element in a.
The complexity is O(m+n) which is the single iteration through both a and b.

Resources