Genera un percorso non intersecante ascii-art


18

Dato 2 input interi che rappresentano la dimensione del campo xe y, output un percorso attraverso il campo.

Esempio di output per 5, 4:

#    
#    
# ###
### #

L'intero campo è 5 per 4 e c'è un percorso fatto di hashmark che attraversano il campo.

Il percorso dovrebbe sempre iniziare nell'angolo in alto a sinistra e andare in fondo a destra. L'intero percorso deve essere randomizzato ogni volta che viene eseguito il programma. Ogni percorso valido dovrebbe essere un possibile output.

Le regole per i percorsi sono:

  • Fatto di hashmarks

  • Ogni hash è collegato solo ad altri 2 hash (cioè il percorso non si interseca o corre lungo se stesso)

Gli spazi non hash possono essere riempiti con qualsiasi altro carattere, ma deve essere coerente (cioè tutti gli spazi, tutti i periodi, ecc.)

Esempi:

2, 2

##
 #

3, 4

##
 ##
  #
  #

5, 5

#####
    #
    #
    #
    #

6, 5

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

7, 9

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

Questo tipo di percorso è simile a una camminata casuale che si auto-evita, tuttavia non può essere adiacente a se stesso diversamente da un vero SAW.

La continuità del percorso e il tocco del percorso sono entrambi definiti senza diagonali.


Formato di output RBX.Lua valido? ;)
devRicher

È corretto che fino a quando ogni percorso valido ha una probabilità positiva di essere scelto, la distribuzione della probabilità è arbitraria?
flawr

@devRicher yeah
Rɪᴋᴇʀ

@flawr sì, è corretto
Rɪᴋᴇʀ

Risposte:


11

MATLAB, 316 305 300 293 byte

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,'']

Grazie @LuisMendo per vari suggerimenti e un sacco di byte =)

Provalo online! (Senza garanzia: si noti che sono necessarie alcune modifiche per farlo funzionare su Octave: prima di tutto ho dovuto rimuovere la functionparola chiave e codificare i valori, in secondo luogo: gli spazi non vengono stampati correttamente come in Matlab. Inoltre controlla i comandi di convoluzione di Octave, che potrebbero agire diversamente.)

Esempio di output per input (7,10)(può già richiedere parecchio tempo):

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

Spiegazione

Ciò genera sequenzialmente percorsi dalla parte superiore sinistra a quella inferiore destra con la connettività 4 desiderata, quindi utilizza il campionamento del rifiuto per rifiutare i percorsi che violano il criterio secondo cui non è possibile avere parti adiacenti.

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 e come sempre:

La convoluzione è la chiave del successo.


19

Befunge, 344 byte

&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-+!#^_

Provalo online!

Come menzionato da @flawr nella loro risposta MATLAB, ciò potrebbe richiedere del tempo se la dimensione del campo è di dimensioni non banali. In effetti è abbastanza facile entrare in una situazione in cui non vale davvero la pena provare ad aspettare che finisca, perché è molto probabile che aspetti fino alla fine dei tempi.

Per capire perché ciò accada, è utile visualizzare il programma mentre viene eseguito in uno dei tanti "debugger visivi" di Befunge. Dato che dati e codice sono la stessa cosa in Befunge, vedrai il percorso che cambia nel tempo. Ad esempio, ecco una breve animazione che mostra come potrebbe apparire una parte di una corsa su un percorso lento.

Animazione che mostra la costruzione del percorso che si blocca in un angolo

Una volta che l'algoritmo decide di compiere quella fatidica svolta a sinistra nella parte inferiore del confine del campo, si è sostanzialmente condannato a una vita di vagabondaggio senza scopo. Da quel momento in poi deve seguire ogni singolo percorso possibile in quella zona recintata prima di poter uscire e provare a girare a destra. E il numero di potenziali percorsi in questi casi può facilmente diventare astronomico.

In conclusione: se sembra impiegare molto tempo, è probabilmente una buona idea semplicemente interrompere l'esecuzione e ricominciare.

Spiegazione

Questo è fondamentalmente un algoritmo ricorsivo, che prova ogni possibile percorso attraverso il campo e quindi svolge i passaggi che sono già stati seguiti ogni volta che si blocca. Poiché Befunge non ha il concetto di funzioni, una funzione ricorsiva è fuori discussione, ma possiamo emulare il processo monitorando lo stato nello stack.

Funziona popolando lo stack con potenziali coordinate che potremmo voler seguire. Quindi estraiamo un set dallo stack e controlliamo se è adatto (cioè nel raggio d'azione e non si sovrappone a un percorso esistente). Una volta che abbiamo un buon posto, scriviamo un #campo di gioco in quella posizione e aggiungiamo quei dettagli allo stack nel caso in cui dovessimo tornare indietro in seguito.

Spingiamo quindi ulteriori quattro serie di coordinate sullo stack (in ordine casuale) indicando i potenziali percorsi che possiamo prendere da questa nuova posizione e torniamo indietro all'inizio del loop. Se nessuno dei potenziali percorsi è possibile, arriveremo al punto in cui abbiamo salvato la posizione dello #scritto, quindi annulleremo quel passaggio e continueremo a provare le potenziali coordinate da un passaggio precedente.

Ecco come appare il codice con le varie parti componenti evidenziate:

Codice sorgente con percorsi di esecuzione evidenziati

*Leggere la larghezza e l'altezza del campo e premere le coordinate di inizio insieme a un 0indicatore di tipo per indicare un percorso potenziale anziché una posizione di backtracking.
*Controlla le posizioni di backtracking (indicate da un 1marcatore di tipo) che vengono ripristinate con un semplice pcomando, poiché sono memorizzate nello stesso formato necessario per riscrivere uno spazio nel campo di gioco.
*Controlla se le coordinate sono ancora all'interno del campo di gioco. Se sono fuori portata, rilasciarli dallo stack e tornare indietro per provare le coordinate potenziali successive.
*Se sono nell'intervallo, ottenere i due valori successivi dallo stack, che è la posizione del passaggio precedente (richiesto nel test che segue questo).
*Controlla se le coordinate entreranno in contatto con un segmento esistente del percorso. La posizione del passaggio precedente viene ovviamente ignorata da questo controllo.
*Se tutti i test hanno esito positivo, scrivi a #nel campo di gioco e controlla se abbiamo raggiunto la posizione di destinazione.
*Se lo abbiamo, scrivi il percorso finale *ed esci.
*In caso contrario, salvare le coordinate nello stack con un 1marcatore di tipo per tornare indietro.
*Questo viene interrotto con un calcolo casuale dei numeri di cui avremo presto bisogno.
*Spingere quattro potenziali destinazioni che possono essere raggiunte dalla posizione corrente. Il numero casuale determina l'ordine in cui vengono inseriti e quindi l'ordine in cui verranno seguiti.
* Torna all'inizio del ciclo principale ed elabora i valori successivi nello stack.


2
Mucca sacra. Spiegazione?
Rɪᴋᴇʀ

@EasterlyIrk Grazie per la generosità. È molto apprezzato
James Holderness,

Sono contento che sia stato utile!
Rɪᴋᴇʀ

2

QBasic, 259 byte

Di certo amo 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
?"#"

Strategia di base: ad ogni passaggio, stampare #a nella posizione corrente e spostarsi in una direzione casuale. Un array adi 0 e 1 tiene traccia di dove siamo stati. Se la mossa è legale e ci porta all'endpoint, GOTO 9per uscire dal loop e stampare la finale #. Altrimenti, se la mossa è legale, fai un altro passo. Altrimenti, cancella lo schermo e ricomincia (molto più golfoso della codifica di un algoritmo di backtracking!).

Testato sul mio laptop in QB64, questo generalmente produce un risultato per 9, 9cinque secondi o meno. Le esecuzioni 10, 10sono durate da tre a 45 secondi. Teoricamente, tutti i percorsi legali hanno probabilità diverse da zero, ma la probabilità di un percorso con curve grandi è vanificante. Di tanto in tanto ho visto percorsi con una o due piccole curve:

Percorso di esempio

Versione non golfata e / o spiegazione approfondita disponibili su richiesta.


2

R, 225 byte

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="")}

Spiegazione:

Generiamo un grafico normale (reticolare) [x * y] non indirizzato con pesi casuali sui bordi, quindi troviamo il percorso più breve dall'inizio alla fine. Tuttavia, nel percorso generato potrebbero essere presenti celle con più di due vicini, ad esempio:

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

Quindi dovremmo applicare l'algoritmo del percorso più breve due volte. Nella seconda volta impostiamo tutti i pesi su 1 tranne quelli che si trovano nel percorso trovato corrente impostato su 0;

risultato

#
#
### 
  # 
  #
  ###

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="")
}

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>

Espansione: gli #s vengono posizionati casualmente nell'array fino a quando non viene trovato un percorso attraverso il campo usando una ricerca in ampiezza; viene quindi emesso il primo, e quindi il più breve, percorso; questo garantisce che il percorso non si intersechi. Si noti che in particolare per i campi più grandi è possibile superare lo stack del motore JS prima di trovare un percorso. 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');
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.