生成一个ascii-art不相交的路径


18

给定2个代表该字段大小的整数输入,xy输出通过该字段的路径。

输出示例5, 4

#    
#    
# ###
### #

整个字段是5 x 4,并且有一个由井号组成的路径穿过该字段。

路径应始终从左上角开始,然后到右下角。每次运行程序时,应将整个路径随机化。每个有效路径都应该是可能的输出。

路径规则为:

  • 由井号组成

  • 每个哈希仅连接到其他2个哈希(即路径不相交或与之并行)

非哈希空格可以用其他任何字符填充,但必须一致(即所有空格,所有句点等)。

例子:

2, 2

##
 #

3, 4

##
 ##
  #
  #

5, 5

#####
    #
    #
    #
    #

6, 5

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

7, 9

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

这种路径类似于自我规避的随机行走,但是与真正的声表面波不同,它不能与自身相邻。

路径连续性和路径接触都没有对角线。


RBX.Lua输出格式有效吗?;)
devRicher

只要选择每条有效路径的概率都是正的,概率分布是任意的,是否正确?
瑕疵

@devRicher耶
Rɪᴋᴇʀ

@flawr啊,这是正确的
Rɪᴋᴇʀ

Answers:


11

MATLAB,316个305 300 293字节

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

感谢@LuisMendo的各种建议和一堆字节=)

在线尝试!(没有保修:请注意,需要一些调整才能使其在Octave上运行:首先,我需要删除function关键字并对值进行硬编码,其次:在Matlab中,空格无法正确打印。同样,我也没有这样做检查八度的卷积命令,这可能会有所不同。)

输入的示例输出(7,10)(可能已经花了一段时间):

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

说明

这将以所需的4连通性从左上到右下依次生成路径,然后使用拒绝采样拒绝确实违反了不能包含相邻部分的条件的路径。

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

哦,一如既往:

卷积是成功的关键。


19

Befunge,344字节

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

在线尝试!

正如@flawr在其MATLAB回答中提到的那样,如果字段大小为任何非平凡的大小,则可能需要一些时间。实际上,很容易陷入一种情况,即实际上不值得等待它完成,因为您很有可能要等到时间结束。

为了理解为什么会发生这种情况,在Befunge的许多“可视调试器”之一中查看程序的执行情况很有帮助。由于数据和代码在Befunge中是同一回事,因此您可以查看路径随时间变化的情况。例如,这是一个简短的动画,显示了在慢速道路上跑步的一部分可能看起来像什么。

动画显示路径构造陷入困境

一旦算法决定在场边界的底部向左转,那么它就注定要终生漫无目的的徘徊。从那时起,它必须遵循该围栏区域中的所有可能路径,然后才能退出并尝试向右转。在这种情况下,潜在路径的数量很容易变成天文数字。

底线:如果似乎要花很长时间,则最好中止执行并重新开始。

说明

这基本上是一种递归算法,尝试遍历该字段的所有可能路径,然后在遇到任何困难时解开已经遵循的步骤。由于Befunge没有函数的概念,因此递归函数是不可能的,但是我们可以通过跟踪堆栈上的状态来模拟过程。

这可以通过使用可能要遵循的潜在坐标填充堆栈来实现。然后我们从堆栈中拉出一组,并检查它是否合适(即在范围内且不与现有路径重叠)。找到好位置后,我们将a #写入该位置的运动场中,并将这些详细信息添加到堆栈中,以备日后回溯之用。

然后,我们将另外四组坐标(以随机顺序)推入堆栈,以指示可以从该新位置获取的潜在路径,然后跳回到循环的起点。如果所有可能的路径都不可行,那么我们将到达堆栈上保存了#写出的位置的位置,因此我们将撤消该步骤并继续尝试从先前的步骤开始的潜在坐标。

这是代码的样子,突出显示了各个组成部分:

突出显示执行路径的源代码

*读取字段的宽度和高度,然后将开始坐标和0类型标记一起推入,以指示可能的路径而不是回溯位置。
*检查通过1简单p命令还原的回溯位置(由类型标记指示),因为它们以将空间写回到游戏场所需的确切格式存储。
*检查坐标是否仍在运动场内。如果它们超出范围,则将它们从堆栈中删除并循环返回以尝试下一个潜在的坐标。
*如果它们在范围内,请从堆栈中获取下两个值,这是上一步的位置(此步骤之后的测试中要求)。
*检查坐标是否将与路径的现有线段接触。此检查显然会忽略上一步的位置。
*如果所有测试均成功,#则将“ a” 写入运动场,并检查我们是否已到达目标位置。
*如果有,写出最终路径,*然后退出。
*否则,使用1类型标记将坐标保存到堆栈中,以供以后回溯。
*这被随机数计算打断了,我们很快将需要它。
*推送可以从当前位置到达的四个潜在目的地。随机数确定了它们被推入的顺序,从而决定了它们被遵循的顺序。
* 包装回到主循环的开始并处理堆栈中的下一个值。


2
天啊。说明?
Rɪᴋᴇʀ

@EasterlyIrk谢谢您的赏赐。非常感谢。
James Holderness

很高兴这很有用!
Rɪᴋᴇʀ

2

QBasic,259个字节

我肯定爱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
?"#"

基本策略:在每个步骤中,将a打印#到当前位置并向随机方向移动。a0和1组成的数组跟踪我们去过的地方。如果此举是合法的,并且将我们带到端点,则GOTO 9退出循环并打印final #。否则,如果此举是合法的,请再采取一步。否则,请清除屏幕并重新开始(比编写回溯算法要复杂得多!)。

在我的笔记本电脑上使用QB64进行测试后,通常会9, 9在五秒钟或更短的时间内产生结果。运行时间10, 10介于3到45秒之间。从理论上讲,所有合法路径的概率都不为零,但是具有大曲线的路径的概率却很小。不过,我偶尔会看到带有一两个小曲线的路径:

样品路径

可根据要求提供非高尔夫版本和/或深入说明。


2

R,225个字节

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

说明:

我们生成一个规则的(晶格)[x * y]无向图,其边缘上有随机权重,然后找到从起点到终点的最短路径。但是,在生成的路径中,例如,可能存在具有两个以上neigbor的像元:

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

因此,我们应该两次应用最短路径算法。第二次,我们将所有权重设置为1,但当前找到的路径中的权重设置为0;

结果

#
#
### 
  # 
  #
  ###

取消高尔夫:

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字节

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>

扩展:将#s随机放置在数组中,直到使用广度优先搜索找到通过该字段的路径为止;然后输出第一个路径,因此也是最短路径;这样可以保证路径不会相交。请注意,特别是对于较大的字段,有可能在找到路径之前超过JS引擎的堆栈。取消高尔夫:

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');
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.