I am new to Ruby and I am trying out the merge sort algorithm as given in Wikipedia
I am getting "comparison of Fixnum with Array failed (ArgumentError)" failed error when comparing the first elements of the left and right array in the merge method. What could be the reason and how do I resolve this problem? Thanks :)
def mergeSort(array)
if array.length == 1
return array
end
middle = array.length/2 - 1
left = array[0..middle]
right = array[middle+1..array.length-1]
left = mergeSort(left)
right = mergeSort(right)
merge(left,right)
end
def merge(left,right)
result = []
while left.length > 0 || right.length > 0
if left.length > 0 && right.length > 0
one = left[0]
two = right[0]
puts ("one's class is #{one.class} two's class is #{two.class} two is #{two}")
if one <= two
result << left.shift
else
result << right.shift
end
elsif left.length > 0
result.push(left)
left = []
else
result.push(right)
right = []
end
end
puts "result is #{result}"
result
end
The error is on these lines:
elsif left.length > 0
result.push(left)
left = []
else
result.push(right)
right = []
end
A simple example should indicate why:
irb(main):067:0> a=[1,2]
=> [1, 2]
irb(main):068:0> b=[3,4]
=> [3, 4]
irb(main):069:0> a.push(b)
=> [1, 2, [3, 4]]
Instead of push(), try concat().
Related
I am learning ruby and have started practicing problems from leetcode, yesterday I have a problem which I am not able to solve since yesterday.
I tried hard doing that in ruby, but not able to do yet.
I tried this
def give_chair(a)
u = a.uniq
d = []
u.each do |i|
d << i if a.count(i) == 1
end
d
end
def smallest_chair(times, target_friend)
friend = times[target_friend]
sorted_arrival_times = times.sort
leave_time_chair = {}
chair = 0
chairs_array = []
uniq_chars_array = []
sorted_arrival_times.each do |i|
if leave_time_chair.keys.select { |k| i[0] > k }.empty?
leave_time_chair[i[1]] = chair
chair+=1
else
all_keys = leave_time_chair.keys.select { |k| k <= i[0] }
chairs_array = leave_time_chair.values
p chairs_array
if give_chair(chairs_array).empty?
leave_time_chair[i[1]] = chairs_array.sort.first
else
leave_time_chair[i[1]] = give_chair(chairs_array).sort.first
end
end
if i == friend
p leave_time_chair
return leave_time_chair[i[1]]
end
end
end
# a = [[33889,98676],[80071,89737],[44118,52565],[52992,84310],[78492,88209],[21695,67063],[84622,95452],[98048,98856],[98411,99433],[55333,56548],[65375,88566],[55011,62821],[48548,48656],[87396,94825],[55273,81868],[75629,91467]]
# b = 6
# p smallest_chair(a, b)
but it is failing for some test cases.
I am not able to create an algorithm for it.
Question = https://leetcode.com/problems/the-number-of-the-smallest-unoccupied-chair
My approach:
First I sort the times array according to arrival times.
Then I iterate over each array element
Now if the arrival time is greater than all the previous leaving time (I am creating key, value pair of leaving time and chair given) then I add a new key=> value pair in leave_time_chair (which is hash) and where key is the leaving time of current array and value is the chair given to it.
Then I increment the chair (chair+=1)
Else I get all those leaving time which are equal or less than the current arrival time (all_keys = leave_time_chair.keys.select { |k| k <= i[0] })
Then I get all the chairs of those times
Now I have all the chairs like this => [0, 0, 1, 2] so I wrote one function [ give_chair(a) ] which gives me those elements which are not repeated. like this => [1, 2] and then I assign the shortest number (chair) to the leaving time of current array. and so on...
Then if my current array is equal to the friend I return the chair of it. by extracting it from a hash (leave_time_chair) return leave_time_chair[i[1]]
my naive solution (not optimize yet), basically my idea that i flat-map the input array into an array with each element is a pair [time arrive/leave, friend index], then i will sort that array base on time (don't care arrive or leave), if both pair have same time, then i'll compare the arrive time of fiend index. Finally i loop through the sorted array and evaluate minimum free chair index each step, whenever i meet the targetFriend i return that minimum free chair index.
# #param {Integer[][]} times
# #param {Integer} target_friend
# #return {Integer}
def smallest_chair(times, target_friend)
# times = [[1,2],[4,7],[2,4]]
# targetFriend = 1
sit_times = times.each_with_index.inject([]) { |combi, (time, index)|
combi += [[time.first, index], [time.last, index]]
}
# [[1, 0], [2, 0], [4, 1], [7, 1], [2, 2], [4, 2]]
sit_times.sort! {|x, y|
c = x[0] <=> y[0]
# [[1, 0], [2, 0], [2, 2], [4, 1], [4, 2], [7, 1]]
c = times[x[1]][0] <=> times[y[1]][0] if c == 0
# [[1, 0], [2, 0], [2, 2], [4, 2], [4, 1], [7, 1]]
c
}
chairs = {} # to mark time of friend
occupied = Array.new(times.size, 0) # occupied chair: 1, otherwise: 0
min_free = 0 # current minimum not occupied chair
sit_times.each do |time, friend_index|
if target_friend == friend_index # check
return min_free
end
sit = chairs[friend_index]
if sit # leave
occupied[sit] = 0
chairs[friend_index] = nil
min_free = sit if min_free > sit
else # arrive
chairs[friend_index] = min_free
occupied[min_free] = 1
min_free += 1 until occupied[min_free] == 0 # re-calculate
end
end
end
Note: the code pass test cases on leetcode but the performance is not good.
update
here is the better version, using 3 priority queues, one for arrive times, one for leave times and the last for chair.
PriorityQueue class
class PriorityQueue
attr_reader :length
def initialize(opts={}, &comparator)
order_opt = opts.fetch(:order, :asc)
#order = order_opt == :asc ? -1 : 1
#comparator = comparator
#items = [nil]
#length = 0
end
def push(item)
#items << item
#length += 1
swim(#length)
true
end
def pop
return nil if empty?
swap(1, #length) if #length > 1
#length -= 1
sink(1) if #length > 0
#items.pop
end
def empty?
#length == 0
end
def swap(i, j)
temp = #items[i]
#items[i] = #items[j]
#items[j] = temp
end
def in_order?(i, j)
x = #items[i]
y = #items[j]
order = #comparator.nil? ? (x <=> y) : #comparator.call(x, y)
order == #order
end
def swim(from)
while (up = from / 2) >= 1
break if in_order?(up, from)
swap(up, from)
from = up
end
end
def sink(from)
while (down = from * 2) <= #length
down += 1 if down < #length && in_order?(down + 1, down)
break if in_order?(from, down)
swap(down, from)
from = down
end
end
end
smallest_chair with priority queues (note that i found using sort is faster than a queue for arrive times, but basically the idea is same)
def smallest_chair_pq(times, target_friend)
# a_pq = PriorityQueue.new { |x, y|
# x[0] <=> y[0]
# }
#
# times.each do |t|
# a_pq.push(t)
# end
# sort arrive times is faster than a priority queue
a_pq = times.sort_by(&:first).reverse
# leave times queue
l_pq = PriorityQueue.new { |x, y|
c = x[0] <=> y[0]
c = x[1] <=> y[1] if c == 0
c
}
# chair-indexes queue
# consider case a friend come in at arrive-time at1
# and there's a range chairs with leave times in range lm <= at1 <= ln
# that mean that friend could pick one of those chairs
# and according this problem requirement, should pick the minimun chair index
c_pq = PriorityQueue.new
target_time = times[target_friend][0]
last_chair_index = 0
until a_pq.empty?
a_top = a_pq.pop
arrive_time = a_top.first
if l_pq.empty?
return 0 if arrive_time == target_time
l_pq.push([a_top.last, 0])
else
l_top = l_pq.pop
if l_top.first <= arrive_time
c_pq.push(l_top.last)
until (l_ntop = l_pq.pop).nil? || arrive_time < l_ntop.first
c_pq.push(l_ntop.last)
end
l_pq.push(l_ntop) unless l_ntop.nil?
min_chair_index = c_pq.pop
return min_chair_index if arrive_time == target_time
l_pq.push([a_top.last, min_chair_index])
else
unless c_pq.empty?
chair_index = c_pq.pop
return chair_index if arrive_time == target_time
l_pq.push([a_top.last, chair_index])
else
last_chair_index += 1
return last_chair_index if arrive_time == target_time
l_pq.push([a_top.last, last_chair_index])
end
l_pq.push(l_top)
end
end
end
end
The below code is my newbie take on a bubble sort method.
#For each element in the list, look at that element and the element
#directly to it's right. Swap these two elements so they are in
#ascending order.
def bubble_sort (array)
a = 0
b = 1
until (array.each_cons(2).all? { |a, b| (a <=> b) <= 0}) == true do
sort = lambda {array[a] <=> array[b]}
sort_call = sort.call
loop do
case sort_call
when -1 #don't swap
a += 1
b += 1
break
when 0 #don't swap
a += 1
b += 1
break
when 1 #swap
array.insert(a,array.delete_at(b))
a += 1
b += 1
break
else #end of array, return to start
a = 0
b = 1
break
end
end
end
puts array.inspect
end
array = [4, 2, 5, 6, 3, 23, 5546, 234, 234, 6]
bubble_sort(array)
I want to be able to alter this method so that it takes a block of code as an argument and uses this to determine how it sorts.
For example:
array = ["hello", "my", "name", "is", "daniel"]
bubble_sort(array) {array[#a].length <=> array[#b].length}
(When I've tried this I've turned a and b into instance variables throughout the code.)
I have tried using yield but I get undefined method 'length' for nil:NilClass once the end of the array is reached. I've tried adding in things such as
if array[#b+1] == nil
#a = 0
#b = 1
end
This helps but I still end up with weird problems like infinite loops or not being able to sort more than certain amount of elements.
Long story short, I have been at this for hours. Is there a simple way to do what I want to do? Thanks.
The way you're calling your lambda is a bit odd. It's actually completely unnecessary. I refactored your code and cleaned up a bit of the redundancy. The following works for me:
def sorted?(arr)
arr.each_cons(2).all? { |a, b| (a <=> b) <= 0 }
end
def bubble_sort (arr)
a = 0
b = 1
until sorted?(arr) do
# The yield call here passes `arr[a]` and `arr[b]` to the block.
comparison = if block_given?
yield(arr[a], arr[b])
else
arr[a] <=> arr[b]
end
if [-1, 0, 1].include? comparison
arr.insert(a, arr.delete_at(b)) if comparison == 1
a += 1
b += 1
else
a = 0
b = 1
end
end
arr
end
sample_array = [4, 2, 5, 6, 3, 23, 5546, 234, 234, 6]
# Sanity check:
100.times do
# `a` is the value of `arr[a]` in our function above. Likewise for `b` and `arr[b]`.
print bubble_sort(sample_array.shuffle) { |a, b| a <=> b }, "\n"
end
EDIT
A cleaner version:
# In place swap will be more efficient as it doesn't need to modify the size of the arra
def swap(arr, idx)
raise IndexError.new("Index #{idx} is out of bounds") if idx >= arr.length || idx < 0
temp = arr[idx]
arr[idx] = arr[idx + 1]
arr[idx + 1] = temp
end
def bubble_sort(arr)
loop do
sorted_elements = 0
arr.each_cons(2).each_with_index do |pair, idx|
comparison = if block_given?
yield pair.first, pair.last
else
pair.first <=> pair.last
end
if comparison > 0
swap(arr, idx)
else
sorted_elements += 1
end
end
return arr if sorted_elements >= arr.length - 1
end
end
# A simple test
sample_array = [4, 2, 2, 2, 2, 2, 5, 5, 6, 3, 23, 5546, 234, 234, 6]
sample_str_array = ["a", "ccc", "ccccc"]
100.times do
print bubble_sort(sample_array.shuffle) { |a, b| a <=> b }, "\n"
print bubble_sort(sample_str_array.shuffle) { |a, b| a.length <=> b.length }, "\n"
end
You're not too far off. Just a few things:
Make your function take a block argument
def bubble_sort (array, &block)
Check to see if the user has provided a block
if block_given?
# Call user's comparator block
else
# Use the default behavior
end
Call the user's comparator block
block.call(a, b)
In the user-provided block, accept block params for the elements to compare
bubble_sort(array) {|a,b| a.length <=> b.length}
That should put you in the right ballpark.
Details of the problem: To find if any combination of the array adds to the largest number found in the array.
Here are the steps I am trying to implement:
Extract the largest number from the array
Create a new array of
all the potential combinations that could be added by using
.combination
Test to see if any of these combinations equals the largest number in the original array.
Status: So far, I am just receiving an unexpected end error for the last end in the code. (I have found different answers online on how to solve the subset sums problem in Ruby, but would like to figure out how to solve it using the logic I have used so far.)
Any help would be great!
def subset_sum(sums)
largest_number = subset_sum.sort.reverse[0]
array_without_largest = subset_sum.sort.reverse[1..-1]
full_combination = []
i = 0
while i <= array_without_largest.length
full_combination = full_combination + array_without_largest.combination(i).to_a.to_s
i += 1
end
j = 0
while j <= full_combination.length
return true if full_combination[j].inject { |sum, x| sum + x} == largest_number
j += 1
end
end
return false
end
puts subset_sum(1,2,3,4,10)
puts subset_sum(-1,-3,3,9,8)
Consider this:
def any_subset_adds_to_max?(array)
sub_array = array - [array.max]
every_combination = (1..sub_array.length).flat_map { |n| sub_array.combination(n).to_a }
every_combination.any? { |combination| combination.reduce(:+) == array.max }
end
[
[1, 2, 3, 4, 10],
[-1, -3, 3, 9, 8]
].map { |test_array| any_subset_adds_to_max? test_array } # => [true, false]
Here is the closest example of the code that I could do while maintaining the originality. It works and I appreciate the help!
def subset_sum(sums)
largest_number = sums.max
array_without_largest = sums - [largest_number]
full_combination = []
array_without_largest.size.times do |i|
full_combination << array_without_largest.combination(i+1).to_a
end
full_combination.flatten!(1)
full_combination.size.times do |i|
return true if full_combination[i].inject(:+) == largest_number
end
false
end
I see that there are better ruby bubble sort codes already posted in places such as here:
Using the Bubble sort method for an array in Ruby
But I am having trouble debugging my current code and would appreciate some help with figuring out why my code does not work. Thanks.
def bubble_sort(arr)
original = arr
x = 0
while x < arr.count - 1
if arr[x] < arr[x + 1]
y = arr[x + 1]
arr[x + 1] = arr[x]
arr[x] = y
end
x += 1
end
if original == arr
return arr
else
return bubble_sort(arr)
end
end
One of the problems is this:
original = arr
You expect original to a copy of arr in its current state, right? Well, no. They will point to the same array. That's why your function will never recurse here:
if original == arr
return arr
else
return bubble_sort[arr]
end
To copy the array, use dup
original = arr.dup
Four issues :
bubble_sort[arr] does not work - you should call bubble_sort(arr)
original == arr - will always be true, since you assigned it to arr before - you should have assigned it using dup - original = arr.dup
arr[x] < arr [x+1] will create an array sorted in reverse order...
you should change the local copy rather than the one you got as parameters - result = arr.dup rather than original = arr.dup
The code after the above fixes:
def bubble_sort(arr)
result = arr.dup
x = 0
while x < result.count - 1
if result[x] > result[x + 1]
y = result[x + 1]
result[x + 1] = result[x]
result[x] = y
end
x += 1
end
if arr == result
return result
else
return bubble_sort(result)
end
end
bubble_sort([1,3,5,2,4])
# => [1, 2, 3, 4, 5]
Can anybody please tell what am I doing wrong in my Merge-Sort
def print_array(a)
print a
print "\n\n"
end
#Merge-Sort code starts Here.
def merge_sort(a)
if a.size < 2
return a
end
middle = (a.length / 2).to_i
left = a.slice(0, middle)
right = a.slice(middle, a.size)
merge_sort(left)
merge_sort(right)
a = merge(left, right)
end
def merge(left, right)
result = []
while left.length > 0 || right.length > 0
if left.length > 0 && right.length > 0
if left[0] <= right[0]
result << left.slice!(0)
else
result << right.slice!(0)
end
elsif left.length > 0
result.concat left.slice!(0..left.length-1)
elsif right.length > 0
result.concat right.slice!(0..right.length-1)
end
end
result
end
a = [ 3, 2, 1 ]
print_array(a)
a = merge_sort(a)
print_array(a)
Basically, What I know is that when it comes time to Merge [2] and [1], that happens correctly (ie. [1, 2] is returned) , but for the previous step of the Recursion as it unwinds, it comes back (or doesn't come back) as [1, 2] but as [2, 1], ie.
Previous step
-> left = [3]
-> right = [2, 1] #This is the value of right from recursion instead of [1, 2]
I don't know how to fix this, and it's driving me crazy, Can you please help me?
These two lines:
merge_sort(left)
merge_sort(right)
should be:
left = merge_sort(left)
right = merge_sort(right)
Since your merge sort is not sorting in place, you need to assign the result back to the variables.