What is wrong with my LongestMatrixPath ruby algorithm? - ruby

Using the Ruby language, have the function LongestMatrixPath(strArr) take the array of strings stored in strArr, which will be an NxM matrix of positive single-digit integers, and find the longest increasing path composed of distinct integers. When moving through the matrix, you can only go up, down, left, and right. For example: if strArr is ["345", "326", "221"], then this looks like the following matrix:
3 4 5
3 2 6
2 2 1
For the input above, the longest increasing path goes from: 3 -> 4 -> 5 -> 6. Your program should return the number of connections in the longest path, so therefore for this input your program should return 3. There may not necessarily always be a longest path within the matrix.
def recursiveFunction(array, row, col, path)
current = array[row][col]
row-1 >= 0 ? up = array[row-1][col] : up = nil
row+1 < array.length ? down = array[row+1][col] : down = nil
col-1 >= 0 ? left = array[row][col-1] : left = nil
col+1 < array[row].length ? right = array[row][col+1] : right =
nil
if !path.include?(current) && current.to_i > path[-1].to_i
path.push(current)
p path
if up && up.to_i > current.to_i
recursiveFunction(array, (row-1), col, path)
elsif down && down.to_i > current.to_i
recursiveFunction(array, (row+1), col, path)
elsif left && left.to_i > current.to_i
recursiveFunction(array, row, (col-1), path)
elsif right && right.to_i > current.to_i
recursiveFunction(array, row, (col+1), path)
end
end
path
end
def LongestMatrixPath(strArr)
# turn each string into an array
strArr.map!{|str| str.split("")}
# calculate up, down, left, right formula
# up = [-1][same]
# down = [+2][same]
# left = [same][-1]
# right = [same][+1]
# create data structure to store paths
path = []
# create loop that loops through each element in each array
inside strArr
# then apply recursive function to each element
longest_path = 0
row = 0
while row < strArr.length
col = 0
while col < strArr[row].length
result = recursiveFunction(strArr, row, col, path=[])
longest_path = result.length if result.length > longest_path
col += 1
end
row += 1
end
# return number of connections
result.length-1
end
# LongestMatrixPath(["345", "326", "221"])
# correct answer: 3, my answer: 3
# LongestMatrixPath(["12256", "56219", "43215"])
# correct answer: 4, my answer: 3
# LongestMatrixPath(["67", "21", "45"])
# correct answer: 3, my answer: 1
# LongestMatrixPath(["111", "111", "111"])
# correct answer: 0, my answer: 0
# LongestMatrixPath(["123", "456", "789"])
# correct answer: 4, my answer: 4

The problem exists in this fragment:
if up && up.to_i > current.to_i
recursiveFunction(array, (row-1), col, path)
elsif down && down.to_i > current.to_i
recursiveFunction(array, (row+1), col, path)
elsif left && left.to_i > current.to_i
recursiveFunction(array, row, (col-1), path)
elsif right && right.to_i > current.to_i
recursiveFunction(array, row, (col+1), path)
end
Here, if up is larger than current and, for example, left is also larger than current, you only go up (because of elsif). Instead, you need to go in all possible directions and choose most "promising" one. I altered your code a bit to achieve that, trying to change as little as possible. This works, but might not be the best option though.
def recursiveFunction(array, row, col, original_path)
current = array[row][col]
row-1 >= 0 ? up = array[row-1][col] : up = nil
row+1 < array.length ? down = array[row+1][col] : down = nil
col-1 >= 0 ? left = array[row][col-1] : left = nil
col+1 < array[row].length ? right = array[row][col+1] : right =
nil
path = original_path.dup
if !path.include?(current) && current.to_i > path[-1].to_i
path.push(current)
# p path
options = []
if up && up.to_i > current.to_i
options << recursiveFunction(array, (row-1), col, path)
end
if down && down.to_i > current.to_i
options << recursiveFunction(array, (row+1), col, path)
end
if left && left.to_i > current.to_i
options << recursiveFunction(array, row, (col-1), path)
end
if right && right.to_i > current.to_i
options << recursiveFunction(array, row, (col+1), path)
end
best_path = options.sort_by{ |p| p.length }.last
path += best_path.drop(path.length) if best_path
end
path
end
Not a couple of thing here:
Instead modifying path parameter given into function, it gets duplicated, so you can work on it in many steps.
All conditions for up, down, left and right are checked now. The results are put into an array and then the best result (in terms of length) is chosen.
Then the remainder of this longest path is appended to path with path += best_path.drop(path.length) (if exists)
The results for examples you gave are correct, except example two, where correct answer is 5, not 4.
There is much more that can be improved in this code though. For example, you can do strArr.map!{|str| str.split("").map(&:to_i)} and get rid of all those .to_i in the code.

Related

Is there any way to use the return value as an argument to the same function during the previous recursion in merge sort

I am coding the Merge sort algorithm but somehow got stuck with a problem. The problem is that I need to use the return value of the merge function as an argument as an previous recursive call of the same merge function. Sorry for not being clear.
Here is my code:
a = [10,5,2,20,-50,30]
def mergeSort(arr):
l = 0
h = len(arr)-1
if h > l:
mid = (l+h) // 2
left = arr[l:mid+1]
right = arr[mid+1:]
mergeSort(left)
mergeSort(right)
merge(left, right)
def merge(l, r):
subarr = []
lc = 0
rc = 0
loop = True
while loop:
if lc > len(l)-1 and rc <= len(r)-1:
for i in range(rc, len(r)):
subarr.append(r[i])
loop = False
elif lc <= len(l)-1 and rc > len(r)-1:
for i in range(lc, len(l)):
subarr.append(l[i])
loop = False
elif l[lc] < r[rc]:
subarr.append(l[lc])
lc += 1
loop = True
elif r[rc] < l[lc]:
subarr.append(r[rc])
rc += 1
loop = True
elif l[lc] == r[rc]:
subarr.append(l[lc])
subarr.append(r[rc])
lc += 1
rc += 1
loop = True
mergeSort(a)
Any help will be appreciated thank you :)
First you need to actually return the result. Right now you return nothing so get None back.
Secondly, just assign to the same variable. left = mergeSort(left) and so on.
UPDATE:
Here is a debugged version.
a=[10,5,2,20,-50,30]
def mergeSort(arr):
l=0
h=len(arr)-1
if h>l:
mid=(l+h)//2
left=arr[l:mid+1]
right=arr[mid+1:]
# Capture the merge into variables here.
left=mergeSort(left)
right=mergeSort(right)
# Need a return of the merge.
return merge(left,right)
# Need to return arr if arr has 0 or 1 elements.
else:
return arr
def merge(l,r):
subarr=[]
lc=0
rc=0
loop=True
while loop:
if lc>len(l)-1 and rc<=len(r)-1:
for i in range(rc,len(r)):
subarr.append(r[i])
loop=False
elif lc<=len(l)-1 and rc>len(r)-1:
for i in range(lc,len(l)):
subarr.append(l[i])
loop=False
elif l[lc]<r[rc]:
subarr.append(l[lc])
lc+=1
loop=True
elif r[rc]<l[lc]:
subarr.append(r[rc])
rc+=1
loop=True
elif l[lc]==r[rc]:
subarr.append(l[lc])
subarr.append(r[rc])
lc+=1
rc+=1
loop=True
# Need to return the results of merge.
return subarr
# Need to actually try calling the function to see the result.
print(mergeSort(a))
I also indented more sanely. Trust me, it matters.
There are multiple problems in our code:
you do not return the sorted slice from mergeSort nor merge. Your implementation does not sort the array in place, so you must return subarr in merge and the return value of merge in mergeSort or arr if the length is less than 2.
your code is too complicated: there are many adjustments such as mid+1, len(l)-1, etc. It is highly recommended to use index values running from 0 to len(arr) excluded. This way you do not have to add error prone +1/-1 adjustments.
the merge function should proceed in 3 phases: merge the left and right arrays as long as both index values are less than the array lengths, then append remaining elements from the left array, finally append remaining elements from the right array.
there is no need to make 3 different tests to determine from which of the left and right array to take the next element, a single test is sufficient.
also use a consistent amount of white space to indent the blocks, 3 or 4 spaces are preferable, tabs are error prone as they expand to different amount of white space on different devices, mixing tabs and spaces, as you did is definitely a problem.
Here is a modified version:
def mergeSort(arr):
# no need to use l and h, use len(arr) directly
if len(arr) > 1:
# locate the middle point
mid = len(arr) // 2
# left has the elements before mid
left = arr[:mid]
# right has the elements from mid to the end
right = arr[mid:]
# sort the slices
left = mergeSort(left)
right = mergeSort(right)
# merge the slices into a new array and return it
return merge(left, right)
else:
# return the original array (should actually return a copy)
return arr
def merge(l, r):
subarr = []
lc = 0
rc = 0
# phase1: merge the arrays
while lc < len(l) and rc < len(r):
if l[lc] <= r[rc]:
subarr.append(l[lc])
lc += 1
else:
subarr.append(r[rc])
rc += 1
# phase2: copy remaining elements from l
while lc < len(l):
subarr.append(l[lc])
lc += 1
# phase3: copy remaining elements from r
while rc < len(r):
subarr.append(r[rc])
rc += 1
# return the merged array
return subarr
a = [10, 5, 2, 20, -50, 30]
print(mergeSort(a))

QuickSort - Median Three

I am working on the QuickSort - Median Three Algorithm.
I have no problem with the first and last element sorting. But, when comes to the Median-three, I am slightly confused. I hope someone could help me on this.
Would be appreciate if someone could provide me some pseudocode?
My understanding is to get the middle index by doing this. (start + end) / 2 , then swap the middle pivot value to the first value, after all these done it should goes well with the normal quick sort ( partitioning and sorting).
Somehow, I couldn't get it works. Please help!
#Array Swap function
def swap(A,i,k):
temp=A[i]
A[i]=A[k]
A[k]=temp
# Get Middle pivot function
def middle(lista):
if len(lista) % 2 == 0:
result= len(lista) // 2 - 1
else:
result = len(lista) // 2
return result
def median(lista):
if len(lista) % 2 == 0:
return sorted(lista)[len(lista) // 2 - 1]
else:
return sorted(lista)[len(lista) // 2]
# Create partition function
def partition(A,start,end):
m = middle(A[start:end+1])
medianThree = [ A[start], A[m], A[end] ]
if A[start] == median(medianThree):
pivot_pos = start
elif A[m] == median(medianThree):
tempList = A[start:end+1]
pivot_pos = middle(A[start:end+1])
swap(A,start,pivot_pos+start)
elif A[end] == median(medianThree):
pivot_pos = end
#pivot = A[pivot_pos]
pivot = pivot_pos
# swap(A,start,end) // This line of code is to switch the first and last element pivot
swap(A,pivot,end)
p = A[pivot]
i = pivot + 1
for j in range(pivot+1,end+1):
if A[j] < p:
swap(A,i,j)
i+=1
swap(A,start,i-1)
return i-1
count = 0
#Quick sort algorithm
def quickSort(A,start,end):
global tot_comparisons
if start < end:
# This to create the partition based on the
pivot_pos = partition(A,start,end)
tot_comparisons += len(A[start:pivot_pos-1]) + len(A[pivot_pos+1:end])
# This to sort the the left partition
quickSort(A,start,pivot_pos -1)
#This to sort the right partition
quickSort(A,pivot_pos+1,end)

Binary Search error when doesn't find a number

I've created a binary search, while looking at the online wiki. I have a class of Athletes that each have a name and number. I'm inputting a file text or csv, doesn't matter - with each of the athletes name's and numbers. My program sorts them first, and then I am trying to add the functionality of searching for a number based off of user input, and displaying who wears that numbered jersey. So my initial post was trying to binary search for people with the same number. As in, if I had Michael Jordan and Lebron James on my list, they both wear 23 - so when my search goes through it would only output 1 (whichever it comes to first). I was looking for how to make my search (below) accept/find multiple occurrences of a number. However, upon further testing, I found that if I input a number not actually in my list it would give me the error: search: stack level too deep which I don't know what that means. I think my search doesn't handle properly if there's no instance of the number, or if the array is 0.
So I was looking for some help to see how I can fix this to work if the number input by a user isn't in the list. So if someone inputs "1000" -- no one has worn that jersey number and should return false. Or something of that sort, break, whatever.
def search(array, num, start = 0, last = nil)
if last == nil
last = array.count - 1
end
mid = (start + last) / 2
if num < array[mid].number
return search(array, num, start, mid - 1)
elsif num > array[mid].number
return search(array, num, mid + 1, last)
else
return mid
end
end
I've now also gotten ==: stack level too deep on the line where if last == nil
This is not the ruby way of doing things. When you have a collection, and you would like to only select some of them based on a certain condition, The ruby approach would be to use Enumerable#select
You would ideally have some array of athletes like so
athletes = [Athlete.new, Athlete.new]
athletes_with_number_23 = athletes.select { |athlete| athlete.number == 23 } #if you want all
first_athlete_wearing_23 = athletes.detect { |athlete| athlete.number == 23 } #if you want only the first one
Disclaimer: this is pseudo code.
I changed my search function:
def search(array, key)
lo = 0
hi = array.length-1
while(lo <= hi)
mid = lo + ((hi-lo)/2)
if array[mid].number == key
return mid
elsif array[mid].number < key
lo = mid + 1
else
hi = mid - 1
end
end
puts "Value not found in array"
end
If I got u right you wanna get ALL athletes with a specific number in a sorted list.
First. Your code is a way too procedural, "This is not the ruby way of doing things.". But I guess it doesn't matter for you.
So, I suggest you find an index of one of the athletes and just walk array left and right from it to collect same-number mans.
Here my procedural version. It uses your function.
def athletes_with_number(athletes, number)
result = []
found_index = search(athletes, number)
return result unless found_index
# walk left
i = found_index
while i >= 0 && athletes[i].number == number
result << athletes[i]
i -= 1
end
# walk right
i = found_index + 1 # athletes[found_index] already added
while i < athletes.size && athletes[i].number == number
result << athletes[i]
i += 1
end
result
end
def search(array, key)
lo = 0
hi = array.length-1
while(lo <= hi)
mid = lo + ((hi-lo)/2)
if array[mid].number == key
return mid
elsif array[mid].number < key
lo = mid + 1
else
hi = mid - 1
end
end
nil
end

Algorithms, DFS

I've written a program to find shortest path in a N*N grid recursively.
def dfs(x,y,Map,p):
N = len(Map)
p += [[x,y]]
if Map[x][y] == 'E':
return p
for i in [[x-1,y],[x+1,y],[x,y-1],[x,y+1]]:
if N > i[0] >= 0 and N > i[1] >= 0 :
if (Map[i[0]][i[1]] == 'P' or Map[i[0]][i[1]] == 'E') and i not in p:
dfs(i[0], i[1], Map,p)
return []
When Map[x][y] = 'E' the recursion don't stop and return p. But it goes till the end. How to correct it and return the path(p).
By the looks of it, the code is prone to loop indefinitely. This is due to lack of checks whether you've entered a node before and moving in all (4) directions from a given node.
To solve it simply, add another array NxN of Boolean values answering the question: visited?. Then update the code to something along the lines:
def dfs(x,y,Map,visited,p):
visited[x,y] = true;
N = len(Map)
(...)
if (Map[i[0]][i[1]] == 'P' or Map[i[0]][i[1]] == 'E')
and i not in p
and visited[i[0], i[1]] == false:
dfs(i[0], i[1], Map,visited,p)

Knapsack 0-1 with fixed quanitity

I'm writing a variation of knapsack 0-1 with multiple constraints. In addition to a weight constraint I also have a quantity constraint, but in this case I want to solve the knapsack problem given that I'm required to have exactly n items in my knapsack, with a weight less than or equal to W. I'm currently implementing a dynamic programming ruby solution for the simple 0-1 case based off of the code at Rosetta Code at http://rosettacode.org/wiki/Knapsack_problem/0-1#Ruby.
What's the best way to implement the fixed quantity constraint?
You could add a third dimension to the table: Number of items. Each item included adds both weight in the weight-dimension, and count in the count-dimension.
def dynamic_programming_knapsack(problem)
num_items = problem.items.size
items = problem.items
max_cost = problem.max_cost
count = problem.count
cost_matrix = zeros(num_items, max_cost+1, count+1)
num_items.times do |i|
(max_cost + 1).times do |j|
(count + 1).times do |k|
if (items[i].cost > j) or (1 > k)
cost_matrix[i][j][k] = cost_matrix[i-1][j][k]
else
cost_matrix[i][j][k] = [
cost_matrix[i-1][j][k],
items[i].value + cost_matrix[i-1][j-items[i].cost][k-1]
].max
end
end
end
end
cost_matrix
end
To find the solution (which items to pick), you need to look at the grid cost_matrix[num_items-1][j][k], for all values of j and k, and find the cell with maximum value.
Once you find the winning cell, you need to trace backwards towards the start (i = j = k = 0). On each cell you examine, you need to determine if item i was used to get here or not.
def get_used_items(problem, cost_matrix)
itemIndex = problem.items.size - 1
currentCost = -1
currentCount = -1
marked = Array.new(cost_matrix.size, 0)
# Locate the cell with the maximum value
bestValue = -1
(problem.max_cost + 1).times do |j|
(problem.count + 1).times do |k|
value = cost_matrix[itemIndex][j][k]
if (bestValue == -1) or (value > bestValue)
currentCost = j
currentCount = k
bestValue = value
end
end
end
# Trace path back to the start
while(itemIndex >= 0 && currentCost >= 0 && currentCount >= 0)
if (itemIndex == 0 && cost_matrix[itemIndex][currentCost][currentCount] > 0) or
(cost_matrix[itemIndex][currentCost][currentCount] != cost_matrix[itemIndex-1][currentCost][currentCount])
marked[itemIndex] = 1
currentCost -= problem.items[itemIndex].cost
currentCount -= 1
end
itemIndex -= 1
end
marked
end

Resources