Need help to create a breadth-first search function - ruby
I’m currently doing Knight’s Travails project.
In this project you need to find the shortest way from A to B for the chess knight.
I don’t know why my program crashes when it comes to breadth-first search function. I cannot catch it with debugger because VScode freezes at reading variable “root” inside knight_moves.
Could you help me find the ussue?
I’ve created the board. It has links to every cell of the board according position of the cell.
I’ve created links between cells with add_edges function. Links are possible ways to move.
So far I’ve got the code below
class Node
attr_reader :pos
attr_accessor :children, :search_info
def initialize (row, column)
#pos = [row, column]
#children = nil
#search_info = Hash.new
end
end
class Board
attr_reader :show
def initialize
create_board
end
def create_board
board = []
8.times do |x|
board<<[x]
end
board.each_with_index do |item, index|
8.times do |x|
board[index] << x unless x == index
end
end
board.each do |x|
x.sort!
end
#board = board
end
def show
#board
end
def fill_with_nodes
#board.each_with_index do |item, index|
item.map! {|column| Node.new(index,column)}
end
end
def add_edges
#board.each_with_index do |row, index|
row.each do |node|
node.children = []
node.children = node.children << #board[node.pos[0]-2][node.pos[1]-1] if (0..7).include?(node.pos[0]-2) && (0..7).include?(node.pos[1]-1)
node.children = node.children << #board[node.pos[0]-2][node.pos[1]+1] if (0..7).include?(node.pos[0]-2) && (0..7).include?(node.pos[1]+1)
node.children = node.children << #board[node.pos[0]+2][node.pos[1]-1] if (0..7).include?(node.pos[0]+2) && (0..7).include?(node.pos[1]-1)
node.children = node.children << #board[node.pos[0]+2][node.pos[1]+1] if (0..7).include?(node.pos[0]+2) && (0..7).include?(node.pos[1]+1)
node.children = node.children << #board[node.pos[0]-1][node.pos[1]-2] if (0..7).include?(node.pos[0]-1) && (0..7).include?(node.pos[1]-2)
node.children = node.children << #board[node.pos[0]+1][node.pos[1]-2] if (0..7).include?(node.pos[0]+1) && (0..7).include?(node.pos[1]-2)
node.children = node.children << #board[node.pos[0]-1][node.pos[1]+2] if (0..7).include?(node.pos[0]-1) && (0..7).include?(node.pos[1]+2)
node.children = node.children << #board[node.pos[0]+1][node.pos[1]+2] if (0..7).include?(node.pos[0]+1) && (0..7).include?(node.pos[1]+2)
end
end
end
def cell (row, column)
#board[row][column]
end
def knight_moves (start, finish)
raise StandardError.new("Invalid start") unless (0..7).include?(start[0]) || (0..7).include?(start[1])
raise StandardError.new("Invalid finish") unless (0..7).include?(finish[0]) || (0..7).include?(finish[1])
queue = []
root = #board[finish[0]][finish[1]]
root.search_info[:distanse] = 0
queue << root
until queue.empty?
node = queue.shift
break if node.pos == [start[0],start[1]]
node.children.each do |child|
unless child.search_info[:distanse]
child.search_info[:distanse] = node.search_info[:distanse] + 1
child.search_info[:predecessor] = node
queue << child
end
end
end
end
end
#This part is for testing
puts a = Board.new
puts a.show.to_s
a.fill_with_nodes
puts a.show.to_s
a.add_edges
a.knight_moves([0,0], [0,1])
def show_cell(board,row, column)
puts ""
puts board.cell(row,column).pos.to_s, board.cell(row,column).children.map {|child| child.pos}.to_s ,board.cell(row,column).search_info.to_s
end
show_cell(a,2,2)
Edit: I've found that line "child.search_info[:predecessor] = node" crashes the programm. And if I use #variable to store "predecessor" instead of hash the programm runs. I don't know why though. What's the reason?
As for me, the main issue with the code is its unnecessary ("incidental") complexity.
Yes, the task you're solving can be reduced to a graph traversal problem, but it doesn't mean you must represent the graph explicitly. For this particular task - where all the possible moves from the arbitrary cell are well-defined and the board itself is limited - you can easily calculate the graph edges on the fly (and without all this additional machinery that makes your code so hard to reason about - even for you). Explicit representation of the board looks redundant too (again, for this particular task).
Taking all this into account, the solution might be as simple as:
class Knight
def initialize
#knight_moves = [[-2, -1], [-2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2], [2, -1], [2, 1]]
end
def move(start, stop)
visited = {}
queue = [[stop, nil]]
while queue.any?
current_cell, next_cell = queue.shift
next if visited.has_key?(current_cell)
visited[current_cell] = next_cell
return build_path(start, stop, visited) if current_cell == start
possible_moves(current_cell).each do |next_move|
queue << [next_move, current_cell] unless visited.has_key?(next_move)
end
end
end
private
def possible_moves(cell)
#knight_moves.
map { |x, y| [cell.first + x, cell.last + y] }.
select(&method(:valid_move?))
end
def build_path(start, stop, visited)
path = [start]
while next_cell = visited[path.last]
path << next_cell
end
path.last == stop ? path : nil
end
def valid_move?(cell)
cell.all? { |n| n >= 0 && n <= 7 }
end
end
knight = Knight.new
knight.move [0,0], [0,1] #=> [[0, 0], [2, 1], [1, 3], [0, 1]]
Related
How do I fix a problem to call a function in Ruby?
I'm trying to use some ruby code that I've found in Github. I've downloaded the code and did the necessary imports the "requires" and tried to run it as it is described in the readme file on github repository. The code is the following: In the file pcset_test.rb the code is the following: require './pcset.rb' require 'test/unit' # # When possible, test cases are adapted from # Introduction to Post-Tonal Theory by Joseph N. Straus, # unless obvious or otherwise noted. # class PCSetTest < Test::Unit::TestCase def test_init #assert_raise(ArgumentError) {PCSet.new []} assert_raise(ArgumentError) {PCSet.new [1, 2, 3, 'string']} assert_raise(ArgumentError) {PCSet.new "string"} assert_raise(ArgumentError) {PCSet.new [1, 2, 3.6, 4]} assert_equal([0, 1, 2, 9], PCSet.new([0, 1, 2, 33, 13]).pitches) assert_equal([3, 2, 1, 11, 10, 0], PCSet.new_from_string('321bac').pitches) assert_equal([0,2,4,5,7,11,9], PCSet.new([12,2,4,5,7,11,9]).pitches) assert_nothing_raised() {PCSet.new []} end def test_inversion end def test_transposition end def test_multiplication end # # set normal prime forte # # 0,2,4,7,8,11 7,8,11,0,2,4 0,1,4,5,7,9 6-31 # 0,1,2,4,5,7,11 11,0,1,2,4,5,7 0,1,2,3,5,6,8 7-Z36 # 0,1,3,5,6,7,9,10,11 5,6,7,9,10,11,0,1,3 0,1,2,3,4,6,7,8,10 9-8 # def test_normal_form testPC = PCSet.new [0,4,8,9,11] assert_kind_of(PCSet, testPC.normal_form) assert_equal([8,9,11,0,4], testPC.normal_form.pitches) assert_equal([10,1,4,6], PCSet.new([1,6,4,10]).normal_form.pitches) assert_equal([2,4,8,10], PCSet.new([10,8,4,2]).normal_form.pitches) assert_equal([7,8,11,0,2,4], PCSet.new([0,2,4,7,8,11]).normal_form.pitches) assert_equal([11,0,1,2,4,5,7], PCSet.new([0,1,2,4,5,7,11]).normal_form.pitches) assert_equal([5,6,7,9,10,11,0,1,3], PCSet.new([0,1,3,5,6,7,9,10,11]).normal_form.pitches) end def test_prime_form assert_equal([0,1,2,6], PCSet.new([5,6,1,7]).prime.pitches) assert_equal([0,1,4], PCSet.new([2,5,6]).prime.pitches) assert_equal([0,1,4,5,7,9], PCSet.new([0,2,4,7,8,11]).prime.pitches) assert_equal([0,1,2,3,5,6,8], PCSet.new([0,1,2,4,5,7,11]).prime.pitches) assert_equal([0,1,2,3,4,6,7,8,10], PCSet.new([0,1,3,5,6,7,9,10,11]).prime.pitches) end def test_set_class testPcs = PCSet.new([2,5,6]) testPrime = testPcs.prime assert_equal([ [2,5,6], [3,6,7], [4,7,8], [5,8,9], [6,9,10], [7,10,11], [8,11,0],[9,0,1], [10,1,2],[11,2,3],[0,3,4], [1,4,5], [6,7,10],[7,8,11],[8,9,0], [9,10,1],[10,11,2],[11,0,3], [0,1,4], [1,2,5], [2,3,6], [3,4,7], [4,5,8], [5,6,9] ].sort, PCSet.new([2,5,6]).set_class.map{|x| x.pitches}) assert_equal(testPcs.set_class.map{|x| x.pitches}, testPrime.set_class.map{|x| x.pitches}) end def test_interval_vector assert_equal([2,1,2,1,0,0], PCSet.new([0,1,3,4]).interval_vector) assert_equal([2,5,4,3,6,1], PCSet.new([0,1,3,5,6,8,10]).interval_vector) assert_equal([0,6,0,6,0,3], PCSet.new([0,2,4,6,8,10]).interval_vector) end def test_complement assert_equal([6,7,8,9,10,11], PCSet.new([0,1,2,3,4,5]).complement.pitches) assert_equal([3,4,5], PCSet.new([0,1,2], 6).complement.pitches) end # # Test values from (Morris 1991), pages 105-111 # Citation: # Morris. Class Notes for Atonal Music Theory # Lebanon, NH. Frog Peak Music, 1991. # def test_invariance_vector assert_equal([1,0,0,0,5,6,5,5],PCSet.new([0,2,5]).invariance_vector) assert_equal([2,2,2,2,6,6,6,6],PCSet.new([0,1,6,7]).invariance_vector) assert_equal([6,6,6,6,6,6,6,6],PCSet.new([0,2,4,6,8,10]).invariance_vector) assert_equal([1,0,0,0,0,0,0,0],PCSet.new([0,1,2,3,4,5,8]).invariance_vector) assert_equal([1,0,0,1,0,0,0,0],PCSet.new([0,1,2,3,5,6,8]).invariance_vector) assert_equal([12,12,12,12,0,0,0,0],PCSet.new([0,1,2,3,4,5,6,7,8,9,10,11]).invariance_vector) end # # Test values from (Huron 1994). Huron rounds, thus the 0.01 margin of error. # Citation: # Huron. Interval-Class Content in Equally Tempered Pitch-Class Sets: # Common Scales Exhibit Optimum Tonal Consonance. # Music Perception (1994) vol. 11 (3) pp. 289-305 # def test_huron h1 = PCSet.new([0,1,2,3,4,5,6,7,8,9,10,11]).huron assert_in_delta(-0.2, h1[0], 0.01) assert_in_delta(0.21, h1[1], 0.01) h2 = PCSet.new([0,2,4,5,7,9,11]).huron assert_in_delta(4.76, h2[0], 0.01) assert_in_delta(0.62, h2[1], 0.01) end def test_coherence end end And in the file pcset.rb the folloing code: # # => PCSet Class for Ruby # => Beau Sievers # => Hanover, Fall 2008. # # # TODO: Make this a module to avoid namespace collisions. # Lilypond and MusicXML output # include Math def choose(n, k) return [[]] if n.nil? || n.empty? && k == 0 return [] if n.nil? || n.empty? && k > 0 return [[]] if n.size > 0 && k == 0 c2 = n.clone c2.pop new_element = n.clone.pop choose(c2, k) + append_all(choose(c2, k-1), new_element) end def append_all(lists, element) lists.map { |l| l << element } end def array_to_binary(array) array.inject(0) {|sum, n| sum + 2**n} end # the following method is horrifically inelegant # but avoids monkey-patching. # TODO: do this right, incl. error checking def pearsons(x, y) if !x.is_a?(Array) || !y.is_a?(Array) then raise StandardError, "x and y must be arrays", caller end if x.size != y.size then raise StandardError, "x and y must be same size", caller end sum_x = x.inject(0) {|sum, n| sum + n} sum_y = y.inject(0) {|sum, n| sum + n} sum_square_x = x.inject(0) {|sum, n| sum + n * n} sum_square_y = y.inject(0) {|sum, n| sum + n * n} xy = [] x.zip(y) {|a, b| xy.push(a * b)} sum_xy = xy.inject(0) {|sum, n| sum + n} num = sum_xy - ((sum_x * sum_y)/x.size) den = Math.sqrt((sum_square_x - ((sum_x*sum_x)/x.size)) * (sum_square_y - ((sum_y*sum_y)/x.size))) (num/den) end class PCSet include Comparable attr_reader :pitches, :base, :input def initialize(pcarray, base = 12) if pcarray.instance_of?(Array) && pcarray.all?{|pc| pc.instance_of?(Fixnum)} #base, #input = base, pcarray #pitches = pcarray.map{ |x| x % #base }.uniq else raise ArgumentError, "Improperly formatted PC array", caller end end def PCSet.new_from_string(pcstring, base = 12) if base > 36 then raise StandardError, "Use PCSet.new to create pcsets with a base larger than 36", caller end pcarray = [] pcstring.downcase.split(//).each do |c| if c <= 'z' and c >= '0' then pcarray.push(c.to_i(36)) end end PCSet.new pcarray, base end def <=>(pcs) #pitches <=> pcs.pitches end def [](index) #pitches[index] end # Intersection def &(other) PCSet.new #pitches & other.pitches end # Union def |(other) PCSet.new #pitches | other.pitches end def inspect #pitches.inspect end def length #pitches.length end def invert(axis = 0) PCSet.new #pitches.map {|x| (axis-x) % #base} end def invert!(axis = 0) #pitches.map! {|x| (axis-x) % #base} end def transpose(interval) PCSet.new #pitches.map {|x| (x + interval) % #base} end def transpose!(interval) #pitches.map! {|x| (x + interval) % #base} end def multiply(m = 5) PCSet.new #pitches.map {|x| (x * m) % #base} end def multiply!(m = 5) #pitches.map! {|x| (x * m) % #base} end def zero transpose(-1 * #pitches[0]) end def zero! transpose!(-1 * #pitches[0]) end def transpositions (0..(#base-1)).to_a.map{|x| #pitches.map {|y| (y + x) % #base}}.sort.map {|x| PCSet.new x} end def transpositions_and_inversions(axis = 0) transpositions + invert(axis).transpositions end # # Normal form after Straus. Morris and AthenaCL do this differently. # def normal_form tempar = #pitches.sort arar = [] # [[1,4,7,8,10],[4,7,8,10,1], etc.] get each cyclic variation tempar.each {arar.push PCSet.new(tempar.unshift(tempar.pop))} most_left_compact(arar) end def normal_form! #pitches = normal_form.pitches end def is_normal_form? self.pitches == self.normal_form.pitches end def set_class transpositions_and_inversions.map{|pcs| pcs.normal_form}.sort end def prime most_left_compact([normal_form.zero, invert.normal_form.zero]) end def prime! self.pitches = self.prime.pitches end def is_prime? self.pitches == self.prime.pitches end def complement new_pitches = [] #base.times do |p| if !#pitches.include? p then new_pitches.push p end end PCSet.new new_pitches end def full_interval_vector pairs = choose(#pitches, 2) # choose every pc pair intervals = pairs.map {|x| (x[1] - x[0]) % #base} # calculate every interval i_vector = Array.new(#base-1).fill(0) intervals.each {|x| i_vector[x-1] += 1} # count the intervals i_vector end def interval_vector i_vector = full_interval_vector (0..((#base-1)/2)-1).each {|x| i_vector[x] += i_vector.pop} i_vector end # # Morris's invariance vector # def invariance_vector(m = 5) t = transpositions.map!{|pcs| self & pcs} ti = invert.transpositions.map!{|pcs| self & pcs} tm = multiply(m).transpositions.map!{|pcs| self & pcs} tmi = invert.multiply(m).transpositions.map!{|pcs| self & pcs} tc = complement.transpositions.map!{|pcs| self & pcs} tic = complement.invert.transpositions.map!{|pcs| self & pcs} tmc = complement.multiply(m).transpositions.map!{|pcs| self & pcs} tmic = complement.invert.multiply(m).transpositions.map!{|pcs| self & pcs} [t, ti, tm, tmi, tc, tic, tmc, tmic].map{|x| x.reject{|pcs| pcs.pitches != #pitches}.length} end # Huron's aggregate dyadic consonance measure. # Huron. Interval-Class Content in Equally Tempered Pitch-Class Sets: # Common Scales Exhibit Optimum Tonal Consonance. # Music Perception (1994) vol. 11 (3) pp. 289-305 def huron if #base != 12 then raise StandardError, "PCSet.huron only makes sense for mod 12 pcsets", caller end # m2/M7 M2/m7 m3/M6 M3/m6 P4/P5 A4/d5 huron_table = [-1.428, -0.582, 0.594, 0.386, 1.240, -0.453] interval_consonance = [] interval_vector.zip(huron_table) {|x, y| interval_consonance.push(x * y) } aggregate_dyadic_consonance = interval_consonance.inject {|sum, n| sum + n} [aggregate_dyadic_consonance, pearsons(interval_vector, huron_table)] end # # Balzano's vector of relations. Citation for all Balzano methods: # # Balzano. "The Pitch Set as a Level of Description for Studying Musical # Pitch Perception" in Music, Mind, and Brain ed. Clynes. Plenum Press. 1982. # def vector_of_relations (0..length-1).to_a.map do |i| (0..length-1).to_a.map do |j| (#pitches[(i + j) % length] - #pitches[i]) % #base end end end # # Checks if the set satisfies Balzano's uniqueness. # def is_unique? vector_of_relations.uniq.size == vector_of_relations.size end # # Checks if the set satisfies Balzano's scalestep-semitone coherence. # For all s[i] and s[i1]: # j < k => v[i][j] < v[i1][k] # Where j and k are scalestep-counting indices. # And unless v[i][j] == 6 (a tritone), in which case the strict inequality is relaxed. # def is_coherent? v = vector_of_relations truth_array = [] all_pair_indices = choose((0..length-1).to_a, 2) all_pair_indices.each do |i, i1| all_pair_indices.each do |j, k| if v[i][j] == 6 truth_array.push(v[i][j] <= v[i1][k]) else truth_array.push(v[i][j] < v[i1][k]) end if v[i1][j] == 6 truth_array.push(v[i1][j] <= v[i][k]) else truth_array.push(v[i1][j] < v[i][k]) end end end !truth_array.include?(false) end # # Strict Balzano coherence, no inequality relaxation for tritones. # def is_strictly_coherent? v = vector_of_relations truth_array = [] all_pair_indices = choose((0..length-1).to_a, 2) all_pair_indices.each do |i, i1| all_pair_indices.each do |j, k| truth_array.push(v[i][j] < v[i1][k]) truth_array.push(v[i1][j] < v[i][k]) end end !truth_array.include?(false) end def notes(middle_c = 0) noteArray = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'] if #base != 12 then raise StandardError, "PCSet.notes only makes sense for mod 12 pcsets", caller end out_string = String.new transpose(-middle_c).pitches.each do |p| out_string += noteArray[p] + ", " end out_string.chop.chop end def info print "modulo: #{#base}\n" print "raw input: #{#input.inspect}\n" print "pitch set: #{#pitches.inspect}\n" print "notes: #{notes}\n" print "normal: #{normal_form.inspect}\n" print "prime: #{prime.inspect}\n" print "interval vector: #{interval_vector.inspect}\n" print "invariance vector: #{invariance_vector.inspect}\n" print "huron ADC: #{huron[0]} pearsons: #{huron[1]}\n" print "balzano coherence: " if is_strictly_coherent? print "strictly coherent\n" elsif is_coherent? print "coherent\n" else print "false\n" end end # def lilypond # # end # # def musicXML # # end ############################################################################### private # # Convert every pitch array to a binary representation, e.g.: # [0,2,4,8,10] -> 010100010101 # 2^n: BA9876543210 # The smallest binary number is the most left-compact. # def most_left_compact(pcset_array) if !pcset_array.all? {|pcs| pcs.length == pcset_array[0].length} raise ArgumentError, "PCSet.most_left_compact: All PCSets must be of same cardinality", caller end zeroed_pitch_arrays = pcset_array.map {|pcs| pcs.zero.pitches} binaries = zeroed_pitch_arrays.map {|array| array_to_binary(array)} winners = [] binaries.each_with_index do |num, i| if num == binaries.min then winners.push(pcset_array[i]) end end winners.sort[0] end end I'm calling them as follows: > my_pcset = PCSet.new([0,2,4,6,8,10]) > my_pcset2 = PCSet.new([1,5,9]) It shoud return: > my_pcset = PCSet.new([0,2,4,6,8,10]) => [0, 2, 4, 6, 8, 10] > my_pcset2 = PCSet.new([1,5,9]) => [1, 5, 9] But is returning nothing. The code is available on github Thanks
Try this in terminal: irb -r ./path_to_directory/pcset.rb and then initialize the objects.
I think the documentation for the repo is bad as it does not explain how you should be running this. The result of my_pcset = PCSet.new([0,2,4,6,8,10]) should set my_pcset to an instance of a PCSet not an array, so these lines from the README file are confusing at best. 3. How to use it Make new PCSets: my_pcset = PCSet.new([0,2,4,6,8,10]) => [0, 2, 4, 6, 8, 10] my_pcset2 = PCSet.new([1,5,9]) => [1, 5, 9] Looking at the code, I see inspect has been delegated to #pitches def inspect #pitches.inspect end I think if you inspect my_pcset you will get the expected result. my_pcset = PCSet.new([0,2,4,6,8,10]) p my_pcset # will print [0, 2, 4, 6, 8, 10] or `my_pcset.inspect` will return what you are expecting.
Change position according to facing in Ruby
I need to implement a move method that change position according to facing, position is a [x,y] and I thinking that if move to south is y+1, to north y-1, to east x-1 and to west x+1. this movements are into a matrix. This is my code. Thank you so much for your help! # Models the Robot behavior for the game class Robot FACINGS = [:south, :east, :north, :west] def initialize(attr = {}) #position = attr[:position] || [1, 1] # #move = attr[:move] #facing_index = facing_index(attr[:facing]) || 0 # south #facing = facing # #errors = end def position #position end def move end def facing #facing = FACINGS[#facing_index] end def errors end private def facing_index(facing) facing if facing.is_a? Integer FACINGS.index(facing&.to_sym) end end
DIRECTION_NUMBER = { :north=>0, :east=>1, :south=>2, :west=>3 } #left = { :north=>:west, :west=>:south, :south=>:east, :east=>:north } #right = #left.invert #=> {:west=>:north, :south=>:west, :east=>:south, :north=>:east} def turn_left #facing = #left[#facing] end def turn_right #facing = #right[#facing] end def move(direction) x, y = #location #location = case direction when :north [x,y+1] when :east [x+1,y] when :south [x,y-1] else [x-1,y] end update_facing(direction) end private def update_facing(direction) change = (DIRECTION_NUMBER[direction] - DIRECTION_NUMBER[#facing]) % 4 case change when 1 turn_right when 2 turn_right; turn_right when 3 turn_left end end #location = [3, 3] #facing = :east move(:south) #location #=> [3, 2] #facing #=> :south move(:north) #location #=> [3, 3] #facing #=> :north move(:west) #location #=> [2, 3] #facing #=> :west move(:east) #location #=> [3, 3] #facing #=> :east
Add MOVES which says how to move based on how you're facing. MOVES = { north: [0, 1], south: [0, -1], east: [1, 0], west: [-1,0] } def move move = MOVES.fetch(#facing) #position[0] += move[0] #position[1] += move[1] end MOVES.fetch(#facing) is used instead of MOVES[#facing] so an error will be raised if there is no move for that facing. You could also do this with a case statement, but this keeps move simple and data driven. You can add more directions like northeast: [1,1]. And if you make this an instance variable, you can customize how individual robots move. # Define `moves` and `moves=` to get and set `#moves` attr_accessor :moves def initialize(attr = {}) ... # Initialize `moves` with either Robot.new(moves: {...}) # or the default MOVES #moves ||= attr[:moves] || MOVES ... end def move move = moves.fetch(#facing) #position[0] += move[0] #position[1] += move[1] end
FACINGS enum example. module FACINGS NORTH = [0, 1] SOURTH = [0, -1] EAST = [1, 0] WEST = [-1,0] end class Robot attr_reader :position def initialize(attr = {}) #position = attr[:position] || [1, 1] end def move(facings) #position[0] += facings[0] #position[1] += facings[1] end end r = Robot.new r.move(FACINGS::NORTH) r.move(FACINGS::SOURTH) r.move(FACINGS::WEST) r.move(FACINGS::EAST)
Adding value from parent node
I am trying to implement the A* Algorithm to find the shortest path from A to B. I am using a linked list with nodes to find the best path. One of the values I need to calculate is g, which is the cost of movement from the starting point to the current node. Moving horizontal or vertical cost 10, and moving diagonal costs 14. For example, 11 22 33 44 55 66 Moving from 11 to 22 cost 10. From 22 to 66 cost 14, so moving from 11 to 66 cost 24 total according to that particular path. In my node class, I have a method that calculate g from the previous node to the current node, in the previous example, 22 to 66. In order to find the total g cost, I added the g value from the parent's node. However, this is extremely expensive and takes forever to run, especially when the number of possible paths is huge. What can I do to speed up this algorithm? Is there a better way to calculate the g value? Here is the relevant code from my Node class. class Node attr_accessor :position, :g, :h, :parent def initialize(position, parent = nil) #position = position #parent = parent if parent.nil? #g = 0 else #g = calc_g + #parent.g #<= this line costs the issue. end end def calc_h(end_pos) (#position.first - end_pos.first).abs + (#position.last - end_pos.last).abs end def calc_g row_diff = (#position.first - self.parent.position.first).abs col_diff = (#position.last - self.parent.position.last).abs if [row_diff, col_diff] == [1,1] g = 14 else g = 10 end end end Here is my MazeSolver class. require_relative 'node' class MazeSolver attr_accessor :open_list, :closed_list, :maze, :start_node, :end_node, :current_node def initialize(file_name) #open_list = [] #closed_list = {} #maze = create_maze_array(file_name) #start_node = Node.new(find_point("S")) #end_node = Node.new(find_point("E")) #current_node = #start_node end def create_maze_array(file_name) maze = File.readlines(file_name).map(&:chomp) maze.map! do |line| line.split('') end maze end def find_point(sym) #maze.each_with_index do |line, row| line.each_with_index do |el, col| return [row, col] if el == sym end end end def find_neighbors(position) row, col = position (row-1..row+1).each do |r| (col-1..col+1).each do |c| next if #closed_list.values.include?([r, c]) unless #maze[r][c] == '*' #open_list << Node.new([r, c], #current_node) end end end #open_list end def solved? #current_node.position == #end_node.position end def solve until solved? #closed_list[#current_node] = #current_node.position find_neighbors(#current_node.position) #current_node = lowest_f(#open_list) #open_list.delete(#current_node) end path = get_path(#current_node) display_route(path) end # determine the lowest f score node def lowest_f(nodes) f_score = {} nodes.each do |node| f_score[node] = node.g + node.calc_h(#end_node.position) end f_score.sort_by { |node, score| score }.first.first end # create path array def get_path(node) path = [] until node.position == #start_node.position path << node.position node = node.parent end path end # display maze without route and maze with route def display_route(path) display_maze path.each do |move| unless #maze[move.first][move.last] == 'E' #maze[move.first][move.last] = 'X' end end puts display_maze end # display the maze as it is. def display_maze #maze.each do |line| puts line.join end end end if __FILE__ == $PROGRAM_NAME puts "What is the name of the maze file?" begin MazeSolver.new(gets.chomp).solve rescue puts "File does not exist. Try Again" retry end end Here is the link to repl with example mazes that I worked with. maze1.txt is the one that takes a long time to run.
Ruby Program to solve Circular Primes below number x
I'm working on project Euler #35. I am getting the wrong number returned and I can't find where I have done wrong! def is_prime?(num) (2..Math.sqrt(num)).each { |i| return false if num % i == 0} true end def is_circular?(num) len = num.to_s.length return true if len == 1 (len - 1).times do new_n = cycle(num) break unless is_prime?(new_n) end end def cycle(num) ary = num.to_s.split("") return ary.rotate!.join.to_i end def how_many circulars = [] (2..1000000).each do |num| if is_prime?(num) && is_circular?(num) circulars << num end end p circulars.count end how_many #=> 14426 The returned number is '14426'. I am only returning the circular primes, supposedly the correct answer is '55'
I have edited your code with few fixes in Ruby way. Your mistake was including corect set of [a, b, c] three times to count, instead of counting them as a one circular prime number. Your answer was correct, while 55 is the number of unique sets. require 'prime' class Euler35 def is_circular?(num) circulars_for(num).all?{ |el| ::Prime.instance.prime?(el) } end def circulars_for(a) a.to_s.split("").length.times.map{|el| a.to_s.split("").rotate(el).join.to_i } end def how_many circulars = [] ::Prime.each(1_000_000) do |num| continue if circulars.include?(num) if is_circular?(num) circulars << circulars_for(num) end end circulars.count end end puts Euler35.new.how_many # => 55
How to replace element in multidimensional array in ruby
I am having some difficulty with my code and I am hoping for some insight: I have a 2d array for a board and I am attempting to replace a number with "X" when called, but am having struggles achieving this. class BingoBoard def initialize #bingo_board = Array.new(5) {Array (5.times.map{rand(1..100)})} #bingo_board[2][2] = 'X' end def new_board #bingo_board.each{|row| p row} end def ball #letter = ["B","I","N","G","O"].shuffle.first #ball = rand(1..100) puts "The ball is #{#letter}#{#ball}" end def verify #ball #bingo_board.each{|row| p row} #bingo_board.collect! { |i| (i == #ball) ? "X" : i} end end newgame = BingoBoard.new puts newgame.ball newgame.verify I am aware that when verify is called it is iterating only through the array1, but I am unsure how to go about making the fix. Any help appreciated.
This is the root of the problem: #bingo_board.collect! { |i| (i == #ball) ? "X" : i} In this example, i is an array. So what you might want to do is to replace your code with something like: #bingo_board.collect! do |i| # you're iterating over a double array here if i.include?(#ball) # i is a single array, so we're checking if the ball number is included i[i.index(#ball)] = 'X'; i # find the index of the included element, replace with X else i end end Or if you prefer one-liner: #bingo_board.collect! { |i| i.include?(#ball) ? (i[i.index(#ball)] = 'X'; i) : i } Be aware that this is going to only replace the first occurrence of the element. So, say if your ball is 10, and you have: [8, 9, 9, 10, 10] you will get: [8, 9, 9, "X", 10] If you want ALL of the 10s to be replaced, then do something like: #bingo_board.collect! do |i| if i.include?(#ball) i.collect! { |x| x == #ball ? 'X' : x } else i end end