Reconstructing the shortest path in a graph in BFS - ruby

I am implementing a function in ruby using BFS, and I would like to know how to print the shortest path in an undirected graph, between a start value and end value
Graph
In this example the graph has ten vertices. Each node represents a vertex and it has an array that stores adjacent nodes (edges).
$ irb
graph.to_s
1. 1 -> [2 3 4 5 8 9 10]
2. 2 -> [1 4 5 6 7 8 10]
3. 3 -> [1 4 6]
4. 4 -> [1 2 3 6 7 8 9 10]
5. 5 -> [1 2 8 9 10]
6. 6 -> [2 3 4 7 8 9 10]
7. 7 -> [2 4 6 8 9]
8. 8 -> [1 2 4 5 6 7 9 10]
9. 9 -> [1 4 5 6 7 8]
10. 10 -> [1 2 4 5 6 8]
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Expected Output
2,1,9 or 2,4,9, etc.
BFS
def bfs_shortest_path(graph, start=2, search=9)
if graph.nodes[start].nil? || graph.nodes[search].nil?
return nil
end
visited = Set.new
search_queue = Queue.new
search_queue.enq(start)
while !search_queue.empty? do
current_node_key = search_queue.deq
current_node = graph.nodes[current_node_key]
visited.add(current_node_key)
if current_node.value == search
return current_node # I would like to return the PATH Eg. 2,1,9
end
adjacent_nodes_array = current_node.adjacent_nodes.map{|x| x.value}
adjacent_nodes_array.each do |value|
if !visited.include?(value)
search_queue.enq(value)
graph.nodes[value].concat_to_path(current_node.path_from_start)
end
end
end
end
Node
class Node
attr_reader :value
attr_reader :adjacent_nodes
def initialize(value)
#value = value
#adjacent_nodes = []
end
def add_edge(node)
#adjacent_nodes.push(node)
end
def to_s
"#{#value} -> [#{#adjacent_nodes.map(&:value).sort.join(" ")}]"
end
end
Graph
class Graph
attr_reader :nodes
def initialize
#nodes = {}
end
def add_node(node)
#nodes[node.value] = node
end
def add_edge(node1,node2)
if #nodes[node1.value].adjacent_nodes.map(&:value).include? (node2.value)
puts "#{node1.value} and #{node2.value} already have an edge"
elsif node1.value == node2.value
puts "node1.value == node2.value"
else
#nodes[node1.value].add_edge(#nodes[node2.value])
#nodes[node2.value].add_edge(#nodes[node1.value])
end
end
def to_s
#nodes.keys.sort.each_with_index do |key,index|
puts "#{index + 1}. #{#nodes[key].to_s}"
end
end
end
Generating a Graph
def generate_random_graph
g = Graph.new
[*1..10].shuffle.each do |node_value|
g.add_node(Node.new(node_value))
end
40.times do
key1 = g.nodes.keys.sample
key2 = g.nodes.keys.sample
g.add_edge(g.nodes[key1],g.nodes[key2])
end
return g
end
Test
graph = generate_random_graph
graph.to_s
bfs_shortest_path(graph,2,9)

Thanks for the comments
Working Version
class Node
attr_reader :value
attr_reader :adjacent_nodes
attr_reader :path_from_start
def initialize(value)
#value = value
#adjacent_nodes = []
#path_from_start = []
end
def add_edge(node)
#adjacent_nodes.push(node)
end
def add_to_path(value)
#path_from_start.push(value)
end
def concat_to_path(value_array)
#path_from_start.concat(value_array)
end
def to_s
"#{#value} -> [#{#adjacent_nodes.map(&:value).sort.join(" ")}]"
end
end
class Graph
attr_reader :nodes
def initialize
#nodes = {}
end
def add_node(node)
#nodes[node.value] = node
end
def add_edge(node1,node2)
if #nodes[node1.value].adjacent_nodes.map(&:value).include? (node2.value)
puts "#{node1.value} and #{node2.value} already have an edge"
elsif node1.value == node2.value
puts "node1.value == node2.value"
else
#nodes[node1.value].add_edge(#nodes[node2.value])
#nodes[node2.value].add_edge(#nodes[node1.value])
end
end
def to_s
#nodes.keys.sort.each_with_index do |key,index|
puts "#{index + 1}. #{#nodes[key].to_s}"
end
end
end
def generate_random_graph
g = Graph.new
[*1..10].shuffle.each do |node_value|
g.add_node(Node.new(node_value))
end
40.times do
key1 = g.nodes.keys.sample
key2 = g.nodes.keys.sample
g.add_edge(g.nodes[key1],g.nodes[key2])
end
return g
end
def bfs(graph, start_node_value=2, search_value=9)
if graph.nodes[start_node_value].nil? || graph.nodes[search_value].nil?
return nil
end
visited = Set.new
search_queue = Queue.new
search_queue.enq(graph.nodes[start_node_value])
while !search_queue.empty? do
current_node = search_queue.deq
visited.add(current_node)
if current_node.value == search_value
return current_node
end
current_node.adjacent_nodes.each do |node|
if !visited.include?(graph.nodes[node.value])
search_queue.enq(graph.nodes[node.value])
end
end
end
end
def bfs_shortest_path(graph, start=2, search=9)
if graph.nodes[start].nil? || graph.nodes[search].nil?
return nil
end
visited = Set.new
visited.add(start)
search_queue = Queue.new
search_queue.enq(start)
while !search_queue.empty? do
current_node_key = search_queue.deq
current_node = graph.nodes[current_node_key]
current_node.add_to_path(current_node.value)
if current_node.value == search
return current_node.path_from_start
end
adjacent_nodes_array = current_node.adjacent_nodes.map{|x| x.value}
adjacent_nodes_array.each do |value|
if !visited.include?(value)
search_queue.enq(value)
visited.add(value)
graph.nodes[value].concat_to_path(current_node.path_from_start)
end
end
end
end
def test_graph
graph = generate_random_graph
graph.to_s
bfs_shortest_path(graph,2,9)
end

To keep a history of where you've been so you can reconstruct a path, instead of a visited set, use a hash. The hash tracks which node is the predecessor of each visited node. When you push a neighbor onto the queue, add the current node we're moving away from as the parent/predecessor by came_from[neighbor] = current.
This hash also works for eliminating cycles as visited does.
When you reach the goal, you can rebuild the path by repeatedly keying into this came_from hash until you run out of predecessors. Reverse the array (linear time) and return it as the final path.
Here's a minimal, runnable example you can adapt to your class structure:
def reconstruct_path(tail, came_from)
path = []
while !tail.nil?
path << tail
tail = came_from[tail]
end
path.reverse
end
def bfs(graph, start, goal)
q = Queue.new
q.enq(start)
came_from = {start => nil}
while !q.empty?
curr = q.deq
if graph.key? curr
return reconstruct_path(goal, came_from) if curr == goal
graph[curr].each do |neighbor|
if !came_from.key?(neighbor)
came_from[neighbor] = curr
q.enq(neighbor)
end
end
end
end
end
graph = {
"A" => ["B", "C"],
"B" => ["A", "F"],
"C" => ["D"],
"D" => ["E", "F"],
"E" => [],
"F" => []
}
=begin
+----+
v |
A--->B--->F
| ^
V |
C--->D----+
|
v
E
=end
p bfs(graph, "A", "F") # => ["A", "B", "F"]
p bfs(graph, "A", "E") # => ["A", "C", "D", "E"]
p bfs(graph, "B", "E") # => ["B", "A", "C", "D", "E"]

Related

Need help to create a breadth-first search function

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]]

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)

Iteration and array

How do I iterate 9 times and produce three arrays like this:
1 a
2 b
3 c
["a","b","c"]
4 d
5 e
6 f
["d","e","f"]
7 g
8 h
9 i
["g","h","i"] ?
-------------------------------------
1.upto(9) do
xxx = gets.chomp
wn << xxx
if wn.length ==3
puts wn.inspect
end
end
------------------------------------
I get the following output:
a
b
c
["a", "b", "c"]
d
e
f
g
h
i
Not the results I hoped for :(
A simple solution:
a1 = []
a2 = []
a3 = []
1.upto(9) do |i|
if a1.empty? || a1.size < 3
a1 << gets.chomp!
elsif a2.size < 3
a2 << gets.chomp!
else
a3 << gets.chomp!
end
end
puts a1
puts a2
puts a3
Create the 3 arrays, iterate 9 times, create conditions to populate them.
Do you have to iterate? you could always break your string by length, like so:
"abcdefghi".scan(/.{3}/).map{|i| i.split('')} # => [["a", "b", "c"], ["d", "e", "f"], ["g", "h", "i"]]
If you really must iterate:
1.upto(9) do
xxx = gets.chomp
wn << xxx
if wn.length % 3 == 0
puts wn.inspect
end
end
I think nested loops are a clean solution for you.
a = [[],[],[]]
3.times do |i|
3.times { |j| a[i][j] = gets.strip }
puts a[i].inspect
end

infinite enumerator rewind

I have a function that generates an enumerator in the following manner:
def create_example_enumerator(starting_value)
current = starting_value
e = Enumerator.new do |y|
loop do
current += 1
y << current
end
end
end
The current behavior is pretty straightforward.
> e = create_example_enumerator(0)
#<Enumerator: details>
> e.next
1
> e.next
2
> e.rewind
#<Enumerator: details>
> e.next
3
I would like e.rewind to reset the enumerator back to it's starting value.
Is there a nice way to do that while still using an infinite enumerator?
This should work:
n = Enumerator.new do |y|
number = 1
loop do
y.yield number
number += 1
end
end
n.next #=> 1
n.next #=> 2
n.next #=> 3
n.rewind
n.next #=> 1

Resources