Combine and sort 2 arrays - ruby

This question was asked somewhere else, but I just wanted to check if what I did was applicable given the rspec circumstances:
Write a method that takes two sorted arrays and produces the sorted array that combines both.
Restrictions:
Do not call sort anywhere.
Do not in any way modify the two arrays given to you.
Do not circumvent (2) by cloning or duplicating the two arrays, only to modify the copies.
Hint: you will probably need indices into the two arrays.
combine_arrays([1, 3, 5], [2, 4, 6]) == [1, 2, 3, 4, 5, 6]
Can you just combine the two arrays into a single array and then run a typical bubble sort?
def combine_arrays(arr1,arr2)
final = arr1 + arr2
sorted = true
while sorted do
sorted = false
(0..final.length - 2).each do |x|
if final[x] > final[x+1]
final[x], final[x+1] = final[x+1], final[x]
sorted = true
end
end
end
final
end
p combine_arrays([1,3,5],[2,4,6]) => [1, 2, 3, 4, 5, 6]

Here is a variant which relies solely on Ruby's enumerators. The result is short and sweet.
# merge two sorted arrays into a sorted combined array
def merge(a1, a2)
[].tap do |combined|
e1, e2 = a1.each, a2.each
# The following three loops terminate appropriately because
# StopIteration acts as a break for Kernel#loop.
# First, keep appending smaller element until one of
# the enumerators run out of data
loop { combined << (e1.peek <= e2.peek ? e1 : e2).next }
# At this point, one of these enumerators is "empty" and will
# break immediately. The other appends all remaining data.
loop { combined << e1.next }
loop { combined << e2.next }
end
end
The first loop keeps grabbing the minimum of the two enumerator values until one of the enumerators runs out of values. The second loop then appends all remaining (which may be none) values from the first array's enumerator, the third loop does the same for the second array's enumerator, and tap hands back the resulting array.

Sure, you can do that but you are overlooking a real gimmee - the two arrays you are given will already be sorted.
def combine_arrays(A1, A2)
retVal = Array.CreateInstance(System::Int32, A1.Length + A2.Length - 1)
i = 0
j = 0
while i < A1.Length | j < A2.Length
if i < A1.Length and self.A1(i) < self.A2(j) then
self.retVal(i + j) = self.A1(i)
i += 1
else
self.retVal(i + j) = self.A2(j)
j += 1
end
end
return retVal
end

This is based on the same logic as Dale M's post, but in proper ruby:
def combine_arrays(arr1,arr2)
[].tap do |out|
i1 = i2 = 0
while i1 < arr1.size || i2 < arr2.size
v1 = arr1[i1]
v2 = arr2[i2]
if v1 && (!v2 || v1 < v2)
out << v1
i1 += 1
else
out << v2
i2 += 1
end
end
end
end
combine_arrays([1,3,5], [2,4,6])

Take a look at this one:
def merge(arr1, arr2)
arr2.each { |n| arr1 = insert_into_place(arr1, n) }
arr1.empty? ? arr2 : arr1
end
def insert_into_place(array, number)
return [] if array.empty?
group = array.group_by { |n| n >= number }
bigger = group[true]
smaller = group[false]
if bigger.nil?
number > smaller.last ? smaller << number : smaller.unshift(number)
else
(smaller << number) + bigger
end
end

Related

Quick Sort - How to apply recursive and get output

I am working on a school exercise on Quick Sort.
I have succeded to do the first exercise which is
Challenge
Given an array 'array' and a number 'p' in the first cell in the
array, can you partition the array so that all elements greater than
'p' is to the right of it and all the numbers smaller than 'p' are to
it's left? For example, if given the following as input:
4 5 3 9 1 The first number 4 is the pivot, so you should put the
smaller numbers to the left, and the larger to the right, and output:
3 1 4 5 9 The array should otherwise remain in the same order.
Can you write code to partition an array?
Example p partition([4, 5, 3, 9, 1])
=> [3, 1, 4, 5, 9]
My code for the above in ruby is
def partition(array)
# write your code here
pivot = array.shift()
base = [pivot]
left = []
right = []
array.each { |e| if e < pivot
left.push(e)
else
right.push(e)
end
}
left + base + right
end
p partition([4, 5, 3, 9, 1])
# => [3, 1, 4, 5, 9]
The Challenge for which I am raising this Question is
The function should output like this
p some_function_name([5, 8, 1, 3, 7, 10, 2])
# => 2 3
# 1 2 3
# 7 8 10
# 1 2 3 5 7 8 10
I am trying for the last 36hrs how to apply the partition code above recursively on this challenge. During my 36hrs of research on the Quick Sort algorithm, I can make the code to give the result of a sorted array, but this challenge is asking to provide prints at certain conditions which I am not able to achieve.
Any help is much appreciated.
This one tried for pivot at end
def partition(array)
# write your code here
pivot = array[-1]
i = -1
j = 0
while j < array.length-1
if array[j] < pivot
i += 1
array[i], array[j] = array[j], array[i]
end
j += 1
end
array.insert(i+1, array.pop)
puts index = i+1
puts (array.take index).join(' ')
puts (array.drop index+1).join(' ')
end
partition([5, 8, 1, 3, 7, 10, 2])
this one, I am not able to find a condition for terminating recursive function
def partition(array)
# write your code here
pivot = array.shift()
base = [pivot]
left = []
right = []
array.each { |e| if e < pivot
left.push(e)
else
right.push(e)
end
}
left + base + right
if left.length < 2
return
end
partition(left)
end
p partition([5, 8, 1, 3, 7, 10, 2])
p partition([1, 3, 2])
p partition([8, 7, 10])
It's not clear to my why you want partition to be recursive. There is no real natural way to make it recursive in a simple way. You can introduce a recursive helper method, but I don't see that as an improvement. partition really doesn't need to be more complicated than this:
def partition(array, pivot)
return [], [] if array.empty?
array.partition(&pivot.method(:>))
end
If you absolutely must, you can make it recursive like this:
def partition(...) = partition_rec(...)
private def partition_rec(array, pivot, left = [], right = [])
return left, right if array.empty?
first = array.first
rest = array.drop(1)
if first < pivot
partition_rec(rest, pivot, left + [first], right)
else
partition_rec(rest, pivot, left, right + [first])
end
end
With this partition in place, we can easily write our quicksort:
def quicksort(array)
return array if array.length < 2
pivot = array.first
left, right = partition(array.drop(1), pivot)
quicksort(left) + [pivot] + quicksort(right)
end
Now, all we need to do is to also print the result at each recursive call. A simple way to do that would be with Kernel#p, which returns its argument, so we can just insert it without changing the return value:
def quicksort(array)
return array if array.length < 2
pivot = array.first
left, right = partition(array.drop(1), pivot)
p quicksort(left) + [pivot] + quicksort(right)
end
If we need to replicate the exact format of the string as given in the question, then we should use Kernel#puts instead:
def quicksort(array)
return array if array.length < 2
pivot = array.first
left, right = partition(array.drop(1), pivot)
(quicksort(left) + [pivot] + quicksort(right)).tap do |result|
puts result.join(' ')
end
end
Note that there is a big no-no in your code. Here, you are modifying the argument passed into partition:
array.shift()
Same here:
array.insert(i+1, array.pop)
You must not, ever, mutate an argument. In fact, you should avoid mutation at all, as much as possible. The only thing you are allowed to mutate is yourself, i.e. self. But even then, you should be careful.

Big O Complexity of Algorithm

This method seeks to express num as a product of elements in arr.
For e.g method1(37,[1,3,5]) returns [2,0,7]
// arr is an array of divisors sorted in asc order, e.g. [1,3,5]
def method1(num, arr)
newArr = Array.new(arr.size, 0)
count = arr.size - 1
while num > 0
div = arr[count]
if div <= num
arr[count] = num/div
num = num % div
end
count = count - 1
end
return newArr
end
Would really appreciate if you could give me some help to derive the complexity of the algorithm. Please also feel free to improve the efficiency of my algorithm
Here's a refactored version of your code :
def find_linear_combination(num, divisors)
results = divisors.map do |divisor|
q, num = num.divmod(divisor)
q
end
results if num == 0
end
puts find_linear_combination(37, [5, 3, 1]) == [7, 0, 2]
puts find_linear_combination(37, [1, 3, 5]) == [37, 0, 0]
puts find_linear_combination(37, [5]).nil?
With n being the size of divisors, this algorithm clearly appears to be O(n). There's only one loop (map) and there's only one integer division inside the loop.
Note that the divisors should be written in descending order. If no linear combination is found, the method returns nil.
If you want to sort divisors, the algorithm would be O(n*log n). It could also be a good idea to append 1 if necessary (O(1)).
Here's what you can do:
def method1(num, arr)
newArr = Array.new(arr.size, 0)
count = arr.size()-1
while num>0
div = arr[count]
if div <= num
arr[count] = num / div
num = num % div
end
count = count + 1
end
return arr
end
ar = Array.new(25000000) { rand(1...10000) }
t1 = Time.now
method1(37, ar)
t2 = Time.now
tdelta = t2 - t1
print tdelta.to_f
Output:
0.102611062
Now double the array size:
ar = Array.new(50000000) { rand(1...10) }
Output:
0.325793964
And double again:
ar = Array.new(100000000) { rand(1...10) }
Output:
0.973402568
So an n doubles, the duration roughly triples. Since O(3n) == O(n), the
whole algorithm runs in O(n) time, where n represents the size of the input
array.

implement shell sort by ruby

I try to implement shell sort by ruby.
def shell_sort(list)
d = list.length
return -1 if d == 0
(0...list.length).each do |i|
d = d / 2
puts "d:#{d}"
(0...(list.length-d)).each do |j|
if list[j] >= list[j+d]
list[j], list[j+d] = list[j+d], list[j]
end
end
puts list.inspect
break if d == 1
end
list
end
puts shell_sort([10,9,8,7,6,5,4,3,2,1]).inspect
but the result is incorrect.
=>[2, 1, 3, 4, 5, 7, 6, 8, 9, 10]
I don't know where going wrong, hope someone can help me. Thanks in advance!
I referenced Shell Sort in here : Shell Sort - Wikepedia, and from that I have understood your algorithm is wrong. Iteration of gap sequence is alright, I mean you iterate only upto d/2 == 1.
But for a gap, let's say 2, you simply iterate from 0 to list.length-2 and swap every j and j+2 elements if list[j] is greater than list[j+2]. That isn't even a proper insertion sort, and Shell Sort requires Insertion sorts on gaps. Also Shell Sort requires that after you do an x gap sort, every xth element, starting from anywhere will be sorted (see the example run on the link and you can verify yourself).
A case where it can wrong in a 2 gap sort pass :
list = 5,4,3,2,1
j = 0 passed :
list = 3,4,5,2,1
j = 1 passed :
list = 3,2,5,4,1
j = 2 passed
list = 3,2,1,4,5
After it completes, you can see that every 2nd element starting from 0 isn't in a sorted order. I suggest that you learn Insertion Sort first, then understand where and how it is used in Shell Sort, and try again, if you want to do it by yourself.
Anyway, I have written one (save it for later if you want) taking your method as a base, with a lot of comments. Hope you get the idea through this. Also tried to make the outputs clarify the how the algorithm works.
def shell_sort(list)
d = list.length
return -1 if d == 0
# You select and iterate over your gap sequence here.
until d/2 == 0 do
d = d / 2
# Now you pick up an index i, and make sure every dth element,
# starting from i is sorted.
# i = 0
# while i < list.length do
0.step(list.length) do |i|
# Okay we picked up index i. Now it's just plain insertion sort.
# Only difference is that we take elements with constant gap,
# rather than taking them up serially.
# igap = i + d
# while igap < list.length do
(i+d).step(list.length-1, d) do |igap|
# Just like insertion sort, we take up the last most value.
# So that we can shift values greater than list[igap] to its side,
# and assign it to a proper position we find for it later.
temp = list[igap]
j = igap
while j >= i do
break if list[j] >= list[j - d]
list[j] = list[j-d]
j -= d
end
# Okay this is where it belongs.
list[j] = temp
#igap += d
end
# i += 1
end
puts "#{d} sort done, the list now : "
puts list.inspect
end
list
end
list = [10,9,8,7,6,5,4,3,2,1]
puts "List before sort : "
puts list.inspect
shell_sort(list)
puts "Sorted list : "
puts list.inspect
I think your algorithm needs a little tweaking.
The reason it fails is simply because on the last run (when d == 1) the smallest element (1) isn't near enough the first element to swap it in in one go.
The easiest way to make it work is to "restart" your inner loop whenever elements switch places. So, a little bit rough solution would be something like
(0...(list.length-d)).each do |j|
if list[j] >= list[j+d]
list[j], list[j+d] = list[j+d], list[j]
d *= 2
break
end
end
This solution is of course far from optimal, but should achieve required results with as little code as possible.
You should just do a last run on array. To simplify your code I extracted exchange part into standalone fucntion so you could see now where you should do this:
def exchange e, list
(0...(list.length-e)).each do |j|
if list[j] >= list[j+e]
list[j], list[j+e] = list[j+e], list[j]
end
end
end
def shell_sort(list)
d = list.length
return -1 if d == 0
(0...list.length).each do |i|
d = d / 2
puts "d:#{d}"
exchange(d, list)
puts list.inspect
if d == 1
exchange(d, list)
break
end
end
list
end
arr = [10,9,8,7,6,5,4,3,2,1]
p shell_sort(arr)
Result:
#> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Merge sort algorithm using recursion

I'm doing The Odin Project. The practice problem is: create a merge sort algorithm using recursion. The following is modified from someone's solution:
def merge_sort(arry)
# kick out the odds or kick out of the recursive splitting?
# I wasn't able to get the recombination to work within the same method.
return arry if arry.length == 1
arry1 = merge_sort(arry[0...arry.length/2])
arry2 = merge_sort(arry[arry.length/2..-1])
f_arry = []
index1 = 0 # placekeeper for iterating through arry1
index2 = 0 # placekeeper for iterating through arry2
# stops when f_arry is as long as combined subarrays
while f_arry.length < (arry1.length + arry2.length)
if index1 == arry1.length
# pushes remainder of arry2 to f_arry
# not sure why it needs to be flatten(ed)!
(f_arry << arry2[index2..-1]).flatten!
elsif index2 == arry2.length
(f_arry << arry1[index1..-1]).flatten!
elsif arry1[index1] <= arry2[index2]
f_arry << arry1[index1]
index1 += 1
else
f_arry << arry2 [index2]
index2 += 1
end
end
return f_arry
end
Is the first line return arry if arry.length == 1 kicking it out of the recursive splitting of the array(s) and then bypassing the recursive splitting part of the method to go back to the recombination section? It seems like it should then just keep resplitting it once it gets back to that section as it recurses through.
Why must it be flatten-ed?
The easiest way to understand the first line is to understand that the only contract that merge_sort is bound to is to "return a sorted array" - if the array has only one element (arry.length == 1) it is already sorted - so nothing needs to be done! Simply return the array itself.
In recursion, this is known as a "Stop condition". If you don't provide a stop condition - the recursion will never end (since it will always call itself - and never return)!
The result you need to flatten your result, is because you are pushing an array as an element in you resulting array:
arr = [1]
arr << [2, 3]
# => [1, [2, 3]]
If you try to flatten the resulting array only at the end of the iteration, and not as you are adding the elements, you'll have a problem, since its length will be skewed:
arr = [1, [2, 3]]
arr.length
# => 2
Although arr contains three numbers it has only two elements - and that will break your solution.
You want all the elements in your array to be numbers, not arrays. flatten! makes sure that all elements in your array are atoms, and if they are not, it adds the child array's elements to itself instead of the child array:
arr.flatten!
# => [1, 2, 3]
Another you option you might want to consider (and will be more efficient) is to use concat instead:
arr = [1]
arr.concat([2, 3])
# => [1, 2, 3]
This method add all the elements in the array passed as parameter to the array it is called on.

Ruby Enumeration: Taken first n where block returns true

I want to take the first "n" entries which pass the block
a = 1..100_000_000 # Basically a long array
# This iterates over the whole array -- no good
b = a.select{|x| x.expensive_operation?}.take(n)
I want to short circuit the iteration once i've got n entries where 'expensive' condition is true.
What do you suggest? take_while and keep count of n?
# This is the code i have; which i think can be written better, but how?
a = 1..100_000_000 # Basically a long array
n = 20
i = 0
b = a.take_while do |x|
((i < n) && (x.expensive_operation?)).tap do |r|
i += 1
end
end
Ruby 2.0 implements lazy enumerables, for older versions use the gem enumerable-lazy:
require 'enumerable/lazy'
(1..Float::INFINITY).lazy.select(&:even?).take(5).to_a
#=> [2, 4, 6, 8, 10]
It should work with a simple for loop and a break :
a = 1..100_000_000 # Basically a long array
n = 20
selected = []
for x in a
selected << x if x.expensive_operation?
break if select.length == n
end

Resources