岛屿高尔夫#1:环游世界


43

这是Island Golf系列挑战中的第一个。下一个挑战

给定ASCII艺术形式的孤岛,请输出对其进行绕行的最佳路径。

输入项

您的输入将是一个由两个字符组成的矩形网格,分别代表土地和水。在下面的示例中,土地是#,水是.,但是您可以替换任意两个不同的字符。

...........
...##......
..#####....
..#######..
.#########.
...#######.
...#####.#.
....####...
...........

始终将至少有一个地砖。地砖将是连续的(即只有一个岛)。瓷砖也将是连续的(即没有湖泊)。网格的外边界都是水瓦片。地砖将不会以对角线连接:即,您将永远不会看到类似

....
.#..
..#.
....

输出量

您的代码必须输出相同的网格,并在其上画出最短的环绕。在下面的示例中,环行路径用绘制o,但是您可以替换任何字符,只要它与您的水陆字符不同即可。

一个环游是一个简单的封闭曲线,完全是靠水的瓷砖画,能够完全包围在网格中的所有土地的瓷砖。对角线的连接允许的。例如,这是上述岛屿的环游世界(但不是最短的一个):

.ooooo.....
o..##.oo...
o.#####.o..
o.#######o.
o#########o
ooo#######o
..o#####.#o
..oo####..o
....oooooo.

环行的长度计算如下:对于路径上的每对相邻的图块,如果它们是水平或垂直连接的,则加1;否则,为0。如果对角连接,则加√2。以上路径的长度为22 +7√2(≈31.9)。

最短环游是用最短的长度的环游。您的程序应输出满足此条件的任何一条路径。对于大多数岛屿,将有多种可能的解决方案。这是上述岛的一种解决方案,长度为10 +13√2(≈28.4):

...oo......
..o##oo....
.o#####oo..
.o#######o.
o#########o
.o.#######o
..o#####.#o
...o####.o.
....ooooo..

细节

您的解决方案可能是完整程序或功能。任何默认的输入和输出方法都是可以接受的。

您的输入和输出可以是多行字符串或字符串列表。如果您的语言具有不同于单字符字符串的字符类型,则可以在上一句中用“字符列表”代替“字符串”。如果您的语言需要输入网格的高度和/或宽度,则可以输入。您的输出可能(可选)有一个尾随换行符。如上所述,您可以使用任何三个不同的字符来代替#.o(请在提交中指定要使用的字符)。

测试用例

A.环游时间最短的岛屿:

...
.#.
...

.o.
o#o
.o.

......
.####.
......

.oooo.
o####o
.oooo.

......
......
..##..
...#..
......
......

......
..oo..
.o##o.
..o#o.
...o..
......

.......
.#####.
...#...
...#...
.#####.
.......

.ooooo.
o#####o
o..#..o
o..#..o
o#####o
.ooooo.

.......
...#...
...#...
.#####.
...#...
...#...
.......

...o...
..o#o..
.o.#.o.
o#####o
.o.#.o.
..o#o..
...o...

.......
.#####.
.##..#.
..#..#.
.......

.ooooo.
o#####o
o##..#o
.o#..#o
..oooo.

B.具有多个可能解决方案的岛屿示例:

........
....##..
...####.
..###...
.#####..
.#####..
..##....
........

可能的输出:

....oo..
...o##o.
..o####o
.o###.o.
o#####o.
o#####o.
.o##oo..
..oo....

....oo..
...o##o.
..o####o
.o###.o.
o#####o.
o#####o.
.o##.o..
..ooo...

....oo..
...o##o.
..o####o
.o###..o
o#####.o
o#####o.
.o##oo..
..oo....

....oo..
...o##o.
..o####o
.o###..o
o#####.o
o#####o.
.o##.o..
..ooo...

C. 大型测试用例


这是:每种语言中最短的代码胜出。


1
第三个测试用例的最短绕行是Conway的《人生游戏》中的“面包”模式!
“ SparklePony同志” 17年

Answers:


18

Mathematica(版本9),165个字节

ConvexHullMeshGreg Martin使用的漂亮的简短函数仅在Mathematica版本10中引入,因此我想我会尝试使用古老的Mathematica版本9来尝试它,但是我设法使其变得更短!这是一个函数,并返回一个字符串列表(用.#o为符号)。

""<>#&/@("o"MorphologicalTransform[MorphologicalComponents[#,Method->"ConvexHull"],Max[#(1-#[[2,2]])CrossMatrix@1]&]+"#"#/.{0->"."})&[Characters@#/.{"."->0,"#"->1}]&

说明:

  • 首先,Characters@# /. {"."->0, "#"->1}将输入转换为0s和1s 的矩阵。
  • "o"MorphologicalTransform[MorphologicalComponents[#,Method->"ConvexHull"],Max[#(1-#[[2,2]])CrossMatrix@1]&]+"#"#然后使用Mathematica强大的图像处理功能(但字节非常重……)首先填充岛的凸包(如果在其周围拉伸一串字符串,则形状将是它的形状),然后确定其边界。然后,我们将该矩阵乘以字符串"o",得到0s和"o"s 的矩阵(这要归功于Mathematica对类型的出色适应性),并将其加到"#"该岛的矩阵上。
  • 最后,""<>#& /@ (... /. {0->"."})原来的这个矩阵"o"S,"#"S和0s转换的矩阵"o"S,"#"S和"."s和加入每一行成一个字符串。

当我们在示例B上测试时,得到输出

{"....oo..",
 "...o##o.",
 "..o####o",
 ".o###..o",
 "o#####o.",
 "o#####o.",
 ".o##oo..",
 "..oo...."}

[编辑,感谢Greg Martin:]如果允许使用字符数组而不是字符串列表,则可以将其缩减为144个字节:

"o"MorphologicalTransform[MorphologicalComponents[#,Method->"ConvexHull"],Max[#(1-#[[2,2]])CrossMatrix@1]&]+"#"#/.{0->"."}&[#/.{"."->0,"#"->1}]&

1
做得很好!我从不知道MorphologicalComponents[#, Method->"ConvexHull"] :)您可以通过假设输入已经分割为字符并返回2D字符数组来节省更多字节。
格雷格·马丁

@GregMartin,MorphologicalComponents直到今天我都不知道两者的用法!
不是一棵树

Mathematica新手在这里:我应该如何调用此函数?我尝试f[{"...",".#.","..."}]了一些错误。
DLosc

@DLosc,功能是全部,而不仅仅是f。(好吧,严格来说,这是分号后面的内容。)要调用该函数,请在Mathematica窗口中键入整个内容,然后[输入,您的输入和],因此它的外观应类似于f@m_:=m(1-m[[2,2]]) . . . #/.{"."->0,"#"->1}]&[{"...", ".#.", "..."}](删节)。
不是一棵树

@DLosc好吧,那是因为代码已损坏。我想我已经修复了!(我不知道那里发生了什么;对不起……)
不是一棵树

11

(但是支持Notatree的解决方案,那就更好了!)

Mathematica,168个字节

(x_~n~y_:=Min[Norm[x-#]&/@y];i=#;p=i~Position~#&;t=p["#"|"."]~Select~#&;(i~Part~##="o")&@@@t[#~n~t[ConvexHullMesh[Join[s=p@"#",#+{.1,.1}&/@s]]~RegionMember~#&]==1&];i)&

纯函数,将2D字符数组作为输入并返回2D字符数组。易于阅读的版本:

1  (x_~n~y_ := Min[Norm[x - #] & /@ y];
2  i = #; p = i~Position~# &; 
3  t = p["#" | "."]~Select~# &;
4  (i~Part~## = "o") & @@@ 
5    t[#~n~
6      t[ConvexHullMesh[
7        Join[s = p@"#", # + {.1, .1} & /@ s]]
8      ~RegionMember~# &] == 1 &];
9  i) &

第1行定义了一个函数n,该函数产生x平面中的一个点与一组y其他点之间的(最小)距离。第2行将变量初始化为i输入,既可以解决后来出现的歧义,又可以对其进行修改以产生最终的输出。第2行还定义了一个函数p,该函数返回在中输入的所有出现的坐标i

在第3行上,p["#" | "."]代表输入映射中的每个坐标(因为其所有字符均为"#""."),所以t该函数仅选择那些满足尚未指定属性的坐标。在第4行,i~Part~## = "o"将更改i字符的一堆条目"o";这些字符将根据第5-8行的内容从一组可能的坐标中选择。第9行在计算出答案后即返回答案。

好的,基础架构已完成,现在可以进行实际计算了。ConvexHullMesh是Mathematica的内置函数,用于计算一组点的凸包(包含这些点的最小凸多边形)。从道德上讲,这应该“填满”岛屿的海湾和峡湾(即s = p@"#"),以将它们排除在我们的导航之外。ConvexHullMesh当该组点全部都在一行中时(有一点,测试案例2),存在一个小问题,我们可以通过s在第7行中为其自身附加一个稍微偏移的版本来解决该问题。此输出是一个多边形,因此第7行-9(t[...~RegionMember~# &])会生成该多边形中具有整数坐标的点的列表。最后,第5行和第9行的末尾计算所有与这组整数点之间的距离恰好为1(因此不为0)的点;那组成为绕行的道路。

以下是OP链接上大型测试用例的输出。请注意,在左上角,何时去西南还是西南,这是一个不寻常的选择,这暗示着它掩盖了两个半岛之间一条不可见的坡度-2/3线的阴影(该线段是凸包边界的一部分)。

........................
.............o..........
...........oo#ooooooo...
..........o#.#.##...#o..
........oo.#.#.###.##o..
.......o..########.##o..
.....oo...############o.
...oo#....############o.
..o#.###.##############o
.o##.##################o
.o####################o.
.o.##################.o.
.o##################..o.
.o..################..o.
o###################..o.
o#####################o.
o.##################.o..
o####################o..
o#...##############.o...
o##...#############o....
o#.....###....#oooo.....
.oooooo#ooooooo.........
.......o................

Mathematica通常将字符串表示为一维字符数组吗?如果不是,那么您将需要获取/返回一维字符串数组。(另外,期待解释!我想如果没有Mathematica,我将无法运行它,对吗?)
DLosc

Mathematica具有字符串数据类型,但是看来对于该站点而言,字符数组也是有效的(即,我在开始使用PPCG时就了解了这一点,但我忘记了为什么这样做的合法性)。是的,不幸的是,Mathematica是非免费的,因此许多人无法访问:(
Greg Martin

1
@GregMartin我总是在sandbox.open.wolframcloud.com上
ovs '17

目前的共识是,不能使用单字符字符串列表代替字符串。据我所知,Mathematica中的“字符”只是单字符字符串,就像在Python中一样。在Java之类的语言中,情况有所不同,它具有单独的char类型。在这种情况下,char可以使用数组代替字符串。
DLosc

1
这是我的阅读方式:于2014年发布了主要支持的答案。我链接的答案于2016年发布,以试图澄清早期答案中的歧义。因此,当人们说:“不,我们不希望较早的答案表示单字符字符串的列表还可以的时候,我读了对新答案的否定分数。” 但是不管元数据如何,我都不允许在这个问题中使用单字符字符串列表(并且我澄清了用词来反映这一点)。
DLosc

10

Python 3,779个字节(带制表符缩进)

这是整个程序。它从stdin读取输入并将其打印到stdout。Stdin必须以EOF结尾。使用大输入量运行的示例:https : //ideone.com/XIfYY0

import itertools,sys
L=list
C=itertools.count
d=L(map(L,filter(None,sys.stdin.read().split('\n'))))
X=len(d[0])
Y=len(d)
R=range
def P(r):return all(d[y][x]=='.'for x,y in r)
def S(f):
    for n in C(0):
        if P(f(n)):l=n
        else:break
    for n in C(l+1):
        if P(f(n)):return l,n
def f(V,a,*b):return L(eval('lambda '+a+':('+i+')',V)for i in b)
V=locals()
def D(n):
    y=min(n,Y-1);x=n-y
    while y>=0and x<X:yield(x,y);x+=1;y-=1
def E(n):
    x=max(0,n-Y);y=x+Y-n
    while y<Y and x<X:yield(x,y);x+=1;y+=1
F=f(V,'p','(p,y)for y in R(0,Y)','(x,p)for x in R(0,X)')+[D,E]
r=f(V,'x,y','x','y','x+y','x-y+Y')
B=L(map(S,F))
for x in R(0,X):
    for y in R(0,Y):
        z=L(zip(r,B))
        if all(g(x,y)in R(a,b+1)for g,(a,b)in z)and any(g(x,y)in e for g,e in z):d[y][x]='o'
print('\n'.join(''.join(x)for x in d))

这个想法很简单:它计算最小的八边形边界,并绘制所有计算出的边界内并与至少一条边相交的像元。


1
您实际上并不需要sys.stdin用作输入。input(),使用多行将完成这项工作,并且减少字节的开销
Dead Possum

2
也许可以替换R(0,x)R(x)
ceilingcat '17

+1(不使用内置)。
罗伯特·弗雷泽

1
真好!更多高尔夫技巧:使用lambda定义P和分别保存5个字节fL(generator expression)=> [generator expression]; F,,rB似乎只能使用一次,因此可以内联。
DLosc

8

JavaScript(ES6),369343字节

f=s=>(a=s.split`
`.map(s=>[...s]),m=Array(8),a.map((b,i)=>b.map((c,j)=>c>'#'||[i-j,i,j+i,j,j-i,-i,-i-j,-j].map((d,k)=>d>m[k]||(m[k]=d-1)))),[o,p,q,r,t,u,v,w]=m,g=(i,j,k,l,...p)=>i-k|j-l?a[i][j]=g(i+(k>i)-(k<i),j+(l>j)-(l<j),k,l,...p):1/p[0]?g(k,l,...p):'o',g(p,p-o,p,q-p,q-r,r,r-t,r,-u,t-u,-u,u-v,w-v,-w,o-w,-w,p,p-o),a.map(b=>b.join``).join`
`)

说明:字符串被拆分为一个字符数组(不清楚字符数组输入是否可接受)。然后,对数组进行迭代,并找到所有焊盘的位置。由等式给出的边界线x - y = ox = px + y = qy = ry - x = t-x = u-x - y = v-y = w被确定为使得最大可能的参数被选择,其中所有土地在于超越线。这具有将岛围成八角形的效果。八角形的角的坐标很容易从参数中计算出来,并且其边上的单元格被填充。然后将数组连接回字符串。八边形足够的原因如下:

   /o#     /o#     /o#
 |/o #   |/o #   |/ o#
 *o###   * o #   *  o#
/|o #   /|o #   /| o#
 |o#     |o#     |o#

考虑八边形的一个角。沿着两条边缘的某个点,路径将受到陆地的限制,因为我们构建了八边形以尽可能接近该陆地。如果拐角处本身没有陆地,则该路径可以采用如右图所示的替代路线,但是正交和对角线步数仍然相同,因此距离不变。


“ ... p”做什么?
罗伯特·弗雷泽

@RobertFraser技术名称是数组解构。但是,在这种情况下,它仅充当rest of arguments参数。
尼尔

@Neil实际上,技术名称是rest参数传播运算符使用相同的语法。(您可以...p在不同的地方使用这两种方法。)销毁是另一回事(尽管可以在销毁中使用传播算子)。
Brian McCutchon

@BrianMcCutchon没错,我的意思是传播运算符,但是无论如何要破坏参数列表中的结构。
尼尔

6

蟒3.5,224,263,234个 218字节

通过摆脱嵌套函数并将其变成单行,可以再释放16个字节。

def h(s,k=0,i=0):w=s.find('\n')+1;x=s.find('X')-w;k=k or x;d=[1,w+1,w,w-1,-1,-w-1,-w,-w+1]*2;u=s[:k]+'o'+s[k+1:];return['X'>s[k]and i<8and(h(u,k+d[i+2],i+2)or h(u,k+d[i+1],i+1)or h(u,k+d[i],i))or'',s][s[k]>'X'and k==x]

打掉29个字节:

def f(s):
 w=s.find('\n')+1;x=s.find('X')-w;d=[1,w+1,w,w-1,-1,-w-1,-w,-w+1]*2
 def h(s,k,i):u=s[:k]+'o'+s[k+1:];return['X'>s[k]and i<8and(h(u,k+d[i+2],i+2)or h(u,k+d[i+1],i+1)or h(u,k+d[i],i))or'',s][s[k]>'X'and k==x]
 return h(s,x,0)

输入是单个字符串,其中“〜”代表海洋,“ X”代表陆地,“ o”代表边界。(使用“ X”会为“>”而不是“ ==”保存一个字节)

高尔夫版本更少,带注释:

def f(s):
    w=s.find('\n')+1                         # width of one row
    x=s.find('X')-w                          # starting point
    d=[1,w+1,w,w-1,-1,-w-1,-w,-w+1]*2        # delta to add to current index to move in 
                                             # the 8 directions: E, SE, S, SW, W, NW, 
                                             # N, NE. Make it long to avoid
                                             # lots of modulo operations in 
                                             #    the recursive calls

    def h(s,k,i):                            # s is the island string, k the current
                                             # position, i the direction index
        if s[k]>'X'and k==x:                 # if back at the begining,
            return s                         #   return the map

        elif 'X'>s[k] and i<8:               # if there is water here, and haven't
                                             #  looped around,
            u=s[:k]+'o'+s[k+1:]              #  make a new map with an 'o' in the 
                                             #  current spot

            r = h(u,k+d[i+2],i+2)            # try a 90 degree right turn
            if r: return r

            r = h(u,k+d[i+1],i+1)            # try a 45 degree turn
            if r: return r

            r= h(u,k+d[i],i)                 # try straight ahead
            if r: return r

        return ''                            # this path failed

    return h(s,x,0)

@DLosc已修复。(我应该删除旧答案吗?)
RootTwo

真好!(是的,您应该删除旧的答案-如果有人想看到它,则可以查看该帖子的修订历史。)
DLosc

5

C#7 - 414个 369 327字节

编辑:切换到一维循环,计算ij即时

编辑:更改了输入法,折叠了查找表,并切换到定义良好的初始边界...并删除了最后一个外部for循环中的无意义空间

using C=System.Console;class P{static void Main(){var D=C.In.ReadToEnd().Replace("\r","");int W=D.IndexOf('\n')+1,H=D.Length,z=H,k,q,c;int P()=>z%W*(k%3-1)+z/W*(k/3-1)+H;var B=new int[9];for(;z-->0;)for(k=9;k-->0&D[z]%7<1;)if(B[k]<=P())B[k]=P()+1;for(;++z<H;C.Write(q>9?'o':D[z]))for(q=k=9;k-->0;)q*=(c=P()-B[k])>0?0:c<0?1:2;}}

在线试用

完整的程序,需要输入标准,它打印到标准输出,使用#.o。对于每个像元,它会计算一个“轮廓”(即在8个方向上的距离(为了方便起见,它计算的是第九个,但这始终是0),并记录每个最大值,然后写出整个地图再次,并用“ o”替换既在边界上又不在外部的任何单元格,下面的注释代码说明了它们的工作方式。

根据我对“从灭绝中拯救大雁”的回答,这将产生一个最小的八角形(有效的环行最大面积),该八角形将岛包围。

注意:这是我生命中一次使用最近十年的东西,并且此代码需要C#7进行编译。如果您没有C#7,则需要替换一行,该行在代码中已明确标记。

用法示例和输出:

type t7.txt | IslandGolf1.exe

.........ooooooooooo....
........o....#......o...
.......o...#.#.##...#o..
......o....#.#.###.##.o.
.....o....########.##..o
....o.....############.o
...o.#....############.o
..o#.###.##############o
.o##.##################o
o.####################.o
o..##################..o
o.##################...o
o...################...o
o###################...o
o#####################.o
o.##################..o.
o####################o..
o#...##############.o...
o##...#############o....
o#.....###....#...o.....
.o.....#.........o......
..ooooooooooooooo.......

格式化和注释的代码:

using C=System.Console;

class P
{
    static void Main()
    {
        // \n 10
        // # 35
        // . 46
        // o 111


        var D=C.In.ReadToEnd().Replace("\r",""); // map

        int W=D.IndexOf('\n')+1, // width
            H=D.Length, // length
            z=H, // position in map (decomposed into i and j by and for P)
            k, // bound index
            q, // bound distance, and later cell condition (0 -> outside, 8 -> inside, >8 -> on boudary)
            c; // (free), comparison store

        // 'indexes' into a profile for the point z at index k
        // effectively {i=z%W,j=z/W,-i,-j,i+j,j-i,-i-j,i-j,0}[k] (inside order is a bit different) (0 const is always treated as 'inside bounds')
        // each non-zero-const entry describes the distance in one of the 8 directions: we want to maximise these to find the 'outer bounds'
        // the non-zero-const bounds describe 8 lines, together an octogen
        int P()=>z%W*(k%3-1)+z/W*(k/3-1)+H; // new C#7 local method syntax (if you don't have C#7, you can test this code with the line below instead)
        //k=0;System.Func<int>P=()=>z%W*(k%3-1)+z/W*(k/3-1)+H; // old lambda syntax (must pre-assign k to make static checker happy)

        var B=new int[9]; // our current bounds, each is initially null (must only call P() when on a #)
        // B[k] starts off a 0, P() has a +H term, and W+(H/W)<H for W >= 3, so B[k] is assigned the first time we compare it (H-i-j always > 0)

        for(;z-->0;) // for each cell
            for(k=9;k-->0& // for each bound
                D[z]%7<1;) // if this cell is #
                if(B[k]<=P())B[k]=P()+1; // update bound if necessary (add one so that we define the bound _outside_ the hashes)
        // z=-1
        for(;++z<H; // for each cell
                C.Write(q>9?'o':D[z])) // print the cell (if q > 9, then we are on the bounds, otherwise, spit out whatever we were before)
            // check we are not 'outside' any of the bounds, and that we are 'on' atleast one of them
            for(q=k=9;k-->0;) // for each bound
                q*=(c=P()-B[k])>0?0: // outside bound (q=0)    (??0 is cheaper than (int) or .Value)
                    c<0?1: // inside (preserve q)
                    2; // on bound (if q != 0, then q becomes > 9)
    }
}

最大的八边形?或最小?
Sarge Borsch

@SargeBorsch谢谢,修复了文字。
VisualMelon
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.