Recursive merge sort in Ruby - ruby

I am trying to write a ruby method which performs a merge sort recursively. I have the method working, but It's one of those times where I accidentally got it working so I have no idea WHY it works, and would love to understand how the code I have written works. In psuedocode, the steps I followed look like this.
Split the original array of length n until I have n arrays of length 1
Merge and sort 2 arrays of length m at time to return an array of length m*2
Repeat the step above until I have a single now sorted array of length n
Basically what this looks like to me is a large tree branching out into n branches, with each branch containing an array of length 1. Then I need to take these n branches and somehow merge them back into a single branch within the method.
def merge_sort(arr)
return arr if arr.length == 1
merge(merge_sort(arr.slice(0, arr.length/2)),
merge_sort(arr.slice(arr.length/2, arr[-1])))
end
def merge(arr1, arr2)
sorted = []
begin
less_than = arr1[0] <=> arr2[0]
less_than = (arr1[0] == nil ? 1 : -1) if less_than == nil
case less_than
when -1
sorted << arr1[0]
arr1 = arr1.drop(1)
when 0
sorted << arr1[0]
sorted << arr2[0]
arr1 = arr1.drop(1)
arr2 = arr2.drop(1)
when 1
sorted << arr2[0]
arr2 = arr2.drop(1)
end
end until (arr1.length == 0 && arr2.length == 0)
sorted
end
merge_sort([1,6,3,8,22,3,11,24,54,68,79,80,98,65,46,76,53])
#Returns => [1, 3, 3, 6, 8, 11, 22, 24, 46, 53, 54, 65, 68, 76, 79, 80, 98]
The method I have actually correctly sorts the list, but I am not totally sure how the method combines each branch and then returns the sorted merged list, rather than just the first two length one arrays it combines.
Also, If anyone has ideas for how I can make the merge method prettier to look more like the ruby code I have grown to love please let me know.

Here is my implementation of mergesort in Ruby
def mergesort(array)
return array if array.length == 1
middle = array.length / 2
merge mergesort(array[0...middle]), mergesort(array[middle..-1])
end
def merge(left, right)
result = []
until left.length == 0 || right.length == 0 do
result << (left.first <= right.first ? left.shift : right.shift)
end
result + left + right
end
As you can see, the mergesort method is basically the same as yours, and this is where the recursion occurs so that is what I will focus on.
First, you have your base case: return array if array.length == 1 This is what allows the recursion to work and not go on indefinitely.
Next, in my implementation I have defined a variable middle to represent the middle of the array: middle = array.length / 2
Finally, the third line is where all the work occurs: merge mergesort(array[0...middle]), mergesort(array[middle..-1])
What you are doing here is telling the merge method to merge the mergesorted left half with the mergesorted right half.
If you assume your input array is [9, 1, 5, 4] what you are saying is merge mergesort([9, 1]), mergesort([5, 4]).
In order to perform the merge, you first have to mergesort [9, 1] and mergesort [5, 4]. The recursion then becomes
merge((merge mergesort([9]), mergesort([1])), (merge mergesort([5]), mergesort([4])))
When we recurse again, the mergesort([9]) has reached the base case and returns [9]. Similarly, mergesort([1]) has also reached the base case and returns [1]. Now you can merge [9] and [1]. The result of the merge is [1, 9].
Now for the other side of the merge. We have to figure out the result of merge mergesort([5]), mergesort([4]) before we can merge it with [1, 9]. Following the same procedure as the left side, we get to the base case of [5] and [4] and merge those to get [4, 5].
Now we need to merge [1, 9] with [4, 5].
On the first pass, result receives 1 because 1 <= 4.
On the next pass, we are working with result = [1], left = [9], and right = [4, 5]. When we see if left.first <= right.first we see that it is false, so we return right.shift, or 4. Now result = [1, 4].
On the third pass, we are working with result = [1, 4], left = [9], and right = [5]. When we see if left.first <= right.first we see that it is false, so we return right.shift, or 5. Now result = [1, 4, 5].
Here the loop ends because right.length == 0.
We simply concatenate result + left + right or [1, 4, 5] + [9] + [], which results in a sorted array.

Here is my version of a recursive merge_sort method for Ruby. Which does the exact same as above, but slightly different.
def merge_sort(array)
array.length <= 1 ? array : merge_helper(merge_sort(array[0...array.length / 2]), merge_sort(array[array.length / 2..-1]))
end
def merge_helper(left, right, merged = [])
left.first <= right.first ? merged << left.shift : merged << right.shift until left.length < 1 || right.length < 1
merged + left + right
end
p merge_sort([]) # => []
p merge_sort([20, 8]) # => [8, 20]
p merge_sort([16, 14, 11]) # => [11, 14, 16]
p merge_sort([18, 4, 7, 19, 17]) # => [4, 7, 17, 18, 19]
p merge_sort([10, 12, 15, 13, 16, 7, 19, 2]) # => [2, 7, 10, 12, 13, 15, 16, 19]
p merge_sort([3, 14, 10, 8, 11, 7, 18, 17, 2, 5, 9, 20, 19]) # => [2, 3, 5, 7, 8, 9, 10, 11, 14, 17, 18, 19, 20]

Related

ruby method returns wrong elements with odd/even index ruby 2.6.0

def even_odd_array
number = 5169294814153321
array_odd_index = []
array_even_index = []
array_of_chars = number.to_s.chars.map(&:to_i)
array_of_chars.each { |x| array_of_chars.index(x) % 2 == 0 ? array_even_index << x : array_odd_index << x } <----- this returns wrong arrays
#array_odd_index, array_even_index = array_of_chars.each_slice(2).to_a.transpose
p array_even_index
p array_odd_index
end
array_even_index [5, 6, 2, 4, 4, 5, 3, 3, 2]
array_odd_index [1, 9, 9, 8, 1, 1, 1]
what's wrong with it and are there any other ways to make it?
The problem with your actual code is that index returns the index of the first element it finds in the receiver. As 1 is 4 times in number it'll return the index of the first 1 in number from left to right, same for all other repeated numbers.
An easy solution; use each_with_index which allows you to iterate over each element in the receiver plus yielding the current index of that element, so you can check if the index is even or not, deciding where to push the element:
array_of_chars.each_with_index do |x, index|
if index.even?
array_even_index << x
else
array_odd_index << x
end
end
Or you can use partition plus with_index for that:
array_even_index, array_odd_index = 5169294814153321.digits.reverse.partition.with_index { |_, index| index.even? }
p array_even_index # [5, 6, 2, 4, 1, 1, 3, 2]
p array_odd_index # [1, 9, 9, 8, 4, 5, 3, 1]

Find all pivot values of quickSort algorithm

I seem to be a little confused on the proper implementation of Quick Sort.
If I wanted to find all of the pivot values of QuickSort, at what point do I stop dividing the subarrays?
QuickSort(A,p,r):
if p < r:
q = Partition(A,p,r)
Quicksort(A,p,q-1)
Quicksort(A,q+1,r)
Partition(A,p,r):
x = A[r]
i = p-1
for j = p to r-1:
if A[j] ≤ x:
i = i + 1
swap(A[i], A[j])
swap(A[i+1], A[r])
return i+1
Meaning, if I have an array:
A = [9, 7, 5, 11, 12, 2, 14, 3, 10, 6]
As Quick Sort breaks this into its constitutive pieces...
A = [2, 5, 3] [12, 7, 14, 9, 10, 11]
One more step to reach the point of confusion...
A = [2, 5] [7, 12, 14, 9, 10, 11]
Does the subArray on the left stop here? Or does it (quickSort) make a final call to quickSort with 5 as the final pivot value?
It would make sense to me that we continue until all subarrays are single items- but one of my peers have been telling me otherwise.
Pivots for your example would be: 6, 3, 11, 10, 9, 12. Regarding
Does the subArray on the left stop here?
It is always best to examine the source code. When your recursive subarray becomes [2, 5, 3], function QuickSort will be invoked with p = 0 and r = 2. Let's proceed: Partition(A,0,2) will return q = 1, so the next two calls will be Quicksort(A,0,0) and Quicksort(A,2,2). Therefore, Quicksort(A,0,1) will never be invoked, so you'll never have a chance to examine the subarray [2, 5] - it has already been sorted!

Insert a number into an ordered array

I have an array of numbers sorted either in ascending or descending order, and I want to find the index at which to insert a number while preserving the order of the array. If the array is [1, 5, 7, 11, 51] and the number to insert is 9, I would be expecting 3 so I could do [1, 5, 7, 11, 51].insert(3, 9). If the array is [49, 32, 22, 11, 10, 8, 3, 2] and the number to be inserted is 9, I would be expecting 5 so I could do [49, 32, 22, 11, 10, 8, 3, 2].insert(5, 9)
What would be the best/cleanest way to find the index at which to insert 9 in either of these two arrays while preserving the sorting of the array?
I wrote this code that works, but it's not very pretty:
array = [55, 33, 10, 7, 1]
num_to_insert = 9
index_to_insert = array[0..-2].each_with_index.map do |n, index|
range = [n, array[index.next]].sort
index.next if num_to_insert.between?(range[0], range[1])
end.compact.first
index_to_insert # => 3
Wand Maker's answer isn't bad, but it has two problems:
It sorts the entire array to determine whether it's ascending or descending. That's silly when all you have to do is find one element that's not equal to the one before it compare the first and last elements to determine this. That's O(n) O(1) in the worst case instead of O(n log n).
It uses Array#index when it should use bsearch. We can do a binary search instead of iterating over the whole array because it's sorted. That's O(log n) in the worst case instead of O(n).
I found it was clearer to split it into two methods, but you could of course turn it into one:
def search_proc(ary, n)
case ary.first <=> ary.last
when 1 then ->(idx) { n > ary[idx] }
when -1 then ->(idx) { n < ary[idx] }
else raise "Array neither ascending nor descending"
end
end
def find_insert_idx(ary, n)
(0...ary.size).bsearch(&search_proc(ary, n))
end
p find_insert_idx([1, 5, 7, 11, 51], 9)
#=> 3
p find_insert_idx([49, 32, 22, 11, 10, 8, 3, 2], 9)
#=> 5
(I use Range#bsearch here. Array#bsearch works the same, but it was more convenient to use a range to return an index, and more efficient since otherwise we'd have to do each_with_index.to_a or something.)
This is not a good way, but perhaps cleaner since you can use the method insert_sorted(number) on either an ascending or descending array without bothering about the index it will be placed on:
module SortedInsert
def insert_index(number)
self.each_with_index do |element, index|
if element > number && ascending?
return index
end
if element < number && descending?
return index
end
end
length
end
def insert_sorted(number)
insert(insert_index(number), number)
end
def ascending?
first <= last
end
def descending?
!ascending?
end
end
Use it on a array as follows:
array = [2, 61, 12, 7, 98, 64]
ascending = array.sort
descending = array.sort.reverse
ascending.extend SortedInsert
descending.extend SortedInsert
number_to_insert = 3
puts "Descending: "
p number_to_insert
p descending
p descending.insert_sorted(number_to_insert)
puts "Ascending: "
p number_to_insert
p ascending
p ascending.insert_sorted(number_to_insert)
This will give:
Descending:
3
[98, 64, 61, 12, 7, 2]
[98, 64, 61, 12, 7, 3, 2]
Ascending:
3
[2, 7, 12, 61, 64, 98]
[2, 3, 7, 12, 61, 64, 98]
Notes:
The module defines a few methods that will be added to the specific Array object alone.
The new methods provides a sorted array (either ascending/descending) a method insert_sorted(number) which enables to insert the number at sorted position.
In case the position of insertion is required, there is a method for that too: insert_index(number), which will provide the index to which the number needs to be inserted so that the resultant array remains sorted.
Caveat: The module assumes the array being extended is sorted either as ascending or descending.
Here is the simplest way I can think of doing.
def find_insert_idx(ary, n)
is_asc = (ary.sort == ary)
if (is_asc)
return ary.index { |i| i > n }
else
return ary.index { |i| i < n }
end
end
p find_insert_idx([1,5,7,11,51], 9)
#=> 3
p find_insert_idx([49,32,22,11,10,8,3,2], 9)
#=> 5

Given integers how do I find asc and desc sequences of three?

I have integers i.e. 9, 5, 4, 3, 1, 6, 7, 8. I want to return the index where a sequence of three descending or ascending integers exists. In the example above I would get indices 1 and 5. What is the ruby code for this?
def seq
array = [9,5,4,3,1,6,7,8]
array.each_with_index |val, index|
if (val < (array[index + 1]).val < (array[index + 1]).val)
puts "#{index}"
# Skip two indexes
end
end
I think the logic behind your solution is almost correct, but your syntax is pretty far off from valid Ruby.
Here are a pair of pretty verbose solutions that will (hopefully) be fairly obvious:
numbers = [9, 6, 5, 4, 3, 1, 6, 7, 8]
# Find non-overlapping sets
i = 0
until i > numbers.length - 2
a, b, c = numbers[i..i + 2]
if (a - b == b - c) && (a - b).abs == 1
puts "#{i} (#{a},#{b},#{c})"
# Skip next two indexes
i += 3
else
i += 1
end
end
# Find overlapping sets (same solution, but don't skip indexes)
(0...numbers.length - 2).each do |i|
a, b, c = numbers[i..i + 2]
if (a - b == b - c) && (a - b).abs == 1
puts "#{i} (#{a},#{b},#{c})"
end
end
Since the question is not clear enough. I will assume the question is about finding 3 ascending or descending continuous numbers. If the length of the satisfied sequence it longer than 3, e.g [2, 3, 4, 5], it returns 0 and 1.
Here is the algorithm, do list[index] - list[index - 1] for all elements, and repeat it for another time, the answer will be the index of 0 elements after the calculation.
Intuitively,
original 9, 5, 4, 3, 1, 6, 7, 8
first pass -4, -1, -1, -2, 5, 1, 1
2nd pass 3, 0, -1, 7, 4, 0 -> the answer will be the indexes of 0's, which is 1, 5
Algorithm:
lst = [9, 5, 4, 3, 1, 6, 7, 8]
lst1 = lst.each_cons(2).map{ |a, b| b-a }
lst2 = lst1.each_cons(2).map{ |a, b| b-a }
result = lst2.each_index.select{|i| lst2[i] == 0}
result = [1, 5]
Here’s a solution using each_cons(3).with_index:
[9,5,4,3,1,6,7,8].each_cons(3).with_index.select { |s, i| s[0] < s[1] && s[1] < s[2] }.map(&:last)

How do I loop through a set of numbers, do some addition and append to an array?

My goal here is to create an array with the sum totals of every combination of 2 numbers on a set of dice. I'm creating the beginning of a loop that adds die1[0] to die2[0..5] before going through die1[1] + die2[0..5] and so on.
I've got this code below and I'm doing something wrong. I want to be able to call specific numbers in the array, such as dieSums[4], and get one number. Any idea what i'm doing incorrectly here?
die1 = [1,2,3,4,5,6]
die2 = [1,2,3,4,5,6]
dieSums = []
count = 0
while count <= 5 do
dieSums << die1[0] + die2[count]
count += 1
puts dieSums[5]
end
A while loop, as you've written it, isn't very Rubyonic. (Rubinic?) A more idiomatic way to iterate over the elements of an array:
#!/usr/bin/ruby
die1 = [1,2,3,4,5,6]
die2 = [1,2,3,4,5,6]
dieSums = []
die1.each do |d1|
die2.each do |d2|
dieSums << d1 + d2
end
end
puts dieSums[5]
Of course, die1 and die2 are identical in this case, so you could replace die2 with die1 and it'd all work out.
You are calling puts dieSums[5] inside the loop. dieSums[5] won't exist until the last iteration. It'll work if you call it outside the loop:
die1 = [1,2,3,4,5,6]
die2 = [1,2,3,4,5,6]
dieSums = []
count = 0
while count <= 5 do
dieSums << die1[0] + die2[count]
count += 1
end
puts dieSums[5] #=> 7
As a side note: notice that you are over-complicating the problem (because you think in imperative terms, take a look at Functional programming). The sum of all possible values for two dice:
>> die = [1,2,3,4,5,6]
>> die.product(die).map { |v1, v2| v1 + v2 }
=> [2, 3, 4, 5, 6, 7, 3, 4, 5, 6, 7, 8, 4, 5, 6, 7, 8, 9, 5, 6, 7, 8, 9, 10, 6, 7, 8, 9, 10, 11, 7, 8, 9, 10, 11, 12]
Call uniq at the end if you don't want repeated values.
Use repeated_combination(2) instead of product if you don't care about the order.
Note that die.product(die) = die.repeated_permutation(2))
Finding all the sums for N dice is almost as simple:
>> die.repeated_permutation(5).map { |values| values.inject(:+) }

Resources