Generieren Sie einen sich nicht überschneidenden Ascii-Art-Pfad

18

Bei 2 Ganzzahleingaben, die die Größe des Feldes darstellen, xund yeinen Pfad durch das Feld ausgeben.

Beispielausgabe für 5, 4:

#    
#    
# ###
### #

Das gesamte Feld ist 5 mal 4 groß, und es gibt einen Pfad aus Hash-Markierungen, der das Feld kreuzt.

Der Pfad sollte immer in der oberen linken Ecke beginnen und nach rechts unten verlaufen. Der gesamte Pfad sollte bei jeder Ausführung des Programms zufällig ausgewählt werden. Jeder gültige Pfad sollte eine mögliche Ausgabe sein.

Die Regeln für Pfade sind:

  • Hergestellt aus Hashmarks

  • Jeder Hash ist nur mit 2 anderen Hashes verbunden (dh der Pfad schneidet sich nicht und verläuft nicht neben sich selbst)

Die Nicht-Hash-Leerzeichen können mit jedem anderen Zeichen gefüllt werden, müssen jedoch konsistent sein (dh alle Leerzeichen, alle Punkte usw.).

Beispiele:

2, 2

##
 #

3, 4

##
 ##
  #
  #

5, 5

#####
    #
    #
    #
    #

6, 5

## ###
 # # #
## # #
# ## #
###  #

7, 9

#######
      #
####  #
#  #  #
#  #  #
#  #  #
#  ####
#
#######

Diese Art von Pfad ähnelt einem selbstvermeidenden zufälligen Spaziergang, kann jedoch im Gegensatz zu einer echten SAW nicht an sich selbst angrenzen.

Pfadkontinuität und Pfadberührung werden beide ohne Diagonalen definiert.

Rɪᴋᴇʀ
quelle
RBX.Lua Ausgabeformat gültig? ;)
devRicher
Ist es richtig, dass die Wahrscheinlichkeitsverteilung willkürlich ist, solange jeder gültige Pfad eine positive Wahrscheinlichkeit hat, gewählt zu werden?
Fehler
@devRicher yeah
Rɪᴋᴇʀ
@flawr ja, das ist richtig
Rɪᴋᴇʀ

Antworten:

11

MATLAB, 316 305 300 293 Bytes

function P=f(a,b);z(a,b)=0;P=z;c=@(X)conv2(+X,[0,1,0;1,1,1;0,1,0],'s');B=1;while B;P=z;P(1)=1;for K=1:a*b;S=z;S(a,b)=1;for L=2:a*b;S(~S&c(S)&~P)=L;end;[i,j]=find(S&c(P==K));if i;r=randi(nnz(i));else;break;end;P(i(r),j(r))=K+1;if P(a,b);break;end;end;B=any(any(c(P>0)>3));end;P(P>0)=35;P=[P,'']

Danke @LuisMendo für verschiedene Vorschläge und ein paar Bytes =)

Probieren Sie es online! (Ohne Gewähr: Beachten Sie, dass einige Anpassungen erforderlich waren, damit es auf Octave ausgeführt werden konnte: Erstens musste ich das functionSchlüsselwort entfernen und die Werte fest codieren, zweitens: Die Leerzeichen werden nicht richtig gedruckt wie in Matlab. Auch das habe ich nicht getan Überprüfen Sie die Faltungsbefehle von Octave, die sich möglicherweise anders verhalten.)

Beispielausgabe für Eingabe (7,10)(kann schon eine Weile dauern):

#         
#         
##        
 ##       
  #   ### 
  #   # ##
  #####  #

Erläuterung

Dadurch werden nacheinander Pfade von links oben nach rechts unten mit der gewünschten 4-Konnektivität generiert. Anschließend werden mithilfe der Zurückweisungsabtastung Pfade zurückgewiesen, die gegen das Kriterium verstoßen, dass Sie keine angrenzenden Teile haben können.

function P=f(a,b);
z(a,b)=0;                                 % a matrix of zeros of the size of th efield
P=z;                                    
c=@(X)conv2(+X,[0,1,0;1,1,1;0,1,0],'s');  % our convolution function, we always convolute with the same 4-neighbourhood kernel
B=1;
while B;                                  % while we reject, we generate paths:
    P=z;
    P(1)=1;                               % P is our path, we place the first seed
    for K=1:a*b;                          % in this loop we generate the all shortest paths (flood fill) from the bottom right, withot crossing the path to see what fiels are reachable from the bottom left
        S=z;
        S(a,b)=1;                         % seed on the bottom left
        for L=2:a*b;
            S(~S&c(S)&~P)=L;              % update flood front
        end;
        [i,j]=find(S&c(P==K));            % find a neighbour of the current end of the path, that is also reachable from the bottom left
        if i;                             % if we found some choose a random one
            r=randi(nnz(i));
        else;
            break;                        % otherwise restart (might not be necessary, but I'm too tired to think about it properly=)
        end;
        P(i(r),j(r))=K+1;                 % update the end of the current path
        if P(a,b);                        % if we finished, stop continuing this path
            break;
        end;
    end;
    B=any(any(c(P>0)>3));                 % check if we actually have a valid path
end;
P(P>0)=35;                                % format the path nicely
P=[P,''];

Oh und wie immer:

Faltung ist der Schlüssel zum Erfolg.

fehlerhaft
quelle
19

Befunge, 344 Bytes

&v>>>#p_:63p:43g`\!+v>/*53g+\01g:2%2*1-\2/!*63g+\0\:v
 40$ v++!\`g14:p35:\<^2\-1*2%2p10::%4+g00:\g36\g35-1_v
#11^$_83p73v >1+:41g`!#v_$,1+:43g`!#v_@>->2>+00p+141^_
<p1^     vp< ^,g+7g36:<<<<1+55p36:<<<< ^1?0^#7g36g35*
8&p|!++!%9#2g+7g10\*!-g38g10!-g37:g00!!*<>3^
443>:!#v_>>1-::3%1-:53g+00p\3/1-:63g+01p^
^>^>>$#<"#"53g63g7+p41g53g-43g63g-+!#^_

Probieren Sie es online!

Wie in der MATLAB-Antwort von @flawr erwähnt, kann dies einige Zeit in Anspruch nehmen, wenn die Feldgröße nicht trivial ist. Tatsächlich ist es ziemlich einfach, in eine Situation zu geraten, in der es sich nicht lohnt, auf das Ende zu warten, da Sie mit ziemlicher Wahrscheinlichkeit bis zum Ende der Zeit warten.

Um zu verstehen, warum dies geschieht, ist es hilfreich, das Programm so zu betrachten, wie es in einem der vielen "visuellen Debugger" von Befunge ausgeführt wird. Da Daten und Code in Befunge dasselbe sind, können Sie den Pfad anzeigen, wie er sich im Laufe der Zeit ändert. Hier ist zum Beispiel eine kurze Animation, die zeigt, wie ein Teil eines Laufs auf einem langsamen Pfad aussehen könnte.

Animation, die den Wegebau zeigt, der sich in einer Ecke festsetzt

Sobald der Algorithmus diese schicksalhafte Wende nach links unten an der Feldgrenze beschließt, hat er sich im Wesentlichen zu einem Leben des ziellosen Wanderns verurteilt. Von diesem Punkt an muss es jedem einzelnen möglichen Pfad in diesem eingezäunten Bereich folgen, bevor es sich zurückziehen und die Abzweigung nach rechts versuchen kann. Und die Anzahl der möglichen Pfade in diesen Fällen kann leicht astronomisch werden.

Fazit: Wenn es lange zu dauern scheint, ist es wahrscheinlich eine gute Idee, die Ausführung abzubrechen und erneut zu beginnen.

Erläuterung

Dies ist im Grunde ein rekursiver Algorithmus, der jeden möglichen Pfad durch das Feld ausprobiert und dann Schritte abwickelt, die bereits befolgt wurden, wenn es hängen bleibt. Da Befunge nicht über das Konzept von Funktionen verfügt, kommt eine rekursive Funktion nicht in Frage, aber wir können den Prozess emulieren, indem wir den Status auf dem Stapel verfolgen.

Dies funktioniert, indem der Stapel mit potenziellen Koordinaten gefüllt wird, denen wir folgen möchten. Dann ziehen wir einen Satz vom Stapel und prüfen, ob er geeignet ist (dh in Reichweite und nicht überlappend mit einem vorhandenen Pfad). Sobald wir einen guten Platz haben, schreiben wir einen# in das Spielfeld an diesem Ort und fügen diese Details dem Stapel hinzu, falls wir später zurückgehen müssen.

Anschließend legen wir weitere vier Sätze von Koordinaten (in zufälliger Reihenfolge) auf den Stapel und geben die möglichen Pfade an, die wir von diesem neuen Ort aus nehmen können, und springen zum Anfang der Schleife zurück. Wenn keiner der möglichen Pfade durchführbar ist, gelangen wir zu dem Punkt auf dem Stapel, an dem wir die Position der ausgeschriebenen Pfade gespeichert haben. #Machen Sie diesen Schritt rückgängig und versuchen Sie die potenziellen Koordinaten erneut ab einem Schritt vor dem anderen.

So sieht der Code mit den verschiedenen hervorgehobenen Komponenten aus:

Quellcode mit hervorgehobenen Ausführungspfaden

*Lesen Sie die Breite und Höhe des Feldes und drücken Sie die Startkoordinaten zusammen mit einer 0Typmarkierung, um einen potenziellen Pfad anstelle eines Rückverfolgungsorts anzugeben.
*Suchen Sie nach Backtracking-Positionen (gekennzeichnet durch eine 1Typmarkierung), die mit einem einfachen pBefehl zurückgesetzt werden, da sie in dem genauen Format gespeichert sind, das zum Zurückschreiben eines Leerzeichens in das Spielfeld erforderlich ist.
*Überprüfen Sie, ob sich die Koordinaten noch im Spielfeld befinden. Wenn sie außerhalb der Reichweite liegen, lassen Sie sie vom Stapel fallen und kehren Sie zurück, um die nächsten potenziellen Koordinaten zu ermitteln.
*Wenn sie sich im Bereich befinden, erhalten Sie die nächsten beiden Werte vom Stapel. Dies ist der Speicherort des vorherigen Schritts (erforderlich für den folgenden Test).
*Überprüfen Sie, ob die Koordinaten mit einem vorhandenen Streckenabschnitt in Kontakt kommen. Die Position des vorherigen Schritts wird bei dieser Prüfung offensichtlich ignoriert.
*Wenn alle Tests erfolgreich sind, schreiben Sie eineSchieben Sie vier potenzielle Ziele, die vom aktuellen Standort aus erreicht werden können. Die Zufallszahl bestimmt die Reihenfolge, in der sie gepusht werden, und damit die Reihenfolge, in der sie befolgt werden.#in das Spielfeld, und überprüfen Sie, ob wir den Zielort erreicht haben.
*Wenn ja, schreiben Sie den endgültigen Pfad auf *und beenden Sie das Programm.
*Andernfalls speichern Sie die Koordinaten mit einer 1Typmarkierung auf dem Stapel, um sie später zurückzuverfolgen.
*Dies wird durch eine Zufallszahlenberechnung unterbrochen, die wir bald brauchen werden.
*
* Gehen Sie zurück zum Anfang der Hauptschleife und verarbeiten Sie die nächsten Werte auf dem Stapel.

James Holderness
quelle
2
Heilige Kuh. Erläuterung?
14.
@EasterlyIrk Danke für das Kopfgeld. Es wird sehr geschätzt.
James Holderness
Froh, dass es nützlich war!
25.
2

QBasic, 259 Bytes

Ich liebe GOTOs.

RANDOMIZE TIMER
INPUT w,h
DO
CLS
x=1
y=1
REDIM a(w+3,h+3)
2a(x+1,y+1)=1
LOCATE y,x
?"#"
d=INT(RND*4)
m=1AND d
x=x+m*(d-2)
y=y-d*m+m+d-1
c=a(x,y+1)+a(x+2,y+1)+a(x+1,y)+a(x+1,y+2)=1
IF(x=w)*c*(y=h)GOTO 9
IF(x*y-1)*x*y*c*(x<=w)*(y<=h)GOTO 2
LOOP
9LOCATE y,x
?"#"

Grundlegende Strategie: Drucken Sie bei jedem Schritt ein #an die aktuelle Position und bewegen Sie sich in eine zufällige Richtung. Ein Array avon Nullen und Einsen zeichnet auf, wo wir waren. Wenn der Umzug legal ist und uns zum Endpunkt bringt, GOTO 9beenden Sie die Schleife und drucken Sie das Finale aus# . Andernfalls, wenn der Umzug legal ist, machen Sie einen weiteren Schritt. Anderenfalls den Bildschirm leeren und von vorne beginnen (viel besser als das Codieren eines Backtracking-Algorithmus!).

Auf meinem Laptop in QB64 getestet, führt dies in der Regel in höchstens 9, 9fünf Sekunden zu einem Ergebnis . Läufe 10, 10haben zwischen drei und 45 Sekunden gedauert. Theoretisch haben alle legalen Pfade eine Wahrscheinlichkeit ungleich Null, aber die Wahrscheinlichkeit eines Pfades mit großen Kurven ist verschwindend gering. Ich habe gelegentlich Pfade mit ein oder zwei kleinen Kurven gesehen:

Beispielpfad

Ungolfed-Version und / oder ausführliche Erklärung auf Anfrage erhältlich.

DLosc
quelle
2

R 225 Bytes

function(x,y){library(igraph);a=matrix(rep(" ",x*y),c(y,x));g=make_lattice(c(y,x));w=runif(ecount(g));for (i in 1:2){v=shortest_paths(g,1,x*y,weights=w)$vpath[[1]];w[]=1;w[E(g)[v%--%v]]=0;};a[v]="#";cat(rbind(a,"\n"),sep="")}

Erläuterung:

Wir erzeugen einen regelmäßigen (Gitter-) [x * y] ungerichteten Graphen mit zufälligen Kantengewichten und finden dann den kürzesten Weg vom Anfang bis zum Ende. In dem generierten Pfad können sich jedoch Zellen befinden, die mehr als zwei Nachbarn haben, zum Beispiel:

#
#
####
  ##
  #
  ###

Wir sollten also zweimal den Algorithmus mit dem kürzesten Pfad anwenden. Beim zweiten Mal setzen wir alle Wertigkeiten auf 1, mit Ausnahme derjenigen, die sich im aktuell gefundenen Pfad befinden und auf 0 gesetzt sind.

Ergebnis

#
#
### 
  # 
  #
  ###

Ungolfed:

function(x,y){
    library(igraph);#igraph library should be installed
    a=matrix(rep(" ",x*y),c(y,x));#ASCII representation of the graph
    g=make_lattice(c(y,x));# regular graph
    w=runif(ecount(g));#weights
    for (i in 1:2){
        #find vertices that are in the path
        v=shortest_paths(g,1,x*y,weights=w)$vpath[[1]];
        #set all weights to 1 except those that are in the current found path that set to 0
        w[]=1;
        w[E(g)[v%--%v]]=0;
    }
    a[v]="#";
    cat(rbind(a,"\n"),sep="")
}
rahnema1
quelle
1

JavaScript (ES7), 333 331 330 329 324 318 312 Byte

f=
(h,w,c=[...Array(h)].map(_=>Array(w).fill` `),g=a=>{for(z of b=[[[h-1,w-1]]])a.map(([x,y])=>b.every(([[i,j]])=>i-x|j-y)&(z[0][0]-x)**2+(z[0][1]-y)**2<2&&b.push([[x,y],...z]));return b.find(([[x,y]])=>!x&!y)||g([...a,[h,w].map(n=>Math.random()*n|0)])})=>g([]).map(([x,y])=>c[x][y]=`#`)&&c.map(a=>a.join``).join`
`
Height: <input type=number min=1 id=h>Width: <input type=number min=1 id=w><input type=button value="Generate!" onclick=o.textContent=f(+h.value,+w.value)><pre id=o>

Erweiterung: #s werden zufällig im Array platziert, bis mit einer Breitensuche ein Pfad durch das Feld gefunden wird; der erste und daher kürzeste derartige Pfad wird dann ausgegeben; Dies garantiert, dass sich der Pfad nicht selbst schneidet. Beachten Sie, dass es insbesondere bei größeren Feldern möglich ist, den Stack der JS-Engine zu überschreiten, bevor ein Pfad gefunden wird. Ungolfed:

function r(n) {
    return Math.floor(Math.random() * n);
}
function f(h, w) {
    var a = []; // array of placed #s
    var b; // breadth-first search results
    var c;
    do {
        a.push([r(h), r(w)]); // place a # randomly
        b = [[[h - 1, w - 1]]]; // start from bottom right corner
        for (z of b) // breadth-first search
            for ([x, y] of a) // find possible next steps
                if (!b.some(([[i, j]]) => i == x && j == y))
                    if ((z[0][0] - x) ** 2 + (z[0][1] - y) ** 2 < 2)
                        if (x || y)
                            b.push([[x, y], ...z]); // add to search
                        else if (!c)
                            c = [[x, y], ...z]; // found path
    } while (!c);
    a = [...Array(h)].map(() => Array(w).fill(' '));
    for ([x, y] of c) // convert path to output
        a[x][y] = '#';
    return a.map(b => b.join('')).join('\n');
}
Neil
quelle