Hackerboard Wiki HaboBlog
Hackerboard bei Facebook Hackerboard bei Google+ Hackerboard bei Twitter

[HaBo]

 
Programmieraufgaben Hier 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) ...

Antwort
Alt 08.02.09, 17:50   #16 (permalink)
CDW
Moderator
 
Benutzerbild von CDW
 
Registriert seit: 20.07.05
CDW Leistung: OpteronCDW Leistung: OpteronCDW Leistung: OpteronCDW Leistung: OpteronCDW Leistung: OpteronCDW Leistung: Opteron
Likes: 202
Standard

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) :- N =:= (X-DX)**2+(Y-DY)**2.

next(P,L,Ps) :- findall(E,(member(E,L),distance(E,P,5)),Ps).

eval(P,L,(X,Y))	:- next(P,L,Ps),
	findall((Len,E),(member(E,Ps),next(E,L,Es),length(Es,Len)),Us),
	sort(Us,Sorted),member((_,X,Y),Sorted).

get(_,[],[]).
get(P,L,[N|Sol]) :- eval(P,L,N),delete(L,N,NL),get(N,NL,Sol).
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.

Ausgabe für 5x5:
spoiler   

Code:
?- solve(5,(1,1),R),write(R).
[(2,3),(1,5),(3,4),(5,5),(4,3),(5,1),(3,2),(1,3),(2,1),(4,2),(5,4),(3,5),(1,4),(2,2),(4,1),(5,3),(4,5),(2,4),(1,2),(3,1),(5,2),(3,3),(2,5),(4,4)]
__________________
Noch mal, für alle Pseudo-Geeks: 1+1=0. -> 10 wäre Überlauf!
Selig, wer nichts zu sagen hat und trotzdem schweigt.
CDW ist offline   Mit Zitat antworten
Alt 08.02.09, 18:09   #17 (permalink)
Senior Member
 
Registriert seit: 03.09.05
Lesco Leistung: Facit NTK
Likes: 0
Standard

Zitat:
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.
Lesco ist offline   Mit Zitat antworten
Alt 09.02.09, 12:30   #18 (permalink)
CDW
Moderator
 
Benutzerbild von CDW
 
Registriert seit: 20.07.05
CDW Leistung: OpteronCDW Leistung: OpteronCDW Leistung: OpteronCDW Leistung: OpteronCDW Leistung: OpteronCDW Leistung: Opteron
Likes: 202
Standard

Zitat:
Original von Lesco Meine Prolog-Unkenntnis hindert mich jedoch daran, zu erkennen, ob das bei deiner Lösung auch schon so gemacht wird.
spoiler   

eigentlich schon:
Code:
eval(P,L,(X,Y))	:- next(P,L,Ps),
	findall((Len,E),(member(E,Ps),next(E,L,Es),length(Es,Len)),Us),
	--->>sort(Us,Sorted),member((_,X,Y),Sorted) <<----.
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.
CDW ist offline   Mit Zitat antworten
Alt 10.02.09, 15:27   #19 (permalink)
 
Registriert seit: 22.03.08
MontyPerl Leistung: Facit NTK
Likes: 0
Standard

Na dann will ich doch auch nochmal
springerproblemReloaded   

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
Genug Sch****vergleich?
MontyPerl ist offline   Mit Zitat antworten
Alt 11.02.09, 18:41   #20 (permalink)
CDW
Moderator
 
Benutzerbild von CDW
 
Registriert seit: 20.07.05
CDW Leistung: OpteronCDW Leistung: OpteronCDW Leistung: OpteronCDW Leistung: OpteronCDW Leistung: OpteronCDW Leistung: Opteron
Likes: 202
Standard

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).

Nun cheaten wir:
cheat 1, SICStus   

Code:
?- time2(solve(50,(0,0),R)).      
 0.609 sec.                <--- vorher
 ?- compile(springer).  <- produziert VM-Code
% compiling e:/home/cdw/eigene dateien/prolog/springer.pl...
% compiled e:/home/cdw/eigene dateien/prolog/springer.pl in module user, 0 msec -416 bytes
yes
| ?- time2(solve(50,(1,1),_)).
 0.281 sec.              <----- nachher
yes
| ?-

cheat2, SICSTus   

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
Code:
| ?- forall(time2(solve(50,(1,1),_),T),(write(T),nl)).
ms(281)
ms(281)
ms(281)
ms(281)
...
995 Antworten später
ms(1750)
1750ms für 1000 Lösungen= 1.7 ms/Lösung
und das 360x360 Brett:
10000 Werte
Code:
| ?- forall(time2(solve(360,(1,1),_),T),(write(T),nl)).
ms(19671)
ms(19671)
ms(19671)
ms(19671)
ms(19671)
... 9994 Lösungen
ms(30562)
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.
CDW ist offline   Mit Zitat antworten
Antwort
   
- Anzeige -

Werbung ist gerade online    

[HaBo] » Software Home » Code Kitchen » Programmieraufgaben » Springerproblem
Themen-Optionen
Ansicht

Forumregeln
Es ist Ihnen nicht erlaubt, neue Themen zu verfassen.
Es ist Ihnen nicht erlaubt, auf Beiträge zu antworten.
Es ist Ihnen nicht erlaubt, Anhänge hochzuladen.
Es ist Ihnen nicht erlaubt, Ihre Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks sind aus
Pingbacks sind aus
Refbacks sind aus


Ähnliche Themen
Thema Autor Forum Antworten Letzter Beitrag
Springerproblem Nornagest Programmieraufgaben 16 19.04.04 16:20


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61