Gere um caminho sem interseção ascii-art

18

Dadas 2 entradas inteiras que representam o tamanho do campo xe y, produz um caminho através do campo.

Exemplo de saída para 5, 4:

#    
#    
# ###
### #

O campo inteiro é 5 por 4, e há um caminho feito de hashmarks cruzando o campo.

O caminho deve sempre começar no canto superior esquerdo e ir para o canto inferior direito. Todo o caminho deve ser randomizado toda vez que o programa é executado. Todo caminho válido deve ser uma saída possível.

As regras para caminhos são:

  • Feito de hashmarks

  • Todo hash é conectado apenas a outros 2 hashes (ou seja, o caminho não se cruza ou corre ao lado dele)

Os espaços não hash podem ser preenchidos com qualquer outro caractere, mas devem ser consistentes (ou seja, todos os espaços, todos os períodos etc.)

Exemplos:

2, 2

##
 #

3, 4

##
 ##
  #
  #

5, 5

#####
    #
    #
    #
    #

6, 5

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

7, 9

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

Esse tipo de caminho é semelhante a um passeio aleatório que evita auto-evitação, no entanto, não pode ser adjacente a si mesmo, diferente de uma verdadeira SAW.

A continuidade e o toque do caminho são definidos sem diagonais.

Rɪᴋᴇʀ
fonte
Formato de saída RBX.Lua válido? ;)
devRicher
É correto que, desde que todo caminho válido tenha uma probabilidade positiva de ser escolhido, a distribuição de probabilidade seja arbitrária?
flawr
@devRicher yeah
Mar
@flawr sim, isso é correto
Rɪᴋᴇʀ

Respostas:

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

Obrigado @LuisMendo por várias sugestões e um monte de bytes =)

Experimente online! (Sem garantia: observe que foram necessários alguns ajustes para executá-lo no Octave: primeiro, preciso remover a functionpalavra - chave e codificar os valores; depois: os espaços não são impressos corretamente como no Matlab. Também não verifique os comandos de convolução do Octave, que podem agir de maneira diferente.)

Exemplo de saída para entrada (7,10)(já pode demorar um pouco):

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

Explicação

Isso gera caminhos seqüencialmente da parte superior esquerda para a parte inferior direita com a conectividade 4 desejada e, em seguida, usa a amostragem de rejeição para rejeitar caminhos que violam o critério de que você não pode ter peças adjecentes.

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

Ah e como sempre:

Convolução é a chave do sucesso.

flawr
fonte
19

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

Experimente online!

Como o @flawr mencionado na resposta do MATLAB, isso pode levar algum tempo se o tamanho do campo for de qualquer tamanho não trivial. Na verdade, é muito fácil entrar em uma situação em que não vale a pena tentar esperar que termine, porque é bem provável que você espere até o fim dos tempos.

Para entender por que isso acontece, é útil visualizar o programa em execução em um dos muitos "depuradores visuais" do Befunge. Como dados e código são a mesma coisa no Befunge, você poderá visualizar o caminho à medida que muda com o tempo. Por exemplo, aqui está uma curta animação mostrando como pode ser uma parte de uma corrida em um caminho lento.

Animação mostrando a construção do caminho ficando preso em um canto

Uma vez que o algoritmo decide fazer aquela fatídica curva para a esquerda na parte inferior do limite do campo, ele essencialmente se condenou a uma vida inteira de perambulação sem objetivo. A partir desse ponto, ele deve seguir todos os caminhos possíveis naquela área cercada antes que possa recuar e tentar a curva à direita. E o número de caminhos em potencial nesses casos pode facilmente se tornar astronômico.

Conclusão: se parece que está demorando muito tempo, provavelmente é uma boa idéia abortar a execução e começar de novo.

Explicação

Este é basicamente um algoritmo recursivo, tentando todos os caminhos possíveis através do campo e, em seguida, desenrolando as etapas que já foram seguidas sempre que ele fica preso. Como o Befunge não tem o conceito de funções, uma função recursiva está fora de questão, mas podemos emular o processo rastreando o estado na pilha.

Isso funciona preenchendo a pilha com possíveis coordenadas que podemos seguir. Depois, puxamos um conjunto da pilha e verificamos se ele é adequado (ou seja, dentro do intervalo e não se sobrepõe a um caminho existente). Depois de termos um bom lugar, escrevemos um #no campo de jogo nesse local e adicionamos esses detalhes à pilha, caso precisemos voltar mais tarde.

Em seguida, colocamos mais quatro conjuntos de coordenadas na pilha (em ordem aleatória), indicando os possíveis caminhos que podemos seguir a partir desse novo local e voltamos ao início do loop. Se nenhum dos caminhos possíveis for possível, chegaremos ao ponto da pilha em que salvamos o local da #gravação, então desfazeremos essa etapa e continuaremos tentando coordenadas em potencial a partir de uma etapa anterior.

É assim que o código se parece com as várias partes do componente destacadas:

Código fonte com caminhos de execução destacados

*Leia a largura e a altura do campo e empurre as coordenadas de início junto com um 0marcador de tipo para indicar um caminho potencial em vez de um local de retorno.
*Verifique se há locais de retorno (indicados por um 1marcador de tipo) que são revertidos com um pcomando simples , pois eles são armazenados no formato exato necessário para gravar um espaço no campo de jogo.
*Verifique se as coordenadas ainda estão dentro do campo de jogo. Se estiverem fora do alcance, solte-os da pilha e retorne para tentar as próximas coordenadas em potencial.
*Se estiverem dentro do intervalo, obtenha os dois próximos valores da pilha, que é o local da etapa anterior (necessária no teste a seguir).
*Verifique se as coordenadas entrarão em contato com um segmento existente do caminho. A localização da etapa anterior é obviamente ignorada nessa verificação.
*Se todos os testes forem bem-sucedidos, escreva um #no campo de jogo e verifique se chegamos ao local de destino.
*Se tivermos, escreva o caminho final *e saia.
*Caso contrário, salve as coordenadas na pilha com um 1marcador de tipo para retroceder posteriormente.
*Isso é interrompido com um cálculo de número aleatório que precisaremos em breve.
*Envie quatro destinos em potencial que podem ser alcançados a partir do local atual. O número aleatório determina a ordem em que eles são enviados e, portanto, a ordem em que serão seguidos.
* Volte ao início do loop principal e processe os próximos valores na pilha.

James Holderness
fonte
2
Vaca sagrada. Explicação?
Rɪᴋᴇʀ
@EasterlyIrk Obrigado pela recompensa. Isso é muito apreciado.
precisa saber é o seguinte
Ainda bem que foi útil!
Rɪᴋᴇʀ
2

QBasic, 259 bytes

Eu com certeza amo GOTO.

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
?"#"

Estratégia básica: em cada etapa, imprima #a no local atual e mova-se em uma direção aleatória. Uma matriz ade 0s e 1s mantém o controle de onde estivemos. Se a mudança for legal e nos levar ao terminal, GOTO 9para sair do loop e imprimir a final #. Caso contrário, se a mudança for legal, dê outro passo. Senão, limpe a tela e comece de novo (muito mais golfe do que codificar um algoritmo de retorno!).

Testado no meu laptop no QB64, isso geralmente produz um resultado 9, 9em cinco segundos ou menos. As execuções 10, 10demoraram entre três e 45 segundos. Teoricamente, todos os caminhos legais têm probabilidade diferente de zero, mas a probabilidade de um caminho com grandes curvas é muito pequena. Ocasionalmente, vi caminhos com uma ou duas pequenas curvas:

Caminho da amostra

Versão ungolfed e / ou explicação detalhada disponíveis mediante solicitação.

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

Explicação:

Geramos um gráfico não-direcionado (reticulado) [x * y] regular com pesos aleatórios nas arestas e, em seguida, encontramos o caminho mais curto do início ao fim. No entanto, no caminho gerado, pode haver células que tenham mais de dois vizinhos, por exemplo:

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

Portanto, devemos aplicar o algoritmo de caminho mais curto duas vezes. Na segunda vez, definimos todos os pesos para 1, exceto aqueles que estão no caminho encontrado atual e definido como 0;

resultado

#
#
### 
  # 
  #
  ###

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
fonte
1

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

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>

Expansão: #s são colocados aleatoriamente na matriz até que um caminho seja encontrado através do campo usando uma pesquisa pela primeira vez; o primeiro e, portanto, o caminho mais curto, é produzido; isso garante que o caminho não se cruze. Observe que, especialmente para campos maiores, é possível exceder a pilha do mecanismo JS antes que um caminho seja encontrado. 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
fonte