アイランドゴルフ#1:周航


43

これは、一連のアイランドゴルフチャレンジの最初のものです。次の挑戦

ASCII-artでアイランドを指定すると、それを迂回する最適なパスを出力します。

入力

入力は、土地と水を表す2つの文字で構成される長方形のグリッドになります。以下の例では、土地はで#あり、水はです.。ただし、任意の2つの異なる文字を使用できます。

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

常に少なくとも1つの土地タイルがあります。土地のタイルはすべて連続しています(つまり、島は1つだけです)。水タイルも連続しています(つまり、湖はありません)。グリッドの外側の境界線はすべて水タイルになります。ランドタイルは斜めに接続されません。つまり、次のようなものは表示されません。

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

出力

コードは、最短のナビゲーションが描画された同じグリッドを出力する必要があります。以下の例では、周航経路はで描画されてoいますが、陸地や水域のキャラクターと区別できる限り、任意のキャラクターに置き換えることができます。

周航は完全にグリッド上のすべての土地タイルを取り囲む水タイルの上に完全に描かれた単純閉曲線、、です。斜めの接続許可されています。たとえば、これは上記の島の周航です(ただし、最短ではありません)。

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

周回ナビゲーションの長さは次のように計算されます。パス上の隣接するタイルのペアごとに、水平または垂直に接続されている場合、1を追加します。斜めに接続されている場合は、√2を追加します。上記のパスの長さは22 +7√2(≈31.9)です。

最短一周は、最短長さの一周です。プログラムは、この条件を満たすパスを1つ出力する必要があります。ほとんどの島では、複数の可能な解決策があります。長さ10 +13√2(≈28.4)の上記の島に対する1つのソリューションを次に示します。

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

詳細

あなたの解決策は、完全なプログラムまたは機能かもしれません。いずれかのデフォルトの入力と出力の方法が許容されています。

入力と出力は、複数行の文字列または文字列のリストです。言語に単一文字の文字列とは異なる文字タイプがある場合、前の文の「文字列」を「文字のリスト」に置き換えることができます。言語でグリッドの高さや幅を入力する必要がある場合は、入力してください。出力には、(オプションで)単一の末尾改行が含まれる場合があります。上記のように、代わりに3つの異なる文字#.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
3番目のテストケースの最短の巡回は、ConwayのGame of Lifeの「ローフ」パターンです!
同志SparklePony

回答:


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と0のマトリックスによ"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"] あなたも、より多くの入力がすでに文字に分割されていることを仮定することにより、同様の文字の2次元配列を返すことによってバイト保存することができます:)。
グレッグマーティン

@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間の(最小の)距離を生成する関数を定義します。2行目は、変数を入力に対して初期化し、カリー化するあいまいさを後で解決し、最終的な出力を生成するためにそれを変更できるようにします。行2 は、の入力のすべての出現の座標を返す関数も定義しています。xyipi

ライン3上に、p["#" | "."](すべての文字のいずれかであるので、すべての単一の入力マップから座標を表し"#"、または"."ので、)tの関数であることまだ不特定性を満たす選択のみ座標。4行目でi~Part~## = "o"は、の一連のエントリをiキャラクターに変更し"o"ます。これらの文字は、5〜8行目の内容に従って、可能な座標のセットから選択されます。そして、9行目は計算されると答えを返します。

さて、インフラストラクチャが完了し、実際の計算になりました。ConvexHullMeshは、一連のポイント(これらのポイントを含む最小の凸多角形)の凸包を計算するためのMathematicaの組み込み関数です。道徳的に言えば、これは島の入り江とフィヨルド(つまりs = p@"#")を「埋める」必要があり、それらを私たちのcicrumnavigationから除外します。そのConvexHullMesh点のセットがすべて1行にある場合(テストケース#2)に少し問題がありますs。これは、わずかにオフセットしたバージョンを行7に追加することで解決します。この出力はポリゴンなので、行7 -9(t[...~RegionMember~# &])は、そのポリゴン内の整数座標を持つポイントのリストを生成します。最後に、5行目と9行目の終わりは、この整数点のセットから正確に1(したがって0ではない)にあるすべての点を計算します。そのセットが迂回パスになります。

以下は、OPのリンクでの大規模なテストケースの出力です。左上にある、西に行くときと南西に行くときの異常な選択は、2つの半島の間の見えない傾斜-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は通常、文字列を文字の1D配列として表しますか?そうでない場合は、代わりに文字列の1D配列を取得または返す必要があります。(また、説明を楽しみにしています!Mathematicaがなくてもこれを実行できるとは思わないでしょう?)
DLosc

Mathematicaには文字列データ型がありますが、文字の配列もこのサイトの目的に有効であると思われます(つまり、PPCGを始めたときにこれを学びましたが、理由の合法性を忘れています)。はい、残念ながら、Mathematicaはフリーではないため、多くの人がアクセスできません。(
Greg Martin

1
私はいつものMathematicaのソリューションを試してみる@GregMartin sandbox.open.wolframcloud.com
OVS

現在のコンセンサスでは、文字列の代わりに単一文字の文字列のリストを使用することはできません。私が知る限り、Mathematicaの「文字」は、Pythonのように単なる1文字の文字列です。Javaのような言語では状況が異なりますが、Javaには別のcharタイプがあります。その場合、char文字列の代わりに配列を使用できます。
DLosc

1
主な賛成の回答は2014年に投稿されました。以前の回答の曖昧さを明確にするために、リンクした回答は2016年に投稿されました。だから、新しい答えのネガティブスコアを読んで、「いいえ、古い答えが単一文字列のリストに問題がないことを意味したくない」と言っています。しかし、メタに関係なく、この質問では単一文字の文字列のリストを許可していません(そして、それを反映するように表現を明確にしました)。
DLosc

10

Python 3、779バイト(タブでインデント)

これがプログラム全体です。入力を標準入力から読み取り、標準出力に出力します。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つのエッジと交差するセルを描画します。


1
sys.stdin入力として実際に使用する必要はありません。input()、複数行が仕事とコストを行うだろう得ることは少ないバイト
デッドポッサム

2
交換することができるかもしれないR(0,x)R(x)
ceilingcat

組み込みを使用しない場合は+1。
ロバートフレイザー

1
いいね!いくつかのより多くのゴルフのヒント:5を定義するためにラムダを使用して、各バイトの保存PfL(generator expression)=> [generator expression]; FrB一度だけ個々に使用されるように見えるため、インライン化することができます。
DLosc

8

JavaScript(ES6)、369 343バイト

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#

八角形の角を考えてみましょう。2つのエッジに沿ったあるポイントでは、土地にできるだけ近くなるように八角形を構築したため、パスは土地によって制限されます。コーナー自体に土地がない場合、パスは右側に示すように代替ルートを取ることができますが、それでも直交および対角線のステップの数は同じであるため、距離は変わりません。


´... p´は何をしますか?
ロバートフレイザー

@RobertFraser技術名は配列の構造化です。ただし、この場合、rest of argumentsパラメータとしてのみ機能します。
ニール

@Neil実際には、技術名はrest parameterです。スプレッド演算子にも同じ構文が使用されます。(両方を...p別々の場所で使用します。)破壊は別のものです(ただし、スプレッド演算子は破壊に使用できます)。
ブライアンマックラッチン

@BrianMcCutchonそうですね、私はスプレッド演算子を意味しましたが、とにかく引数リストでの構造化は機能します。
ニール

6

Python 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バイト

編集:1Dに切り替え、ループコンピューティング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方向の距離です(便宜上9番目を計算するように見えますが、これは常に0)であり、これらのそれぞれの最大値を記録します。マップ全体を書き出します。再度、境界上にあり、外部にないセルを「o」に置き換えます。以下のコメントコードは、すべての動作を説明しています。

Save the Geese from Extinctionへの私の答えによると、これは島を囲む最小の八角形(最大面積の有効な周回航法)を生成します。

:人生で一度だけ、現在の10年間の何かを使用しています。このコードをコンパイルするにはC#7が必要です。C#7がない場合は、コードで明確にマークされている、交換する必要がある1行があります。

使用例と出力:

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

最大の八角形?または最小?
セージボルシュ

@SargeBorschありがとう、テキストを修正しました。
VisualMelon
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.