ProgrammieraufgabenHier wird regelmäßig eine neue Programmieraufgabe gestellt, die dann gelöst werden soll und in Zusammenarbeit mit den Moderatoren auch besprochen werden kann.
Springerproblem
Diskussion: Springerproblem im Forum Programmieraufgaben, in der Kategorie Code Kitchen; Anzeige
Backtracking mit einfacher Heuristik SICStus/SWI
Code:
:- use_module(library(between)).
:- use_module(library(lists)).
solve(N,S,R) :- numlist(1,N,L),pairs(L,Ps),delete(Ps,S,NPs),!,get(S,NPs,R).
pairs(L,Ps) :- findall((X,Y),(member(X,L),member(Y,L)),Ps).
distance((X,Y),(DX,DY),N) ...
Eingabe:
solve(Boardgroesse,(StartX,StartY),Ergebnis).
Bsp:
solve(5,(1,1),Res).
für 5x5 Board mit (1,1) alst Startfeld.
kann sich zwar nicht mit Lescos Implementierung messen (entweder nutzt er noch ein paar Regeln oder ich sollte weniger Speicher "fressen" ), braucht aber für 8x8 ~31ms, 20x20 ~ 1.7 Sek, 50x50 ~ 70 Sek.
Original von CDW
(entweder nutzt er noch ein paar Regeln oder ich sollte weniger Speicher "fressen" )
Die einzige (algorithmische) Optimierung, die ich verwendet habe ist die Sortierung der Nachfolgefelder anhand der Anzahl ihrer jeweiligen gültigen Nachfolger. Meine Prolog-Unkenntnis hindert mich jedoch daran, zu erkennen, ob das bei deiner Lösung auch schon so gemacht wird.
Allerdings gehe ich grob zusammengefasst so vor:
generiere erstmal 2D Koordinatenliste der einzelnen Felder. Diese wird nun benutzt, um jeweils Nachfolgezüge auszuwählen (und deren Nachfolgezüge für Bewertung). Es kann also sein, dass hier noch Listenoperationen an dem Performanceeinbruch schuld sind. Auch zeigt mir der Profiler an, dass "distance" >90% aller Rechenzeit verbraucht. Ich werde dann bei Gelegenheit probieren, neue Züge zu generieren, anstatt alles aus einer Liste auszuwählen und per "distance" zu prüfen, ob das ein gültiger Zug sein kann.
EDIT:
Ich hab das ein wenig "optimiert" - statt einer vorgefertigten Koordinatenliste wird nun erst bei Bedarf ein Zug generiert. Auch wird für die Prüfung/Speicherung getätigter Züge statt einfacher Liste nun eine Associations (Bintree) benutzt. Entsprechend sind auch die Zeiten:
1.7Sek werden nun für 80x80 Feld gebraucht, 160x160 ist immerhin in 6Sek gemacht, 200x200 in ~10 Sek.
nicht mehr so elegant
Nur Associations (SWI/SICStus)
Code:
:- use_module(library(lists)).
:- use_module(library(assoc)).
solve(N,S,R) :- put_assoc(S,_,_,Tree),Max is N*N-1,get(N,S,Tree,Max,R).
next((X,Y),(NX,NY),N):-
((X>2,NX is X-2;X+1<N,NX is X+2), (Y>1,NY is Y-1;Y<N,NY is Y+1));
((X>1,NX is X-1;X<N,NX is X+1), (Y>2,NY is Y-2;Y+1<N,NY is Y+2)).
next(P,Tree,Ps,N):- findall(E,(next(P,E,N), \+ get_assoc(E,Tree,_)),Ps),!.
eval(P,Tree,(X,Y),N):- next(P,Tree,Ps,N),
findall((Len,E),(member(E,Ps),next(E,Tree,Es,N),length(Es,Len)),Us),
sort(Us,Sorted),!,member((S,X,Y),Sorted).
get(_,_,_,0,[]).
get(N,P,Tree,Count,[P2|Sol]) :- eval(P,Tree,P2,N),put_assoc(P2,Tree,_,NTree),
NC is Count-1,get(N,P2,NTree,NC,Sol).
Oder AVL Trees (nur SICStus, dafür aber ca 3 mal schneller als allgemeine Version)
Code:
:- use_module(library(lists)).
:- use_module(library(avl)).
solve(N,S,R) :- avl_store(S,_,S,Tree),Max is N*N-1,get(N,S,Tree,Max,R).
next((X,Y),(NX,NY),N):-
((X>2,NX is X-2;X+1<N,NX is X+2), (Y>1,NY is Y-1;Y<N,NY is Y+1));
((X>1,NX is X-1;X<N,NX is X+1), (Y>2,NY is Y-2;Y+1<N,NY is Y+2)).
next(P,Tree,Ps,N):- findall(E,(next(P,E,N), \+avl_fetch(E,Tree)),Ps),!.
eval(P,Tree,(X,Y),N):- next(P,Tree,Ps,N),
findall((Len,E),(member(E,Ps),next(E,Tree,Es,N),length(Es,Len)),Us),
sort(Us,Sorted),!,member((S,X,Y),Sorted).
get(_,_,_,0,[]).
get(N,P,Tree,Count,[P2|Sol]) :- eval(P,Tree,P2,N), avl_store(P2,Tree,P2,NTree),
NC is Count-1, get(N,P2,NTree,NC,Sol).
__________________ Noch mal, für alle Pseudo-Geeks: 1+1=0. -> 10 wäre Überlauf!
Selig, wer nichts zu sagen hat und trotzdem schweigt.
Mit der stumpfen (aber effektiven) Optimierung + es ist jetzt reines ANSI C (also viel portabler geht nimma). Außerdem nimmts jetzt die Startx- und Starty-Koordinaten als optionales 3. und 4. Argument. (Default ist 0 0)
Code:
#include <stdio.h>
#include <stdlib.h>
#include "stackChar.h"
#define VISITED 1
typedef struct {
int xdim, ydim;
int **f;
int **moves;
} field;
typedef struct stepStruct {
int dx, dy; /* delta x and delta y */
int succ; /* number of valid successors */
} step;
field *getField(int x, int y);
int isValidPos(field *f, int x, int y);
step *getSteps(field *f, int x, int y);
void *xmalloc(int size);
void printField(field *f, int currx, int curry);
void clearField(field *f);
int solve(field *f, stack s, int x, int y, int visited);
int stepsort(const void *a, const void *b);
int main(int argc, char *argv[]) {
int x, y, startx, starty, success;
field *f;
stack solution;
if (argc == 3) {
startx = 0;
starty = 0;
}
else if (argc == 5) {
startx = atoi(argv[3]);
starty = atoi(argv[4]);
}
else {
fprintf(stderr, "usage: %s <x> <y> [<startx> <starty>]\n", argv[0]);
exit(EXIT_FAILURE);
}
x = atoi(argv[1]);
y = atoi(argv[2]);
solution = newStack(x * y * 2);
f = getField(x, y);
success = solve(f, solution, startx, starty, 1);
if (success) {
printf("success\n");
if (x <= 50 && y <= 40) {
clearField(f);
while (!isEmptyStack(solution)) {
x = shiftStack(solution);
y = shiftStack(solution);
f->f[y][x] |= VISITED;
printField(f, x, y);
}
}
}
else {
printf("couldn't find a solution\n");
}
return success ? EXIT_SUCCESS : EXIT_FAILURE;
}
int solve(field *f, stack s, int x, int y, int visited) {
int i;
step *steps;
f->f[y][x] ^= VISITED;
pushStack(s, x);
pushStack(s, y);
if (visited == (f->xdim * f->ydim)) {
return 1;
}
if ((steps = getSteps(f, x, y)) == NULL) {
f->f[y][x] ^= VISITED;
return 0;
}
for (i = 0; i < 8 && steps[i].succ != -1; i++) {
if (solve(f, s, x + steps[i].dx, y + steps[i].dy, visited + 1)) {
return 1;
}
}
popStack(s); /* y */
popStack(s); /* x */
free(steps);
f->f[y][x] ^= VISITED;
return 0;
}
void clearField(field *f) {
int x, y;
for (y = 0; y < f->ydim; y++) {
for (x = 0; x < f->xdim; x++) {
f->f[y][x] = 0;
}
}
}
void printField(field *f, int currx, int curry) {
int x, y;
printf("\n");
for (x = 0; x < f->xdim; x++) {
printf("--");
}
printf("\n");
for (y = 0; y < f->ydim; y++) {
for (x = 0; x < f->xdim; x++) {
if (x == currx && y == curry) {
printf("@ ");
}
else {
printf("%s", f->f[y][x] & VISITED ? "o " : ". ");
}
}
printf("\n");
}
}
field *getField(int x, int y) {
int *rows = xmalloc(x * y * sizeof(int));
int **arr = xmalloc(y * sizeof(int *));
int *move, **moves, xd, yd;
int i, j;
field *f;
for (i = 0; i < y; i++) {
arr[i] = &rows[i * x];
for (j = 0; j < x; j++) {
arr[i][j] = 0;
}
}
f = xmalloc(sizeof(field));
f->f = arr;
f->xdim = x;
f->ydim = y;
/* these are the 8 movement possibilities
* written in the form (xoffset, yoffset):
* 2, 1; 2, -1; -2, 1; -2, -1;
* 1, 2; -1, 2; 1, -2; -1, -2;
*/
move = malloc(2 * 8 * sizeof(int));
moves = malloc(8 * sizeof(int *));
i = 0;
for (xd = -2; xd <= 2; xd++) {
for (yd = -2; yd <= 2; yd++) {
if (xd && yd && (abs(xd) != abs(yd))) {
move[i*2] = xd;
move[(i*2)+1] = yd;
moves[i] = &move[i*2];
i++;
}
}
}
f->moves = moves;
return f;
}
int isValidPos(field *f, int x, int y) {
if (x < 0 || y < 0 || x >= f->xdim || y >= f->ydim) {
return 0;
}
else if (f->f[y][x] & VISITED) {
return 0; /* this one was already visited! */
}
return 1;
}
step *getSteps(field *f, int x, int y) {
int i, j, k, succ;
step *steps = xmalloc(8 * sizeof(step));
j = 0;
for (i = 0; i < 8; i++) {
if (isValidPos(f, x + f->moves[i][0], y + f->moves[i][1])) {
steps[j].dx = f->moves[i][0];
steps[j].dy = f->moves[i][1];
succ = 0;
for (k = 0; k < 8; k++) {
if (
isValidPos(f, x + steps[j].dx + f->moves[k][0], y + steps[j].dy + f->moves[k][1])
) {
succ++;
}
}
steps[j].succ = succ;
j++;
}
}
if (j == 0) {
free(steps);
return NULL;
}
else if (j < 8) {
steps[j].succ = -1;
}
qsort(steps, j, sizeof(step), &stepsort);
return steps;
}
int stepsort(const void *a, const void *b) {
if (( (step *) a)->succ == ( (step *) b)->succ) {
return 0;
}
if (( (step *) a)->succ > ( (step *) b)->succ) {
return 1;
}
else {
return -1;
}
}
void *xmalloc(int size) {
void *ptr = malloc(size);
if (ptr == NULL) {
fprintf(stderr, "not enough memory\n");
exit(EXIT_FAILURE);
}
return ptr;
}
Außerdem ein patch für den char Stack: (hat jetzt eine shift Methode, damit man den "replay" in der richtigen Reihenfolge anschauen kann)
Code:
23a24
> char shiftStack(stack s);
61a63,77
> char shiftStack(stack s) {
> int i;
> char c;
> if (s->pos == -1) {
> return POP_EMPTY;
> }
> c = s->arr[0];
> for (i = 1; i <= s->pos; i++) {
> s->arr[i-1] = s->arr[i];
> }
> s->arr[s->pos] = '\0';
> s->pos--;
> return c;
> }
>
62a79
> char c;
66c83
< char c = s->arr[s->pos];
---
> c = s->arr[s->pos];
Das Feld wird jetzt auch nur noch ausgegeben, wenn n <= 50 und m <= 40 ist. (Größeres ist für die meisten Terminals einfach nicht sinnvoll darstellbar.)
Code:
$ time ./springerproblem 360 360
success
real 0m0.309s
user 0m0.219s
sys 0m0.069s
$ # Vergleich mit Lesco's Lösung:
$ time ./haskellLoesung '(50,50)' '(1,1)' >/dev/null
real 0m0.324s
user 0m0.242s
sys 0m0.010s
$ time ./springerproblem 50 50
success
real 0m0.008s
user 0m0.005s
sys 0m0.003s
CWD's Lösung (zumindest die SWIPL Lösung, denn die konnte ich ausprobieren..) ist nochmal ein paar Dimensionen langsamer. Und bei mir bricht sie bereits bei 100x100 Feldern ab mit "Out of global stack", trotz 500mb stack..
Mein Rechner:
Code:
$ grep 'name' /proc/cpuinfo
model name : Intel(R) Pentium(R) 4 CPU 1.70GHz
$ grep MemTotal /proc/meminfo
MemTotal: 512684 kB
Pff, richtige Männer messen in cm (oder: traue keiner Statistik, die du nicht selbst gefälscht hast )
Erweiterte Heuristik rein: (aus gleichwertigen Zügen werden nun zuerst die zum Rand nächsten abgearbeitet)
SICStus
Zeile 12,14 "Erweiterung" (prädikat border)
Code:
:- use_module(library(lists)).
:- use_module(library(avl)).
solve(N,S,R) :- Max is N*N-1,empty_avl(Tree),get(N,S,Tree,Max,R).
next((X,Y),(NX,NY),N):-
((X>2,NX is X-2;X+1<N,NX is X+2), (Y>1,NY is Y-1;Y<N,NY is Y+1));
((X>1,NX is X-1;X<N,NX is X+1), (Y>2,NY is Y-2;Y+1<N,NY is Y+2)).
successors(P,N,Tree,R):- findall(_,(next(P,E,N), \+avl_fetch(E,Tree)),List),length(List,R),!.
eval(P,Tree,Res,N):-
findall((Len,Dist,E),(next(P,E,N), \+avl_fetch(E,Tree),
successors(E,N,Tree,Len),border(E,N,Dist)),Us),
sort(Us,Sorted),!,member((_,_,Res),Sorted).
border((X,Y),N,R):- R is min(min(X-1,N-X),min(Y-1,N-Y)).
get(_,P,_,0,[P]).
get(N,P,Tree,Count,[P|Res]) :- avl_store(P,Tree,P,NTree),!,eval(P,NTree,P2,N),
NC is Count-1, get(N,P2,NTree,NC,Res).
Heuristikzeitmessungen sind immer problematisch, da die Laufzeit von gewählen Startparametern und z.B Auswahlreihenfolge bei gleichwertigen Zügen abhängig ist ;). Außerdem ist es gegenüber Prolog nicht fair, da aufgrund von Choicepunkten die "nächste" Lösung viel schneller berechnet werden kann:
Stattdessen messen wir mal ein paar 1000 Werte :D
( ms(Zeit) gibt an, zu welchem Zeitpunkt nach dem Programmstart die Lösung verfügbar war).
Messung für 1000 Werte
Die erste Lösung war zwar erst in der 19 Sekunde verfügbar - aber die 10000 ste in der 30ten.
30562/10000 = 3 ms pro Lösung.
Speicherverbrauch ist natürlich ein Problem - aber eher Interpreterbezogen.
Bsp:
100*100 Feld, SICStus
Code:
time2(solve(100,(1,1),R),T),statistics.
R = [(1,1),(2,3),(1,5),(2,7),(1,9),(2,11),(1,13),(2,15),(1,17),(2,19)|...],
T = ms(1280) ?
memory (total) 12739304 bytes
global stack 8713236 bytes: 5443848 in use, 3269388 free
local stack 353016 bytes: 280012 in use, 73004 free
trail stack 260122 bytes: 94364 in use, 165758 free
control stack 525550 bytes: 359792 in use, 165758 free
atoms 115842 bytes: 3843 in use, 1044732 free
program space 2887380 bytes: 1159476 in use, 1727904 free
das ist die Statisitk nach der Ausgabe der ersten Lösung (d.h die Choicepoints für andere sind noch im Speicher). Wie man sieht sind nur 5 MB global stack belegt.
dasselbe mit SWI-Prolog, 32 bits, Version 5.6.55, Windows:
100*100 Feld
SWI Code
nix wildes, nur avl wieder auf associations gestellt.
Code:
:- use_module(library(lists)).
:- use_module(library(avl)).
solve(N,S,R) :- Max is N*N-1,get(N,S,t,Max,R).
next((X,Y),(NX,NY),N):-
((X>2,NX is X-2;X+1<N,NX is X+2), (Y>1,NY is Y-1;Y<N,NY is Y+1));
((X>1,NX is X-1;X<N,NX is X+1), (Y>2,NY is Y-2;Y+1<N,NY is Y+2)).
successors(P,N,Tree,R):- findall(_,(next(P,E,N), \+get_assoc(E,Tree,_)),List),length(List,R).
eval(P,Tree,Res,N):-
findall((Len,Dist,E),(next(P,E,N), \+get_assoc(E,Tree,_),
successors(E,N,Tree,Len),border(E,N,Dist)),Us),
sort(Us,Sorted),!,member((_,_,Res),Sorted).
border((X,Y),N,R):- R is min(min(X-1,N-X),min(Y-1,N-Y)).
get(_,P,_,0,[P]):-!.
get(N,P,Tree,Count,[P|Res]) :- put_assoc(P,Tree,P,NTree),!,eval(P,NTree,P2,N),
NC is Count-1, get(N,P2,NTree,NC,Res).
Code:
6 ?- solve(100,(1,1),R),statistics.
6.44 seconds cpu time for 17,933,062 inferences
4,973 atoms, 3,062 functors, 2,891 predicates, 73 modules, 101,768 VM-codes
Limit Allocated In use
Heap : 1,192,200 Bytes
Local stack : 33,554,432 1,368,064 1,360,660 Bytes
Global stack : 134,217,728 6,287,360 6,285,556 Bytes
Trail stack : 134,217,728 376,832 375,580 Bytes
9 garbage collections gained 8,406,576 bytes in 0.44 seconds.
1 threads, 0 finished threads used 0.00 seconds.
R = [ (1, 1), (2, 3), (1, 5), (2, 7), (1, 9), (2, 11), (1, 13), (2, 15), (..., ...)|...]
Sind zwar etwas mehr - aber auch noch deutlich unter 10 MB.
__________________ Noch mal, für alle Pseudo-Geeks: 1+1=0. -> 10 wäre Überlauf!
Selig, wer nichts zu sagen hat und trotzdem schweigt.