View Full Version : [Contest 14?] Corsa delle macchine
Voglio proporre un contest un po' alternativo, nel senso che stavolta la speranza e' che diventi una gara tra algoritmi piu' che una mostra di microottimizzazioni.
La sfida si ispira alle corse di macchine che penso piu' di qualcuno avra' giocato con penna e foglio a quadri quand'era piccolo, anche se ho apportato qualche modifica per rendere (almeno in teoria) relativamente semplice l'implementazione della struttura di base. Il regolamento e' relativamente lungo, perche' ho cercato di essere il piu' rigoroso possibile, non escludo pero' bug :P
= Goal =
Muovere la vettura dalla griglia di partenza fino al traguardo
nel piu' breve tempo possibile
= Descrizione del mondo =
Il mondo e' costituito da una griglia rettangolare di celle
Ogni griglia puo' essere di uno tra i quattro seguenti tipi
Partenza: posizione dalla quale la vettura puo' partire
Asfalto: cella che puo' essere percorsa dall'auto
Erba: cella nella quale la macchina non puo' procedere, pena l'eliminazione
Traguardo: cella che l'auto deve raggiungere per concludere l'esecuzione
= Movimento della vettura =
Il tempo scorre in modo discreto. Ad ogni turno la vettura puo' decidere di variare
la propria velocita' ed aggiorna la propria posizione sulla griglia.
La vettura puo' controllare indipendentemente due velocita', quella lungo l'asse x
e quella lungo l'asse y. La velocita' e' espressa in caselle/turno, ed e' un valore
intero.
Se ad un dato istante la velocita' lungo un asse e' V, la velocita' lungo lo stesso
asse al turno successivo puo' essere uno tra i tre valori [V-1,V,V+1].
= Collisioni =
Le caselle di partenza, di arrivo e di asfalto sono considerate sgombre.
La casella d'erba NON e' sgombra.
La vettura puo' procedere solo su caselle sgombre.
Nel caso che uno spostamento che coinvolga diverse celle, tutte le
caselle per cui passa il segmento S che va da P1=(x1,y1) a P2=(x2,y2),
devono essere sgombre.
Il segmento parte dal centro della casella, per cui
in pratica per ogni casella (x,y) tale che
min(x1,x2) <= x <= max(x1,x2)
min(y1,y2) <= y <= max(y1,y2)
distanza( (x,y), S ) <= 1/2
tale casella deve essere sgombra
( distanza( (x,y) , S ) e' definita come
min_{ (x',y') \in S } max( abs(x-x'), abs(y-y') ),
= Condizione iniziale =
L'utente puo' scegliere di partire da una qualsiasi delle caselle di partenza,
con velocita' uguali a 0
= Condizione finale =
La corsa finisce quando in un dato istante la macchina passa attraverso una delle
caselle del traguardo, ovvero quando viene fatta una mossa _valida_ tra (x1,y1)
a (x2,y2) per la quale esiste una casella di traguardo (xt,yt) tale che
distanza( (xt,yt), S ) <= 1/2
= Obiettivo =
Data una certa configurazione, trovare la sequenza di movimenti della macchina che permettano
di ottenere una traiettoria completa tra partenza e arrivo nel minore numero di
passi possibile.
= Input =
In input da console viene fornita la mappa della pista nel seguente formato:
La prima riga contiene due interi separati da uno spazio
rappresentanti rispettivamente il numero
di righe <nrighe> e il numero di colonne <ncolonne> della mappa
Seguono <nrighe> righe ognuna composta di <ncolonne> caratteri e terminata
da un ritorno a capo,
Ogni carattere avra' uno dei seguenti significati
- (meno) : partenza
= (uguale) : arrivo
. (punto) : asfalto
# (cancelletto) : erba (ostacolo)
Esempio di input
24 40
########################################
##########################.....#########
##########################.....#########
###############....#######-----#########
##############..........#......#########
##############....#...........##########
##############....#####......###########
###############.......##################
#######.......###......#################
######..........###.....################
#####.....###...#####....###############
#####....####....#####....##############
####.....####....######...##############
####....#####....######...##############
####....#####....#####....##############
####....######...####.....##############
####.....#####...###.....###############
####.....#####....#.....################
####.....######........#################
####=====#######....####################
########################################
########################################
########################################
########################################
= Output =
L'output del programma dovra' essere nel seguente formato:
ogni riga deve contenere le coordinate <x> e <y> dell'auto ad ogni istante
successivo. Conseguentemente, se il percorso consta di n passi, l'output
sara' composto di n+1 righe
Per fare qualche prova per ora potete usare la pista sopra disegnata, provvedero' ad proporne di molto piu' corpose.
Allego un esempio di percorso valido, non necessariamente il migliore, per la pista segnata sopra.
Ovviamente avevo dimenticato l'allegato :D
Infine, per chi vuole divertirsi a creare tracciati, allego un paio di script python che permettono di convertire una immagine bitmap in tracciato.
Nell'immagine di partenza il colore bianco viene convertito in partenza, il nero in traguardo, il grigio (128,128,128) in asfalto, e tutto il resto in erba.
L'estensione va rinominata in .py
Iscritto.
Ho un'idea. Mi manca un pezzo ancora pero'.
Ho dei dubbi sulla validazione.
Se ho un pezzo
###.
###.
##..
##..
E volessi passare dal primo . in basso a sinistra a quello in alto a destra, e' una mossa regolare?( Passo esattamente per il centro di 4 pixel adiacenti, dei quali uno irregolare)
E altri esempi simili. Come interpretare la regola correttamente?
Altra domanda. Se DOPO aver passato il traguardo, sempre nella stessa mossa finale l'auto mi si schianta va bene lo stesso?
Insomma una vittoria postuma...
PS: Se mi lasciate da solo spezzo le braccine a tutti...
Ho dei dubbi sulla validazione.
Se ho un pezzo
###.
###.
##..
##..
E volessi passare dal primo . in basso a sinistra a quello in alto a destra, e' una mossa regolare?( Passo esattamente per il centro di 4 pixel adiacenti, dei quali uno irregolare)
E altri esempi simili. Come interpretare la regola correttamente?
Ciao ! Intanto grazie per la per la partecipazione :), inizialmente temevo nessuno se lo filasse 'sto contest :P.
Per rispondere alla tua domanda: non e' una mossa ammessa. Nel caso in questione la retta che congiunge i centri delle due caselle passa giusto per l'angolo di una casella # per cui la distanza risulta essere giusto 1/2.
La regola dice che ogni casella la cui distanza (del centro) dalla retta sia minore o uguale a 1/2 deve essere sgombra.
Piu' semplicemente, la regola in questione dice in termini matematici che qualsiasi casella che viene anche solo toccata dalla retta deve essere sgombra.
Spero di essermi chiarito un po' meglio
PS: Se mi lasciate da solo spezzo le braccine a tutti...
Beh, siamo almeno in due :D
Grazie. (Nooo devo rifare il pezzo piu' brutto) Vabbe'.
Forse hai perso questo:
Altra domanda. Se DOPO aver passato il traguardo, sempre nella stessa mossa finale l'auto mi si schianta va bene lo stesso?
Insomma una vittoria postuma...
giusto per fare un paio di esempi:
le caselle grigie sono quelle che devono essere libere
esempio due (uff...perche' solo 1 attach ? :D ).
Grazie. (Nooo devo rifare il pezzo piu' brutto) Vabbe'.
Sorry, la formula in effetti non e' chiarissima, ma era l'unico modo che mi era venuto in mente per scriverlo in modo rigoroso e non interpretabile.
Per l'altra domanda:
La corsa finisce quando in un dato istante la macchina passa attraverso una delle
caselle del traguardo, ovvero quando viene fatta una mossa valida tra (x1,y1)
a (x2,y2) per la quale esiste una casella di traguardo (xt,yt) tale che
distanza( (xt,yt), S ) <= 1/2
(notare il grassetto)
Ovvero la casella di arrivo nel momento in cui si passa il traguardo deve essere sgombra.
Sorry, la formula in effetti non e' chiarissima, ma era l'unico modo che mi era venuto in mente per scriverlo in modo rigoroso e non interpretabile.
Per l'altra domanda:
(notare il grassetto)
Ovvero la casella di arrivo nel momento in cui si passa il traguardo deve essere sgombra.
Ok. Quindi nel tuo esempio, essendo che le caselle di arrivo con = non hanno nulla dopo, occorre arrivarci fermandocisi proprio sopra.
Esatto.
Inizialmente volevo lasciare spazio dietro, solo che quando ho fatto a mano la soluzione di esempio l'ho fatta partendo dal traguardo, per cui a posteriori ho scambiato partenza e arrivo :D, ecco il motivo per cui non c'e' spazio :p
Comunque arriveranno delle piste piu' divertenti e soprattutto piu' grandi :P
Mi ci voleva una birra.
E' lento, ma ho fiducia che funzioni.
Eccolo al lavoro sull'esempio.
29 passi, compresi inizio e fine (In lanciata, spero che valga anche se dopo c'e' un muro!!)
Time - 5999ms
Steps - 29
########################################
##########################.....#########
##########################.....#########
###############....#######@----#########
##############...@@.@...#.@....#########
##############...@#....@.@....##########
##############....#####......###########
###############..@....##################
#######.......###.@.@..#################
######..@..@.@..###...@.################
#####.@...###.@.#####..@.###############
#####....####....#####....##############
####.@...####.@..######@..##############
####....#####....######...##############
####....#####....#####....##############
####@...######@..####..@..##############
####.....#####...###.....###############
####.....#####.@..#...@.################
####.....######..@..@..#################
####@====#######....####################
########################################
########################################
########################################
########################################
Folgorazione notturna, scrivo, rifattorizzo e si scende.
Ho un QuadCore a 2.7GHz ora, e ovviamente ho rifattorizzato in modo da poter iniettare il parellismo, cosa che ho puntualmente fatto.
Leggendo il codice intermendio mi sono accorto che beneficerei dei 64bit. Mannaggia, avessi voglia di reinstallare metterei Windows7 a 64bit.
Time - 408ms
Steps - 29
########################################
##########################.....#########
##########################.....#########
###############....#######@----#########
##############...@@.@...#.@....#########
##############...@#....@.@....##########
##############....#####......###########
###############..@....##################
#######.......###.@.@..#################
######...@.@.@..###...@.################
#####..@..###.@.#####....###############
#####....####....#####.@..##############
####..@..####.@..######...##############
####....#####....######...##############
####....#####....#####.@..##############
####.@..######@..####.....##############
####.....#####...###..@..###############
####.....#####.@..#..@..################
####.....######..@.@...#################
####@====#######....####################
########################################
########################################
########################################
########################################
Kralizek
18-05-2009, 09:47
hai provato anche con altri tracciati?
PS: se vuoi te lo testo sul mio sistema (2x dual core con win vista 64 bit)
Temo sia sbagliato, infatti la macchina parte a velocità 0... mentre nel tuo il primo step è anche il più lungo :read:
Cmq contest interessante, però nn mi viene in mente nulla di sensato :D
^TiGeRShArK^
18-05-2009, 10:10
Temo sia sbagliato, infatti la macchina parte a velocità 0... mentre nel tuo il primo step è anche il più lungo :read:
Cmq contest interessante, però nn mi viene in mente nulla di sensato :D
ehmmm...
veramente il suo primo step è correttamente il + corto.. :fagiano:
Temo sia sbagliato, infatti la macchina parte a velocità 0... mentre nel tuo il primo step è anche il più lungo :read:
Cmq contest interessante, però nn mi viene in mente nulla di sensato :D
Ma la partenza e' quella in alto!
hai provato anche con altri tracciati?
PS: se vuoi te lo testo sul mio sistema (2x dual core con win vista 64 bit)
Si', ne ho provato un altro che ho disegnato io, ma senza controprove da parte di nessuno posso solo vedere se funziona, non se e' buono, peggiore o migliore di altri.
!k-0t1c!
18-05-2009, 10:58
@gugoXX: sarei interessato a provare a portare la tua soluzione in F# e fare qualche benchmark, se sei disponibile a condividere i sorgenti ti prego di contattarmi via PM.
@gugoXX: sarei interessato a provare a portare la tua soluzione in F# e fare qualche benchmark, se sei disponibile a condividere i sorgenti ti prego di contattarmi via PM.
Ma direi che posso postarla qui, come sempre.
Volevo solo aspettare qualche proposta...
!k-0t1c!
18-05-2009, 11:53
Ma direi che posso postarla qui, come sempre.
Volevo solo aspettare qualche proposta...
Avevo chiesto via PM appunto per non "rovinare" il giochino a chi ci si sta mettendo senza tuttavia dover aspettare il completamento da parte di altri :p never mind
Kralizek
18-05-2009, 14:00
sono interessatissimo a vedere il porting in F# una volta fatto :)
Penso !k-0t1c! ci stia gia' lavorando... :)
!k-0t1c!
18-05-2009, 18:55
Grazie a gugoXX ho completato il porting in F# (non molto ottimizzato, giusto un abbozzo). Sul mio PC risolve il tracciato in meno di 2,4s (parsing e caricamento in memoria esclusi, ma insieme non eccedono 1s).
Gli ho appena scritto un'email che credo troverà interessante e che contiene il progetto F#.
Dato che il cuore della soluzione l'ha pensato lui, tuttavia, non posterò la soluzione senza il suo permesso.
Grazie a gugoXX ho completato il porting in F# (non molto ottimizzato, giusto un abbozzo). Sul mio PC risolve il tracciato in meno di 2,4s (parsing e caricamento in memoria esclusi, ma insieme non eccedono 1s).
Gli ho appena scritto un'email che credo troverà interessante e che contiene il progetto F#.
Dato che il cuore della soluzione l'ha pensato lui, tuttavia, non posterò la soluzione senza il suo permesso.
Ma che permessi. Mica ci pagano qui :)
Diciamo anche che piu' che io la soluzione l'hanno pensata Bresnham e Dijkstra.
Il li ho solo messi insieme :)
Strano parsing e caricamento in memoria. Lettura e parsing in C# sono 70ms.
Dai, aspettiamo ancora un giorno o due e poi postiamo.
!k-0t1c!
18-05-2009, 19:03
Ma che permessi. Mica ci pagano qui :)
Diciamo anche che piu' che io la soluzione l'hanno pensata Bresnham e Dijkstra.
Il li ho solo messi insieme :)
Strano parsing e caricamento in memoria. Lettura e parsing in C# sono 70ms.
Dai, aspettiamo ancora un giorno o due e poi postiamo.
Non ho misurato parsing e caricamento, ma come puoi osservare il parsing iniziale lo faccio in pieno stile funzionale, e in questo caso non è il più efficiente. (per intenderci guarda riga 35 del Program.fs che ti ho mandato)
Comunque quando posti tu posto anche io e la risolviamo così :)
Ma la partenza e' quella in alto!
DOH :doh:
Vabè tutto a posto allora :asd:
!k-0t1c!
18-05-2009, 20:39
Cambiando una importante porzione del codice di gugo ho ottenuto questo. Mi pare corretto, se si considera che per ogni passo che tocca il vertice di 4 caselle si considerano percorse solo le due caselle attraversate e non tutte e quattro quelle che le rette in questione formano.
2630ms
########################################
##########################.....#########
##########################.....#########
###############....#######@----#########
##############...@@.@...#.@....#########
##############...@#....@.@....##########
##############...@#####......###########
###############...@...##################
#######.......###......#################
######.....@.@..###.@...################
#####...@.###.@.#####....###############
#####....####....#####@...##############
####..@..####.@..######@..##############
####....#####....######...##############
####....#####....#####.@..##############
####.@..######@..####.....##############
####.....#####...###..@..###############
####.....#####.@..#.@...################
####.....######.@.@....#################
####=@===#######....####################
########################################
########################################
########################################
########################################
28 passi
Cambiando una importante porzione del codice di gugo ho ottenuto questo. Mi pare corretto, se si considera che per ogni passo che tocca il vertice di 4 caselle si considerano percorse solo le due caselle attraversate e non tutte e quattro quelle che le rette in questione formano.
Putroppo da specifiche non e' ammesso, l'avevo chiesto esplicitamente perche' la prima versione che avevo scritto sfruttava proprio questo effetto.
In pratica bisogna considerare l'auto come "spessa", e quindi non puo' passare solo per il punot infinitesimo di contatto tra i due pixel obliqui in questione.
Altrimenti si potrebbe mettere anche un "obliquo" in alto, nella seconda curva.
Dai, posto il codice.
class Program
{
static void Main(string[] args)
{
string[] maps=File.ReadAllLines(@"..\..\Traccia.txt");
Stopwatch sw = new Stopwatch();
sw.Start();
MapClass Map = new MapClass();
Map.Parse2(maps.Skip(1));
Point[] perc = Map.Exec2();
sw.Stop();
Console.WriteLine("Time - {0}ms", sw.ElapsedMilliseconds);
Console.WriteLine("Steps - {0}", perc.Length);
Map.Overlap(perc);
Map.Print(Console.Out);
Console.ReadKey();
}
}
public class MapClass
{
public int DX { get; private set; }
public int DY { get; private set; }
private char[][] Traccia;
public char GetTraccia(Point p)
{
return Traccia[p.Y][p.X];
}
public char GetTraccia(int x, int y)
{
return Traccia[y][x];
}
private char SetTraccia(Point p, char dst)
{
char prev = GetTraccia(p);
Traccia[p.Y][p.X] = dst;
return prev;
}
public void Print(TextWriter tw)
{
for (int y = 0; y < DY; y++)
{
string toprint = new string(Traccia[y]);
tw.WriteLine(toprint);
}
}
public void Overlap(Point[] perc)
{
foreach (Point p in perc)
{
SetTraccia(p, '@');
}
}
const char StartChar = '-';
const char EndChar = '=';
const char IllegalChar = '#';
const char TrackChar = '.';
public void Parse2(IEnumerable<string> srcmapfile)
{
string[] mapfile = srcmapfile.ToArray();
DY = mapfile.Length;
Traccia = new char[DY][];
for (int t = 0; t < DY; t++)
{
Traccia[t] = mapfile[t].ToCharArray();
}
DX = Traccia[0].Length;
//Ricerca partenze e arrivi
for (int y = 0; y < DY; y++)
{
for (int x = 0; x < DX; x++)
{
Point p = new Point(x, y);
char tr = GetTraccia(p);
if (tr == StartChar)
{
Nodo nod = new Nodo(p, Point.Zero);
// Dovro' processare tutte le partenze supponendo velocita' zero
// Memorizzo come nodi da processare.
NToProcess[nod]=Nodo.Invalid;
}
if (tr == EndChar)
{
Arrivo.Add(p);
}
}
}
}
List<Point> Arrivo = new List<Point>();
Dictionary<Nodo, Nodo> NToProcess = new Dictionary<Nodo, Nodo>();
public Point[] Exec2()
{
List<Nodo> res = CreateGraph2();
return res.Select(node => node.Posizione).ToArray();
}
private List<Point> Accelerazioni = new List<Point>(){
new Point(-1,-1),
new Point(-1,0),
new Point(-1,+1),
new Point(0,-1),
new Point(0,0),
new Point(0,+1),
new Point(+1,-1),
new Point(+1,0),
new Point(+1,+1)
};
private List<Nodo> CreateGraph2()
{
// Si occupa della ricerca della soluzione.
// Ha bisogno della Mappa caricata, e dell'array
// NToProcess ovvero i nodi iniziali da cui partire
// AlreadyProcessed Conterra' i nodi gia' processati secondo Dijkstra
// Per ciascun nodo e' segnato il precedente
// I pesi non servono, per costruzione ad ogni passo aggiungeremo 1 al peso precedente.
Dictionary<Nodo, Nodo> AlreadyProcessed = new Dictionary<Nodo, Nodo>();
bool endfound = false;
Nodo End = Nodo.Invalid;
NToProcess.ForAll(ntp => AlreadyProcessed[ntp.Key] = ntp.Value);
// Ottimizzazione. Parallel Dijkstra.
// Ad ogni passo estraggo e processo contemporaneamente tutti i nodi aventi lo stesso peso
while (!endfound && NToProcess.Count > 0)
{
//NextToProcess conterra' alla fine del while i nodi da visitare al prossimo passo
// secondo Dijkstra. Per costruzione si puo' giungere a nodi del peso successivo
// solo a partire dai nodi del peso corrente. (i nodi distano fra loro tutti solo 1)
Dictionary<Nodo, Nodo> NextToProcess = new Dictionary<Nodo, Nodo>();
// Per ciascun nodo da processare cerco le destinazioni possibili, aggiornando le velocita'.
Parallel.ForEach(NToProcess, ntp =>
{
// Conterra' le destinazioni possibili del singolo nodo.
List<KeyValuePair<Nodo, Nodo>> ToSum = new List<KeyValuePair<Nodo, Nodo>>();
Nodo np = ntp.Key;
// A partire da questo nodo, accellero in tutti i modi possibili.
Point partenza = np.Posizione;
foreach (Point acc in Accelerazioni)
{
// Se velocita' prima = 0 e accellerazione = 0 skip, per togliere le autoreferenze (Inutili, meglio muoversi sempre)
if ((np.Velocita.IsZero) && (acc.IsZero)) continue;
Point newvel = np.Velocita + acc; // Nuova velocita'
Point arrivo = partenza + newvel; // Nuova posizione
Nodo nodoarrivo = new Nodo(arrivo, newvel);
// Guardo se il tracciato e' valido, e se eventualmente ho toccato l'arrivo
ValidLine resp = IsValid(partenza, arrivo);
if (resp == ValidLine.Valid)
{
// Dijkstra: Se e'valido e non era ancora stato toccato,
// allora lo aggiungo ai prossimi da controllare
if (!AlreadyProcessed.ContainsKey(nodoarrivo))
{
ToSum.Add(new KeyValuePair<Nodo,Nodo>(nodoarrivo,np));
}
}
else if (resp == ValidLine.End && !endfound)
{
// Altrimenti se e' uno dei finali, lo aggiungo,
// ne tengo traccia e alzo le guardie per l'uscita.
endfound = true;
End = nodoarrivo;
ToSum.Add(new KeyValuePair<Nodo, Nodo>(End, np));
break;
}
}
// Lock per multiprocess, insisto in scrittura su variabili globali ai task
lock (AlreadyProcessed)
{
ToSum.ForAll(ts => {
NextToProcess[ts.Key] = ts.Value;
AlreadyProcessed[ts.Key] = ts.Value;
});
}
});
// Swap per i nodi da processare.
NToProcess = NextToProcess;
}
// Dijkstra end. Ricostruisco il percorso ricorsivamente a partire dalla fine trovata.
List<Nodo> ret = new List<Nodo>();
while (!End.IsInvalid)
{
ret.Add(End);
End = AlreadyProcessed[End];
}
return ret;
}
/// <summary>
/// Valid = la traccia e' valida
/// Invalid = la traccia passa su un punto illegale
/// End = la traccia passa attraverso (o finisce, o parte) un traguardo
/// </summary>
private enum ValidLine
{
Valid, Invalid, End
}
public Bitmap DrawLine(Point p1, Point p2)
{
//Bresnham?
// No basta, 2 palle. Uso i Graphics.
// Zoom * 3 per considerare lo spessore dell'auto.
Bitmap dst = new Bitmap(DX*3, DY*3);
Graphics gr = Graphics.FromImage(dst);
// Penna larga 3
Pen pen = new Pen(Color.Red,3);
gr.Clear(Color.White);
gr.DrawLine(pen, p1.X * 3 + 1, p1.Y * 3 + 1, p2.X * 3 + 1, p2.Y * 3 + 1);
return dst;
}
private ValidLine IsValid(Point fp, Point dp)
{
//Clipping
if (fp.X < 0 || fp.X >= DX || fp.Y < 0 || fp.Y >= DY) return ValidLine.Invalid;
if (dp.X < 0 || dp.X >= DX || dp.Y < 0 || dp.Y >= DY) return ValidLine.Invalid;
int dx = Math.Abs(fp.X - dp.X);
int dy = Math.Abs(fp.Y - dp.Y);
// Ottimizzazione: Casi banali
// Partenza coincidente con arrivo
if (dx == 0 && dy == 0)
{
char onlych = GetTraccia(fp);
if (onlych == IllegalChar) return ValidLine.Invalid;
if (onlych == EndChar) return ValidLine.End;
return ValidLine.Valid;
}
// Partenza o inizio illegali
char dpchar = GetTraccia(dp);
if (dpchar == IllegalChar) return ValidLine.Invalid;
char fpchar = GetTraccia(fp);
if (fpchar == IllegalChar) return ValidLine.Invalid;
// Se e' solo verticale
if (dx == 0)
{
// Se e' alta solo 2
if (dy == 1)
{
if (dpchar == EndChar || fpchar == EndChar) return ValidLine.End;
return ValidLine.Valid;
}
// Altrimenti guardo i punti in mezzo banalmente
int y0=fp.Y;
int y1=dp.Y;
if (y0>y1) Utility.Swap(ref y0, ref y1);
y0++;
ValidLine ret = ValidLine.Valid;
for (int y = y0; y < y1; y++)
{
char ch = GetTraccia(fp.X, y);
if (ch == IllegalChar) return ValidLine.Invalid;
if (ch == EndChar) ret = ValidLine.End;
}
return ret;
}
// Se e' solo orizzontale
if (dy == 0)
{
// Se e' larga solo 2
if (dx == 1)
{
if (dpchar == EndChar || fpchar == EndChar) return ValidLine.End;
return ValidLine.Valid;
}
//Altrimenti guardo i punti in mezzo banalmente
int x0 = fp.X;
int x1 = dp.X;
if (x0 > x1) Utility.Swap(ref x0, ref x1);
x0++;
ValidLine ret = ValidLine.Valid;
for (int x = x0; x < x1; x++)
{
char ch = GetTraccia(x, fp.Y);
if (ch == IllegalChar) return ValidLine.Invalid;
if (ch == EndChar) ret = ValidLine.End;
}
return ret;
}
// Se e' obliqua e lunga solo 2 (vertici di un quadrato 2x2)
// Per definizione nessuno dei punti del quadrato deve essere illegale
if (dx == 1 && dy == 1)
{
char chalt1 = GetTraccia(fp.X, dp.Y);
if (chalt1 == IllegalChar) return ValidLine.Invalid;
char chalt2 = GetTraccia(dp.X, fp.Y);
if (chalt2 == IllegalChar) return ValidLine.Invalid;
if (chalt1 == EndChar || chalt2 == EndChar || dpchar == EndChar || fpchar == EndChar) return ValidLine.End;
return ValidLine.Valid;
}
//Altrimenti ricerca validita' traccia completa
// Disegno traccia (Bresnham)
Bitmap dst = DrawLine(fp, dp);
//Cerco i punti tracciati e guardo dove sono capitati.
// Calcolo i punti da controllare
int minx=Math.Min(fp.X,dp.X)*3;
int miny=Math.Min(fp.Y,dp.Y)*3;
int maxx=Math.Max(fp.X,dp.X)*3+2;
int maxy=Math.Max(fp.Y,dp.Y)*3+2;
ValidLine currentres = ValidLine.Valid;
// Se il punto e' stato tracciato da Bresnham e se sotto c'e' un punto illegale
// Allora esco immediatamente con Invalid
// E se riesco a finire restituisco Valid o End a seconda che nel mentre
// sia passato sopra un traguardo.
for (int y = miny; y <= maxy; y++)
{
for (int x=minx;x<=maxx;x++)
{
Color fndcol = dst.GetPixel(x,y);
if (fndcol.R == 255)
{
char tr = GetTraccia(x/3, y/3);
switch (tr)
{
case IllegalChar:
//Male
return ValidLine.Invalid;
case EndChar:
//Dopo di questo, se per caso non dovessi bocciare, allora avro' finito per traguardo.
currentres = ValidLine.End;
break;
case StartChar:
case TrackChar:
// OK, continuo la ricerca
break;
default:
throw new Exception("Unexpected char");
}
}
}
}
return currentres;
}
}
/// <summary>
/// Deve essere struct per Dictionary.
/// </summary>
public struct Point
{
public int X;
public int Y;
public Point(int px, int py)
{
X = px;
Y = py;
}
private static Point i_Zero = new Point(0, 0);
public static Point Zero
{
get
{
return i_Zero;
}
}
public bool IsZero
{
get
{
return (X==0) && (Y==0);
}
}
private static Point i_Invalid = new Point(int.MinValue, int.MinValue);
public static Point Invalid
{
get
{
return i_Invalid;
}
}
public bool IsInvalid
{
get
{
return (X == int.MinValue) || (Y == int.MinValue);
}
}
public static Point operator+(Point primo, Point altro)
{
return new Point(primo.X + altro.X, primo.Y + altro.Y);
}
}
/// <summary>
/// Deve essere struct per Dictionary.
/// </summary>
public struct Nodo
{
public Point Posizione;
public Point Velocita;
public Nodo(Point ppos, Point pvel)
{
Posizione = ppos;
Velocita = pvel;
}
private static Nodo i_Invalid = new Nodo(Point.Invalid, Point.Invalid);
public static Nodo Invalid
{
get
{
return i_Invalid;
}
}
public bool IsInvalid
{
get
{
return Posizione.IsInvalid || Velocita.IsInvalid;
}
}
}
public static class Utility
{
public static void ForAll<T>(this IEnumerable<T> domain, Action<T> act)
{
foreach (T t in domain) act(t);
}
public static void Swap<T>(ref T t1, ref T t2) where T:struct
{
T tmp = t1;
t1 = t2;
t2 = tmp;
}
}
!k-0t1c!
19-05-2009, 14:07
Gulp, quei 2 post me li ero persi :(
!k-0t1c!
20-05-2009, 01:41
Posto un porting in F# molto fedele alla versione di gugoXX.
Le performance, misurate su .NET 4 Beta 1 (disponibile da ieri per gli abbonati MSDN e dal 22 al pubblico), sono di meno di 20ms inferiori alla versione C# nonostante ci sia un'unica volta (nella logica della soluzione) in cui si approfitta della mutabilità di una struttura dati.
Notate che il mio porting non brilla né per ottimizzazione né per eleganza, ma è un buon compromesso tra le due (anche per non sacrificare fedeltà con la versione di gugoXX). Ho aggiunto inoltre un caso nella ricerca del percorso per determinare immediatamente le caselle da controllare negli spostamenti diagonali (stavolta in maniera aderente alla specifica) per non ricorrere all'uso di bitmaps. Comunque sarebbe carino avere un tracciato di 100x100 o simile ;)
#light
open System
open System.IO
open System.Collections.Generic
open System.Drawing
[<Struct>]
type Point =
val X : int
val Y : int
member x.IsEmpty = x.X = 0 && x.Y = 0
new(x,y) = { X = x; Y = y }
static member (-) (a:Point,b:Point) = Point(a.X-b.X, a.Y-b.Y)
static member (+) (a:Point,b:Point) = Point(a.X+b.X, a.Y+b.Y)
[<Struct>]
type Nodo =
val Posizione : Point
val Velocità : Point
new(p,v) = { Posizione = p; Velocità = v }
type PointType =
| Valid
| End
| Invalid
type Result =
| Next of Nodo * Nodo
| Finish of Nodo * Nodo
| BadPath
type Mappa(mapfile:string seq) =
let traccia = mapfile |> Array.of_seq |> Array.map(fun ln -> ln.ToCharArray())
let DX = traccia.[0].Length
let DY = traccia.Length
let Partenza = traccia |> Array.mapi(fun i x -> x |> Array.mapi(fun j y -> if y = '-' then Some(Point(j,i)) else None) |> Array.filter(fun it -> it.IsSome) |> Array.map(fun it -> it.Value)) |> Array.concat
let Accelerations =
let p = [| -1; 0; 1 |]
p |> Array.map(fun x -> p |> Array.map(fun y -> Point(x, y))) |> Array.concat
//i valori non sono inseriti come costanti perché match non esegue la comparazione con il valore di variabili se non in clausole when
let GetPointType (p:Point) = match traccia.[p.Y].[p.X] with
| '.' -> Valid
| '#' | '-' -> Invalid
| '=' -> End
| _ -> failwith "Unknown character"
//controlla la percorribilità delle caselle tra sp e ep
let CheckPath (sp:Point) (ep:Point) =
async {
//piccoli helper, inlining forzato per favorire le prestazioni
let inline valid c = c = '.'
let inline getSeq(s:int,e) = if s > e then { e..s } else { s..e }
let inline checkMinMax (p:Point) = p.X >= 0 && p.Y >= 0 && p.X < DX && p.Y < DY
if checkMinMax sp && checkMinMax ep then
let s = ep - sp
match GetPointType ep with
| Valid | End -> if s.X = 0 then
if s.Y = 1 then return true
else return getSeq(sp.Y+1,ep.Y-1) |> Seq.forall(fun i -> valid traccia.[i].[sp.X])
elif s.Y = 0 then
if s.X = 1 then return true
else return getSeq(sp.X,ep.X) |> Seq.forall(fun i -> valid traccia.[sp.Y].[i])
else
if Math.Abs(s.X) = Math.Abs(s.Y) then
return Seq.zip (getSeq(sp.Y, ep.Y)) (getSeq(sp.X, ep.X)) |> Seq.windowed(2) |> Seq.forall(fun arr -> let a,b = arr.[0]
let c,d = arr.[1]
valid traccia.[c].[d] &&
valid traccia.[a].[d] &&
valid traccia.[c].[b])
else
let drawLine (p':Point) (p'':Point) =
let bmp = new Bitmap(DX*3, DY*3)
let gr = Graphics.FromImage bmp
gr.Clear(Color.Black)
use pen = new Pen(Color.Red, 3.0f)
gr.DrawLine(pen, p'.X * 3 + 1, p'.Y * 3 + 1, p''.X * 3 + 1, p''.Y * 3 + 1)
bmp
let minx=Math.Min(sp.X,ep.X)*3
let miny=Math.Min(sp.Y,ep.Y)*3
let maxx=Math.Max(sp.X,ep.X)*3+2
let maxy=Math.Max(sp.Y,ep.Y)*3+2
use bmp = drawLine sp ep
return seq { for i = miny to maxy do for j = minx to maxx do if bmp.GetPixel(j,i).R = 255uy then yield valid traccia.[i/3].[j/3] } |> Seq.forall((=) true)
| Invalid -> return false
else return false
}
member x.CreateGraph() =
//controlla che per un dato Point ed una data accelerazione la strada sia percorribile
let esamina (p:Nodo) (acc:Point) (processed:Nodo array) : Async<Result> =
async {
let nuovaVel = p.Velocità + acc
let arrivo = p.Posizione + nuovaVel
let nodoArrivo = Nodo(arrivo,nuovaVel)
if not (processed |> Array.exists((=) nodoArrivo)) then
let! validPath = CheckPath p.Posizione arrivo
if validPath then
match GetPointType arrivo with
| Valid -> return Next (nodoArrivo, p)
| End -> return Finish (nodoArrivo, p)
| _ -> failwith "Non dovrebbe succedere mai!"
return BadPath //solo per non avere problemi con async{}
else
return BadPath
else return BadPath
}
//controlla ricorsivamente e parallelamente i nodi raggiungibili a partire da quelli raggiunti all'ultimo passo
let rec esegui (processati:Dictionary<Nodo,Nodo>) (processandi:Nodo array) =
let examined = processati.Keys |> Array.of_seq
let results = [| for n in processandi do for a in Accelerations -> esamina n a examined |] |> Async.Parallel |> Async.RunSynchronously
match results |> Array.tryFind(fun e -> match e with | Finish _ -> true | _ -> false) with
| Some (Finish(e,s)) -> processati.Add(e, s)
(e,processati)
| None -> let p = List<Nodo>()
let ntp = seq { for e in results do match e with
| Next (x,y) -> yield x,y
| _ -> () } |> Set.of_seq
for (a,b) in ntp do
processati.[a] <- b
p.Add(a)
if p.Count > 0 then esegui processati (p |> Array.of_seq) //esamina il prossimo gruppo
else failwith "Nessun percorso trovato" //non esiste un percorso valido
let startNodes = Partenza |> Seq.map(fun p -> Nodo(p,Point(0,0))) |> Array.of_seq
let procBase =
let d = Dictionary<Nodo,Nodo>()
startNodes |> Array.iter(fun n -> d.Add(n, Nodo(Point(-1,-1), Point(0,0))))
d
let endNode, processati = esegui procBase startNodes
let rec ricostruisciPercorso el lst =
if processati.ContainsKey el then ricostruisciPercorso processati.[el] (el :: lst)
else lst
ricostruisciPercorso endNode []
member x.Traccia = Array.copy traccia
let main() =
let sw = System.Diagnostics.Stopwatch()
sw.Start()
let m = Mappa((if Environment.GetCommandLineArgs().Length > 1 then Environment.GetCommandLineArgs().[1] else "percorso.txt") |> File.ReadAllLines |> Seq.skip(1) |> Array.of_seq)
//avvia la ricerca del percorso
let steps = m.CreateGraph()
sw.Stop()
printfn "%ims" sw.ElapsedMilliseconds
//crea una copia del tracciato originale e inserisce in corrispondenza di ogni step una chiocciola
let t = m.Traccia
steps |> List.iter(fun n -> t.[n.Posizione.Y].[n.Posizione.X] <- '@')
//stampa il risultato
t |> Array.iter(fun a -> String(a) |> printfn "%s")
printfn "%i passi" (List.length steps)
Console.ReadLine() |> ignore
do main();;
PS: Se mi lasciate da solo spezzo le braccine a tutti...
Sono passati 6 mesi.
Direi che c'e' stato tempo, e a parte il porting in F# della mia soluzione, sono rimasto da solo.
Spezzo le braccine?
Sambenito?
Sono passati 6 mesi.
Direi che c'e' stato tempo, e a parte il porting in F# della mia soluzione, sono rimasto da solo.
Spezzo le braccine?
Sambenito?
ehm, io me n'ero totalmente dimenticato, e sono l'autore del contest :asd:.
Ho una soluzione quasi funzionante, il problema e' solo trovare il tempo da dedicarci :S. Cerchero' di rimediare nel fine settimana (ma non prometto...)
Vincenzo1968
14-11-2009, 14:48
Sono passati 6 mesi.
Direi che c'e' stato tempo, e a parte il porting in F# della mia soluzione, sono rimasto da solo.
Spezzo le braccine?
Sambenito?
L'immagine seguente ritrae l'autodafé del 4 ottobre scorso. Gugo viene amorevolmente accompagnato, vestito di sambenito e coroca, al rogo(contest 17 (http://www.hwupgrade.it/forum/showthread.php?t=2030843)):
http://www.guidealgoritmi.it/images/ImgForums/autodafe10.jpg
L'immagine seguente ritrae l'autodafé del 4 ottobre scorso. Gugo viene amorevolmente accompagnato, vestito di sambenito e coroca, al rogo(contest 17 (http://www.hwupgrade.it/forum/showthread.php?t=2030843)):
Eseguiamo processi e sentenze in ordine, per cortesia.
Che quando tocchera' a me, mi dovro' suicidare perche' non c'e' piu' nessuno :D:D
vBulletin® v3.6.4, Copyright ©2000-2026, Jelsoft Enterprises Ltd.