Soluzione Otto Regine
21 Feb 2017
Soluzione Otto Regine
# Il modo giusto di risolvere questo problema è la costruzione di una
# classe che contenga il problema, farlo in una funzione solo sarebbe
# troppo caotico e difficile da gestire. Usiamo un algoritmo
# di backtracking classico. Il backtracking affrontato in modo ricorsivo
# permette di costruire un problema che sia dinamico sia rispetto
# la deimensione della scacchiera che rispetto al numero di regine da
# mettere sulla scacchiera
class NQueens
# Ci sono due elementi nella nostra classe, che rendiamo
# disponibili in lettura. La soluzione (sol) e la dimensione della
# scacchiera per il nostro problema
attr_reader :sol, :size
# Inizializziamo la nostra soluzione sotto forma di scacchiera di
# dimensione quadrata. Questa è una delle due interfacce che richiede
# il check dei dati in ingresso (intero positivo)
def initialize(size = 8, queens = 8)
raise ArgumentError, "size deve essere Fixnum" unless size.is_a? Integer
raise ArgumentError, "size deve essere positivo" unless size > 0
raise ArgumentError, "queens deve essere Fixnum" unless queens.is_a? Integer
raise ArgumentError, "queens deve essere positivo" unless queens > 0
# La soluzione è una matrice n x n inizializzata a 0
@size = size
@queens = queens
@sol = Array.new(@size) { Array.new(@size) { 0 } }
end
# Il resto delle funzioni non hanno alcun input che viene data dall'utente
# (o almeno non dovrebbero) quindi per le altre siccome sappiamo cosa passiamo
# non inseriamo nessun controllo sull'input
# Questa è una funzione interfaccia esterna che permette di ottenere
# all'esterno la soluzione. (attiva anche il calcolo della soluzione)
def solve
# se riesce a piazzare tutte le regine (partendo da zero)
if self.place(0)
# allora ritorna la matrice con la soluzione
return @sol
else
# Altrimenti con un errore ci avvisa che non esiste soluzione
raise RuntimeError, "Non esiste soluzione"
end
end
# La funzione che prova a piazzare una nuova regina, che è
# chiamata ricorsivamente. Se la regina riesce a piazzarla,
# chiama ricorsivamente il piazzamento di queen + 1
def place(queen)
# Strategia di uscita della funzione ricorsiva
return true if queen == @queens # USCITA RICORSIONE
# Piazzando la soluzione, la posizione della regina
# nella matrice viene messa ad 1. Se la soluzione non funziona
# allora si annulla la modifica imponendo la posizione della
# matrice a 0. Questa strategia è chiamata BACKTRACKING
for row in 0...@size
if self.place?(row, queen)
@sol[row][queen] = 1
return true if self.place(queen + 1) # RICORSIONE
@sol[row][queen] = 0 # BACKTRACKING
end
end
return false
end
# Questa funzione controlla se è effettivamente possibile piazzare
# la regina in una data posizione row, col, controllando che nel raggio
# di azione, la regina piazzata in quella posizione row, col non sia
# in grado di mangiare un'altra delle regine già piazzate. Esempio 4 x 4
# REGINE PIAZZATE Regina in 2, 3
# | 0 R 0 0 | | 0 x 0 x |
# | 0 0 0 R | | 0 0 x x |
# | 0 0 0 ? | | x x x x |
# | 0 0 0 0 | | 0 0 x x |
# In questo esempio non posso piazzare la nuova regina in 2, 3 perchè
# entrambe le prime due regine verrebbero mangiate
def place?(row, col)
# Siccome inseriamo le regine una riga alla volta,
# ci basta controllare solo rispetto alla colonna
for i in 0...@size
return false if @sol[row][i] == 1
end
# Usiamo y = x + q1 e y = x - q2 per
# valutare gli elementi diagonali
q1 = row - col
q2 = row + col
for i in 0...@size
if (0...@size).include?(i + q1)
return false if @sol[i + q1][i] == 1
end
if (0...@size).include?(i - q2)
return false if @sol[i - q2][i] == 1
end
end
# Se una qualsiasi delle caselle controllate prima
# contiene una regina, allora ritorniamo false, altrimenti
# ritorniamo true
return true
end
end
# A questo punto risolviamo il vero e proprio problema
# rispondendo con una funzione che usa la notra classe
def otto_regine
regine = 30 # <- cambia qui per cambiare la dimensione
# del problema (es. 12?)
# In una riga, risolviamo il problema
r = NQueens.new(regine, regine).solve
# Stampiamo a schermo la soluzione tanto per vederla
puts r.map { |e|
e.map { |z|
z == 1 ? "Q" : "."
}.join(" ")
}.join("\n")
# Creiamo l'Array che conterrà la soluzione così come richiesto
# nel testo dell'esercizio
s = Array.new(regine) { 0 }
for i in 0...regine
for j in 0...regine
s[i] = j if r[i][j] == 1
end
end
return s
end