How to return an array from a recursive function - ruby

I have a board (multidimensional array). You start from the upper left corner and can move right or down. The base case is the lower right corner.
I made a function that looks for all possible moves and their values within the board. When the base case is reached, the moves and values are stored as an array ARR_SUMNUM.
ARR_SUMNUM = []
def arr_all_moves(ary,a=0,b=0,sum_num=0,directions="")
rows = ary.length
cols = ary[0].length
goal = rows-1 + cols-1
curr_num = ary[a][b]
sum_num += curr_num
if [a,b] == [rows-1,cols-1]
ARR_SUMNUM.push([sum_num, directions])
return
end
if a == rows -1
return arr_all_moves(ary,a,b+1,sum_num,directions+="right ")
elsif b == cols -1
return arr_all_moves(ary,a+1,b,sum_num,directions+="down ")
end
arr_all_moves(ary,a,b+1,sum_num,directions+="right ")
directions.chomp!("right ") ##### I realize this was the source of all my issues
arr_all_moves(ary,a+1,b,sum_num,directions+="down ")
return ARR_SUMNUM
end
board = [ [1,3,4],
[5,6,999],
[8,9,10],
[11,12,13],
[2000,42,13]
]
p arr_all_moves(board)
How do I get this function to work without a constant like ARR_SUMNUM and use a local variable instead? I tried to use arr_sumnum instead, but I get an empty array.

The simplest way might just be to pass the array down the line with each call by setting it as one of the method arguments.
def arr_all_moves(ary, a=0, b=0, sum_num=0, directions=" ", arr_sum_num=[])
rows = ary.length
cols = ary[0].length
goal = rows-1 + cols-1
curr_num = ary[a][b]
sum_num += curr_num
if [a,b] == [rows-1,cols-1]
arr_sum_num.push([sum_num, directions])
return
end
if a == rows -1
return arr_all_moves(ary,a,b+1,sum_num,directions+="right ", arr_sum_num)
elsif b == cols -1
return arr_all_moves(ary,a+1,b,sum_num,directions+="left ", arr_sum_num)
end
arr_all_moves(ary,a,b+1,sum_num,directions+="right ", arr_sum_num)
arr_all_moves(ary,a+1,b,sum_num,directions+="left ", arr_sum_num)
return arr_sum_num
end

There's two ways, one was already mentioned that involved propagating the array down to the recursive function calls. Another would be to actually propagate the array upward and add them in the calling function.
def arr_all_moves(ary,a=0,b=0,sum_num=0,directions=" ")
rows = ary.length
cols = ary[0].length
goal = rows-1 + cols-1
curr_num = ary[a][b]
sum_num += curr_num
if [a,b] == [rows-1,cols-1]
return [[sum_num, directions]]
end
if a == rows -1
return arr_all_moves(ary,a,b+1,sum_num,directions+="right ")
elsif b == cols -1
return arr_all_moves(ary,a+1,b,sum_num,directions+="left ")
end
return arr_all_moves(ary,a,b+1,sum_num,directions+="right ") +
arr_all_moves(ary,a+1,b,sum_num,directions+="left ")
end
board = [ [1,3,4],
[5,6,999],
[8,9,10],
[11,12,13],
[2000,42,13]
]
p arr_all_moves(board)

Here is one possible solution, and you should use down instead of left as we are traversing right or down the 2-D array to reach to lower right corner.
def arr_all_moves(ary, a=0, b=0, sum_num=0, directions=" ")
rows = ary.length
cols = ary[0].length
sum_num += ary[a][b]
if [a,b] == [rows-1,cols-1]
[] << [sum_num, directions]
elsif a == rows-1
arr_all_moves(ary, a, b+1, sum_num, directions+="right ")
elsif b == cols-1
arr_all_moves(ary, a+1, b, sum_num, directions+="down ")
else
arr_all_moves(ary, a, b+1, sum_num, directions+="right ") +
arr_all_moves(ary, a+1, b, sum_num, directions+="down ")
end
end
board = [
[1,3,4],
[5,6,999],
[8,9,10],
[11,12,13],
[2000,42,13]
]
p arr_all_moves(board)

Related

Minesweeper game board | ruby

I have written a code for the drawing board for the minesweeper game, can anyone help me to refactor this code more.
Please find my code below
def draw(height, width, mines)
board = Array.new(height) { Array.new(width,0) }
x = Random.rand(height)
y = Random.rand(width)
mines.times do
until board[x][y] != 'x'
x = Random.rand(height)
y = Random.rand(width)
end
board[x][y] = 'x'
end
board.each_with_index do |row, i|
row.each_with_index do |elem, j|
next if board[i][j] == 'x'
count = 0
count += 1 if i+1 < height && board[i+1][j] == 'x'
count += 1 if j+1 < width && board[i][j+1] == 'x'
count += 1 if i-1 >= 0 && board[i-1][j] == 'x'
count += 1 if j-1 >= 0 && board[i][j-1] == 'x'
board[i][j] = count
end
end
board.each do |row|
row.each do |e|
print "#{e} "
end
print "\n"
end
end
draw(4,4,3)
Thanks in advance.
I think you need to check 8 adjacent cells, not 4. Since this is a refactoring, I kept the original behavior.
def draw(height, width, mines)
board = Array.new(height) { Array.new(width, 0) }
mines.times do
x = rand(height)
y = rand(width)
redo if board[x][y] == 'x'
board[x][y] = 'x'
[[x - 1, y], [x + 1, y], [x, y - 1], [x, y + 1]].each do |x, y|
next if x < 0 || x >= height
next if y < 0 || y >= width
next if board[x][y] == 'x'
board[x][y] += 1
end
end
board.each { |row| puts row.join(' ') }
end

Algorithm Challenge number formatting problem

Invoice numbers are numeric only with any number of digits. To format one correctly, group the digits in group of three plus a group of any remainder, but never leave one digit by itself, unless it's a one digit number. Eg these are all correct formatting
123
12-34
6
783-907-23-45
And these are not
123-4
98-456
There's one more catch user input is passed directly to the function and you never know what characters users might type. Ignore any part of the input that is not digit
Invoice.format_number should always return a string
module Invoice
def self.format_number(str)
return ""
end
end
puts Invoice.format_number("ab1234")
What I have tried
1st approach
arr = []
str.chars.each do |elem|
val = elem =~ /\A[-+]?[0-9]*\.?[0-9]+\Z/
arr << elem if val == 0
end
num_of_digits = arr.length
pairs_of_two = 0
pairs_of_three = 0
if num_of_digits > 5
while num_of_digits > 0 do
break if num_of_digits <= 3
if num_of_digits >= 3 && (num_of_digits % 3 == 0 || num_of_digits % 3 == 2)
pairs_of_three += 1
num_of_digits -= 3
elsif num_of_digits % 2 == 0 || num_of_digits % 2 == 1
pairs_of_two += 1
num_of_digits -= 2
end
end
end
2nd approach
arr = []
str.chars.each do |elem|
val = elem =~ /\A[-+]?[0-9]*\.?[0-9]+\Z/
arr << elem if val == 0
end
len = arr.length - 1
if arr.length > 4
str = ""
i = 0
while i < len do
if arr[i..i+3].length == 4
str << arr[i..i+2].join + "-"
i += 3
elsif arr[i..i+2].length == 3
str << arr[i..i+1].join + "-"
i += 2
elsif arr[i..i+1].length == 2
str << arr[i..i+1].join
i += 2
elsif !arr[i].nil?
str << arr[i]
i += 1
end
end
puts str
else
if arr.length <= 3
puts arr.join
else
puts arr[0..1].join + "-" + arr[2..3].join
end
end
But none of them is correct
Here is the function invoice_number in python
def invoice_number(invoice):
s = ''.join(x for x in invoice if x <= '9' and x >= '0')
n = len(s)
if n <= 3:
return s
w = ''
i = 0
while i + 3 <= n:
for j in range(0, 3):
w += s[i + j]
i += 3
w += ('-')
m = n - i
if m == 0: return w[:-1]
if m == 1: return w[:m-3] + '-' + s[-2:]
return w + s[i:]
Testing
print(invoice_number('1234567'))
print(invoice_number('12345678'))
print(invoice_number('abc123456789'))
print(invoice_number('1234abc5678xyz9foobar'))
123-45-67
123-456-78
123-456-789
123-456-789
Eliminating non-digits is easy with re. For your format, the key is to figure our the "right" splitting indices.
Here is a try:
import re
def splits(n, k):
idx = [(i, min(n, i+k)) for i in range(0, n, k)]
if len(idx) > 1:
(a, b), (c, d) = idx[-2:]
if d - c < 2:
idx[-2:] = [(a, b - 1), (c - 1, d)]
return idx
def myformat(s):
s = re.sub(r'[^0-9]+', '', s)
parts = [s[a:b] for a, b in splits(len(s), 3)]
return '-'.join(parts)
Tests:
>>> myformat('123')
123
>>> myformat('1234')
12-34
>>> myformat('6')
6
>>> myformat('7839072345')
783-907-23-45
As the question was asked for ruby, adding solution for ruby. (The inspiration of the code is mostly from #yuri answer)
def format_invoice(invoice)
# only numbers are allowed
invoice = invoice.tr("^0-9","")
#puts invoice
return invoice if(invoice.length <= 3)
formatted_invoice = ''
i = 0
# Loop to divide the invoice in group of 3
while i + 3 <= invoice.length do
for j in 0..2 do
formatted_invoice += invoice[i + j]
end
i += 3
formatted_invoice += ('-')
end
m = invoice.length - i
return formatted_invoice[0..-2] if m == 0
return formatted_invoice[0..m-4] + '-' + invoice[-2..-1] if m == 1
return formatted_invoice + invoice[i..-1]
end
Testing
puts format_invoice('abc1') # 1
puts format_invoice('abc123') # 123
puts format_invoice('abc123A4') # 12-34
puts format_invoice('1234567') # 123-45-67
puts format_invoice('12345678') # 123-456-78
puts format_invoice('abc123456789') # 123-456-789
puts format_invoice('1234a#c5678xyz9foobar') # 123-456-789

How to create a Minimax algorithm comparing arrays

I'm trying to code a "minimax" algorithm for Tic Tac Toe.
Each node of the tree is of the form [nil/Int, String] where the last element is a nine character string describing the board, and the first is an Integer ranking the node, or nil by default.
If the value is nil, it tries to inherit the appropriate value from child nodes.
This is where I get an error, when comparing an array with an array failed.
class Scene_TicTacToe #Script 2/2
def initialize
#Boardstate as a str from top left corner to bottom right corner.
#boardstate = "---------"
#1 = player, -1 = comp
#active_player = 1
end
def wincheck(boardstate=#boardstate)
#should return -1 for loss, 0 for draw, 1 for win
["OOO","XXX"].each do |f|
for i in 0..2
if (boardstate[i]+boardstate[i+3]+boardstate[i+6]).chr == f || boardstate[(3*i)..(3*i)+2] == f
return f == "OOO" ? 1 : -1
end
end
if (boardstate[0]+boardstate[4]+boardstate[8]).chr == f || (boardstate[2]+boardstate[4]+boardstate[6]).chr == f
return f == "OOO" ? 1 : -1
end
end
return 0
end
def computer_play
#Sets depth,and alpha/beta for pruning, so far so good
depth = 3
alpha = -100
beta = 100
##boardstate starts as "---------"
##active_player: 1=player, -1=computer
play(minimax(#boardstate, depth, alpha, beta, #active_player))
end
def play(array)
#Check actual boardside with parameter boardside to see what move has been
#selected and plays that move
for i in 0...array[1].length
if #boardstate[i] != array[1][i]
#color = array[1][i].chr == "X" ? #ai : #player
##cursor.y = (i / 3) * #side
##cursor.x = (i % 3) * #side
##board.bitmap.fill_rect(#cursor.x,#cursor.y,#side,#side,color)
#boardstate = array[1].dup
end
end
end
def minimax(boardstate, depth, alpha, beta, active_player)
#If bottom node reached, returns [boardstate_score, boardstate]
#wincheck returns 1 if player wins, -1 if computer wins, and 0 otherwise
if depth == 0 or wincheck(boardstate) != 0 or (/-/ =~ boardstate) == nil
return [wincheck(boardstate),boardstate]
end
if active_player == 1 #if player's turn
#Gets an array of all the next possible boardstates and return the one with
#the best eval.
child = generate_child(boardstate, active_player)
child.each do |f| #f = [Int/nil, String]
if f[0] == nil
#This should turn all the nil wincheck values to the best value of children nodes
f[0] = minimax(f[1], depth-1, alpha, beta, -active_player).last[0]
end
alpha = [f[0], alpha].max
if beta <= alpha
break
end
end
return child.sort_by{|c| c[0]}
end
if active_player == -1 #if computer's turn
#Same as above but with worst eval.
child = generate_child(boardstate, active_player)
child.each do |f|
if f[0] == nil
f[0] = minimax(f[1], depth-1, alpha, beta, -active_player).first[0]
end
beta = [f[0], beta].min
if beta <= alpha
break
end
end
#Following line raises "comparison of array with array failed" error :
return child.sort_by{|c| c[0]}
end
end
def generate_child(boardstate, active_player)
#returns boardstate string with one X or O more than current boardstate
#and sets nil as a default wincheck value
c = active_player == 1 ? "O" : "X"
a = []
for i in 0...boardstate.length
if boardstate[i].chr == "-"
s = boardstate.dup
s[i]= c
a << [nil, s]
end
end
return a
end
end
Error: comparison of array with array failed

How to improve time complexity of this code piece?

I am having a hard time trying to down down the time. If this can be done in O(n^2) thatd be awesome.
def printDistance(file)
line = file.gets
if line == nil then return end
sz, sx, sy, ex, ey = line.split(/\s/)
#counter = 0
while line = file.gets do
if line[0...4] == "path"
else
x, y, ds, w = line.split(/\s/,4)
x = x.to_i
y = y.to_i
matrix= Array.new
later = Array.new
tmp = Array.new
processing = Array.new
if matrix[y].class != Array
matrix[y] = Array.new
end
if ds == "" #this cell has NO way to get to
matrix[y][x] = nil
else
matrix[y][x] = ds
end
end
end
for y in 0...matrix.length
processing[y] = Array.new
tmp[y] = Array.new
later[y] = Array.new
end
printing = Array.new
counter = 0
sy = sy.to_i
sx = sx.to_i
processing[sy][sx] = matrix[sy][sx]
matrix[sy][sx] = nil
puts "#{counter},(#{sx},#{sy})" #first one
counter += 1
loop do
print "#{counter},"
counter += 1
for y in 0...processing.length
for x in 0...processing[y].length
if processing[y][x].class != nil
tmp[y][x] = processing[y][x]
dirArr = tmp[y][x].to_s.split(//)
dirArr.each { |c|
if c == "u"
newY = y - 1
newX = x
elsif c == "d"
newY = y + 1
newX = x
elsif c == "l"
newY = y
newX = x - 1
else #c == r
newY = y
newX = x + 1
end
if matrix[newY][newX] != nil
tmpStr = "(#{newX},#{newY})"
printing.unshift(tmpStr)
later[newY][newX] = matrix[newY][newX]
matrix[newY][newX] = nil
end
}
end
end
end
printing.sort!
for i in 0...printing.length
print printing[i]
if i < printing.length - 1
print ","
end
end
printing = [] #resetting
puts
for i in 0...later.length
for j in 0...later[i].length
if later[i][j] != nil
processing[i][j] = later[i][j]
end
end
end
break if NotEmpty(matrix) == false
end
end
def NotEmpty (a)
for i in 0...a.length
for j in 0...a[i].length
if a[i][j] != nil
return true
end
end
end
return false
end
Basically this code reads in a file and places it in a 2-d array representing a maze, then based on maze's starting point it will perform a BFS to put all cells in order from closest to the start to the farthest, then print everything out
This code is now O(n^3) but I am trying to shrink it to O(n^2), is there anyway I can traverse a 2-d array and keeping track of the x and y values without using two forloops?
Any help is appreciated!! Thanks!

How do I write a merge sort?

I am trying to implement a merge sort and am getting stack level too deep (SystemStackError) error when I run my code. I am not sure what the issue may be.
def merge_sort(lists)
lists if lists.count == 1
middle = lists[0..(lists.count / 2) - 1 ]
left = lists[0..middle.count - 1]
right = lists[middle.count..lists.count]
x = merge_sort(left)
y = merge_sort(right)
end
merge_sort [1,2,3,4,5,6,7,8]
Any help would be great!
From wikipedia:
def mergesort(list)
return list if list.size <= 1
mid = list.size / 2
left = list[0...mid]
right = list[mid...list.size]
merge(mergesort(left), mergesort(right))
end
def merge(left, right)
sorted = []
until left.empty? || right.empty?
if left.first <= right.first
sorted << left.shift
else
sorted << right.shift
end
end
sorted.concat(left).concat(right)
end
write this
return lists if lists.count == 1
instead of
lists if lists.count == 1
In Ruby, from a method last statement is always returned by default. But if you want to return from the middle of any lines except the last line conditionally, you must need to use return keyword explicitly.
A simplified version with comments
def merge_sort(arr)
# 0. Base case
return arr if arr.length <= 1
# 1. Divide
mid = arr.length / 2
arr0 = merge_sort(arr[0, mid])
arr1 = merge_sort(arr[mid, arr.length])
# 2. Conquer
output = merge(arr0, arr1)
end
def merge(l, r)
output = []
until l.empty? || r.empty?
output << if l.first <= r.first
l.shift
else
r.shift
end
end
# The order of `concat(l)` or `concat(r)` does not matters
output.concat(l).concat(r)
end
https://www.khanacademy.org/computing/computer-science/algorithms/merge-sort/a/divide-and-conquer-algorithms
This is a good way to do it. Tis a bit tricky at first, but stay at it.
def merge_sort list
if list.length <= 1
list
else
mid = (list.length / 2).floor
left = merge_sort(list[0..mid - 1])
right = merge_sort(list[mid..list.length])
merge(left, right)
end
end
def merge(left, right)
if left.empty?
right
elsif right.empty?
left
elsif left.first < right.first
[left.first] + merge(left[1..left.length], right)
else
[right.first] + merge(left, right[1..right.length])
end
end

Resources