助けて、シェルピンスキーの三角形に閉じ込められている!


44

シェルピンスキーの三角形 描くことは死ぬまで行われ 。ただし、他にも面白いことがあります。三角形をよく見ると、逆さまの三角形をフラクタルグラフのノードとして見ることができます。そのグラフの周りの方法を見つけましょう!

まず、各ノードに番号を割り当てましょう。最大の上下逆三角形はノード0になり、レイヤーごとに(幅が最初に)下降し、連続した番号を左上から右の順に割り当てます。

ここに画像の説明を入力してください
小さい数字が少しぼやけていない大きなバージョンをクリックします。

ナンバリングを定義する別の方法は、センタノードがインデックスを有することである(もちろん、このパターンは、青色三角形内部無限に継続)0、およびノードの子i(次のより小さなスケールの隣接する三角形)がインデックスを有する3i+13i+2および3i+3

このグラフをどのように移動しますか?任意の三角形から実行できる最大6つの自然なステップがあります。

  • 常に、エッジの1つの中点を通って、現在のノードの3つの子の1つに移動できます。これらの動きをNSWおよびとして指定しますSE。例えば、我々はノード上で現在している場合2、これらのノードにつながる789それぞれ。エッジを介した(間接的な子孫への)他の移動は許可されません。
  • また、三角形のエッジに触れない限り、3つの角のいずれかを介して、直接の親または2つの間接的な祖先のいずれかに移動できます。これらの動きをSNEおよびとして指定しますNW。例えば、我々はノード上で現在している場合31Sにつながる10NE無効になりますとNWにつながります0

挑戦

2つの負でない整数xとが与えられた場合、上記の6つの動きのみを使用yxy、からまでの最短経路を見つけます。最短経路が複数ある場合は、いずれかを出力します。

コードは、上の図に示されている5つのレベル以外にも機能するはずです。あなたはそれを仮定するかもしれませんx, y < 1743392200。これにより、32ビット符号付き整数内に収まることが保証されます。これは、ツリーの20レベルに対応することに注意してください。

コードは有効な入力を5秒未満で処理する必要があります。これはブルートフォース幅優先検索を除外しますが、かなり緩やかな制約である必要があります。私の参照実装は、深さ1000の任意の入力を0.5秒で処理します(ノードの場合は480桁の数値です)。

プログラムまたは関数を作成し、STDIN(または最も近い代替)、コマンドライン引数または関数引数を介して入力を取得し、STDOUT(または最も近い代替)、関数の戻り値または関数(out)パラメーターを介して結果を出力できます。

出力は、フラット、文字列の明確なリストでなければならないNSNENWSESW、任意の合理的なセパレータを使用して(スペース、改行、コンマ、","...)。

標準の規則が適用されます。

テストケース

最初のいくつかのテストケースは、上の図を使用して手動で作成できます。その他は、回答が十分に効率的であることを保証します。それらについては、リストされていない同じ長さの他のソリューションがあるかもしれません。

0 40                    => N N N N
66 67                   => S SW N N N
30 2                    => NW NW -or- NE SW
93 2                    => NE SW
120 61                  => NW NW NW NW N SE SW N
1493682877 0            => S S NW NW
0 368460408             => SW SW N N SW SW SE SW SW N SE N N SW SW N SE SE
1371432130 1242824      => NW NW NE NW N SE SW SW SW SE SE SW N N N N SW
520174 1675046339       => NE NW NE NE SE SE SW SW N SE N SW N SW SE N N N N SE SE SW SW
312602548 940907702     => NE NW S SW N N SW SE SE SE SW SE N N SW SE SE SE SW
1238153746 1371016873   => NE NE NE SE N N SW N N SW N SE SE SW N SW N N SE N SE N
547211529 1386725128    => S S S NE NW N N SE N SW N SE SW SE SW N SE SE N SE SW SW N
1162261466 1743392199   => NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE

回答:


5

ルビー、195の 194 190 184バイト

オリジナル:への謝罪とエルは、これは基本的に自分の答えのポートであり、多くの感謝のようにドアノブこの答えをデバッグ中に彼らの助けのため。この問題にはおそらく別のアルゴリズムがあります-何か関係があります*f[x[0,**however far x matches with y**],y]-しかし、私はそれを別の時間のために保存します。

a=->n{n<1?[]:a[~-n/3]+[-n%3]}
f=->x,y{j=x.size
(Array===x)?x==y[0,j]?y[j..-1].map{|m|%w[SE SW N][m]}:x.uniq.map{|m|[%w[NW NE S][m],*f[x[0,x.rindex(m)],y]]}.min_by(&:size):f[a[x],a[y]]}

EDIT: 貪欲アルゴリズムが動作しませんh[299792458, 1000000]。バイトカウントを195に戻しましたが、アルゴリズムをもう一度修復しています。203に上昇するだけのバイト・カウントのためにそれを修正ため息

作成中:このプログラムは、貪欲なアルゴリズムを使用して共通の祖先を見つけますx[0,j]==y[0,j](注:共通の祖先がいくつかある場合があります)。このアルゴリズムは、Ellの祖先の再帰検索に大まかに基づいています。結果の命令の前半はこの共通の祖先に到達する方法であり、後半はに基づいてyに到達することy[j..-1]です。

注:a[n]ここでは、の代わりに数字を使用して、基数 3の全単射数を返します。2,1,01,2,3

例として、f[59,17]またはを実行してみましょうf[[2,0,2,1],[2,1,1]]。ここで、j == 1。に到達するにはx[0,j]、私たちは行く0NW。次に、にy行く、[1,1]またはSW SW

a=->n{n<1?[]:a[~-n/3]+[-n%3]}
h=->m,n{x=a[m];y=a[n];c=[];j=x.size
(j=x.uniq.map{|m|k=x.rindex(m);x[0..k]==y[0..k]?j:k}.min
c<<%w[NW NE S][x[j]];x=x[0,j])until x==y[0,j]
c+y[j..-1].map{|m|%w[SE SW N][m]}}

45

Python 2、 208 205 200バイト

A=lambda n:n and A(~-n/3)+[-n%3]or[]
f=lambda x,y:f(A(x),A(y))if x<[]else["SSNEW"[m::3]for m in
y[len(x):]]if x==y[:len(x)]else min([["NNSWE"[m::3]]+f(x[:~x[::-1].index(m)],y)for
m in set(x)],key=len)

関数、fノード番号のペアを取得し、最短パスを文字列のリストとして返します。

説明

まず、三角形に異なるアドレス指定方式を採用することから始めます。各三角形のアドレスは、次のように定義された文字列です。

  • 中央の三角形のアドレスは空の文字列です。

  • 北、南西、各三角形の南東子供のアドレスが追加形成され01および2三角形のアドレスに、それぞれ、。

基本的に、各三角形のアドレスは、中央の三角形から三角形までの(最短の)パスをエンコードします。プログラムが最初に行うことは、入力された三角形番号を対応するアドレスに変換することです。

図1

大きなバージョンの画像をクリックします。

各三角形で可能な移動は、アドレスから簡単に決定されます。

  • 我々単にアペンド、北、南、西、南、東の子どもたちへ移動するには01、および2アドレスに、それぞれ。

  • 北東、南に移動し、北西の祖先は、我々は最後の(右端)の発生を見つけるには01し、2それを左にアドレスを、それぞれ、およびトリム。何も存在しない場合01または2アドレスには、対応する祖先は存在しません。たとえば、北西の祖先112(つまり、その親)に移動するには、最後の文字である2in の最後の出現を見つけ、112その左側のアドレスをトリミングして11、; 北東の祖先に移動するには、1in の最後の出現(1122番目の文字)を見つけ、その左側のアドレスをトリミングして1、ただし、112南の祖先はありません。0 そのアドレスで。

アドレスのペアについてのいくつかのことに注意してください、xy

  • 場合xの最初のサブストリングでありy、その後yの子孫でありx、したがってから最短経路xには、y単にの間の各三角形の対応する子以下xとしますy。言い換えれば、私たちは、それぞれを置き換えることができ01および2y[len(x):]NSW、およびSEそれぞれ。

  • それ以外の場合、とのi間の最初の不一致のインデックスxとしyます。以下からのパスが存在しないxyそれが通過しないx[:i](同じであるy[:i]第1の共通の祖先、すなわち、)xとはy。したがって、からの任意のパスは、またはその祖先のいずれかに到達xするy必要がありx[:i]ます。この三角形を呼び出してにz進みyます。からに到着するxにはz、上記のように先祖に従います。最短経路zには、y前の箇条書きで与えられます。

場合は、x最初のサブストリングでありy、次いでから最短経路xには、y容易に上記第一弾点によって与えられます。そうでなければ、我々は、離すと自然jの最後の出現の指標の最小可能012の中でx。場合jよりも大きい、または等しい、との間の最初のミスマッチの指標xyi私たちは、単に対応する動き(追加SNEまたはNW経路には、それぞれ)、トリムxの左にj、そして続けます。jがより小さい場合i、物事はより複雑になります。その場合y、共通の祖先にx[:j]直接昇順で到達し、y、または我々は異なる共通の祖先を取得することができるかもしれないxし、yその近いだyの異なる祖先に昇順でxの右へi、そしてそこからを取得y速く。たとえば、からに到達する1222ため1の最短パスは、最初に中央の三角形(アドレスが空の文字列)に上ってからに下ることです1。つまり、最初の移動で不一致点の左側に移動します。ただし、からに到達する1222ため12の最短経路は、からに上昇し122、次にに上昇することです12。つまり、最初の移動により、不一致のポイントの右側に移動します。

それでは、どのようにして最短経路を見つけるのでしょうか?「公式」プログラムは、ブルートフォース風のアプローチを使用してx、の最初の部分文字列でない場合はいつでも祖先へのすべての可能な移動を試みますy。思ったほど悪くはありません!すべてのテストケースを組み合わせて、1〜2秒で解決します。

しかし、その後、再び、私たちははるかに良いことができます:ミスマッチのポイントの左側に複数の直接到達可能な祖先がある場合、右端のものをテストする必要があり、複数の直接到達可能な祖先がある場合ミスマッチのポイントの右にある場合、一番左のものだけをテストする必要があります。これにより、線形の時間アルゴリズムが得られます。xつまり、ソーストライアングルの深さ(ソーストライアングルの対数に​​比例する時間)により、さらに大きなテストケースをズームします。次のプログラムは、少なくとも本質的にこのアルゴリズムを実装しています。ゴルフのために、実際、その複雑さは線形よりも悪いですが、それでも非常に高速です。

Python 2、 271 266 261バイト

def f(x,y):
 exec"g=f;f=[]\nwhile y:f=[-y%3]+f;y=~-y/3\ny=x;"*2;G=["SSNEW"[n::3]for
n in g];P=G+f;p=[];s=0
 while f[s:]:
    i=len(f)+~max(map(f[::-1].index,f[s:]));m=["NNSWE"[f[i]::3]]
    if f[:i]==g[:i]:P=min(p+m+G[i:],P,key=len);s=i+1
    else:p+=m;f=f[:i]
 return P

短いバージョンとは異なり、このバージョンは、スタックをオーバーフローさせることなく非常に大きな値を処理できるように、入力値の対応するアドレスへの変換で再帰を使用しないように特に書かれていることに注意してください。

結果

次のスニペットを使用して、いずれかのバージョンのテストを実行し、結果を生成できます。

def test(x, y, length):
    path = f(x, y)
    print "%10d %10d  =>  %2d: %s" % (x, y, len(path), " ".join(path))
    assert len(path) == length

#         x           y        Length
test(          0,          40,    4   )
test(         66,          67,    5   )
test(         30,           2,    2   )
test(         93,           2,    2   )
test(        120,          61,    8   )
test( 1493682877,           0,    4   )
test(          0,   368460408,   18   )
test( 1371432130,     1242824,   17   )
test(     520174,  1675046339,   23   )
test(  312602548,   940907702,   19   )
test( 1238153746,  1371016873,   22   )
test(  547211529,  1386725128,   23   )
test( 1162261466,  1743392199,   38   )

ゴルフバージョン

         0         40  =>   4: N N N N
        66         67  =>   5: S SW N N N
        30          2  =>   2: NE SW
        93          2  =>   2: NE SW
       120         61  =>   8: NW NW NW NW N SE SW N
1493682877          0  =>   4: S S NW NW
         0  368460408  =>  18: SW SW N N SW SW SE SW SW N SE N N SW SW N SE SE
1371432130    1242824  =>  17: NW NW NE NW N SE SW SW SW SE SE SW N N N N SW
    520174 1675046339  =>  23: NE NE NE NE SE SE SW SW N SE N SW N SW SE N N N N SE SE SW SW
 312602548  940907702  =>  19: NE NW S SW N N SW SE SE SE SW SE N N SW SE SE SE SW
1238153746 1371016873  =>  22: NE NE NE SE N N SW N N SW N SE SE SW N SW N N SE N SE N
 547211529 1386725128  =>  23: S S S S NW N N SE N SW N SE SW SE SW N SE SE N SE SW SW N
1162261466 1743392199  =>  38: NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE

効率的なバージョン

         0         40  =>   4: N N N N
        66         67  =>   5: S SW N N N
        30          2  =>   2: NW NW
        93          2  =>   2: NE SW
       120         61  =>   8: NW NW NW NW N SE SW N
1493682877          0  =>   4: NE S NW NW
         0  368460408  =>  18: SW SW N N SW SW SE SW SW N SE N N SW SW N SE SE
1371432130    1242824  =>  17: NW NW NE NW N SE SW SW SW SE SE SW N N N N SW
    520174 1675046339  =>  23: NE NW NE NE SE SE SW SW N SE N SW N SW SE N N N N SE SE SW SW
 312602548  940907702  =>  19: NE NW S SW N N SW SE SE SE SW SE N N SW SE SE SE SW
1238153746 1371016873  =>  22: NE NE NE SE N N SW N N SW N SE SE SW N SW N N SE N SE N
 547211529 1386725128  =>  23: S S S S NW N N SE N SW N SE SW SE SW N SE SE N SE SW SW N
1162261466 1743392199  =>  38: NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE

6
速かった。私があなたに私の挑戦の1つに答えてもらうたびに、それがどれほど幸せなのかを話すことはできません。:)
マーティンエンダー

2
@MartinBüttnerありがとう、それは大きなcompめ言葉です!FWIW、私はあなたの課題を解決することをとても楽しんでいます。私はこれがまだサンドボックスにある間にこの作業を始めたかもしれませんし、そうでないかもしれません... :)
Ell

2
アドレッシングスキームは素晴らしいです。これはすごい。
BrainSteel

1
@BrainSteel私が最初に思いついたのは、そのアドレッシングスキームを試すことでしたが、1時間以内に概念化、実装、および記述されたもの全体を見るのは素晴らしいことです。+1
レベルリバーセント

1
。後に説明するように、異なるアドレス指定方式である私は確信して私は従わないんだけど、あなたはしているが、画像を参照する場合、OP-これに一致するようになっていない@Zymus
エルは

3

APL(Dyalog Unicode)144 132 129 118 133 132 130 124 117 バイトSBCS

APL言語を学ぶには絶好の場所であるThe APL Orchardでゴルフを楽しんでくれたVenとngnに感謝します。⎕IO←0。ゴルフの提案を歓迎します。

編集:n定義方法を変更し、1インデックスから0インデックスに切り替えることにより、Venとngnのおかげで-12バイト。-3すべてが0インデックスに切り替えられなかったバグを修正したため。方法の変更Pおよび-11バイトのQ定義による。+15バイトは、s[⊃⍋|M-s]セクションを理解するのに役立つngnのおかげで、私のアルゴリズムが間違っていた問題を修正したためです。-バックトラッキングパスを見つける方法の再配置から2バイト、およびバグ修正のための+1バイト。-2バイトは、Adámの定義の再編成に感謝しますI。-6バイトは、定義の再編成と定義'S' 'NE' 'NW' 'N' 'SW' 'SE'方法の再編成によるngnのおかげですt(独立した変数ではなくなりました)。-7バイトはゴルフのngnのおかげsで定義されています。

{M←⊃⍸≠⌿↑1+P Q←⍵{(⍵/3)⊤⍺-+/3*⍳⍵}¨⌊31+⍵×2⋄(∪¨↓6 2'SSNENWNNSWSE')[P[I],3+Q↓⍨⊃⌽I←⍬{M≥≢⍵:⍺⋄(⍺∘,∇↑∘⍵)s[⊃⍋|M-s←⌽⊢.⊢⌸⍵]}P]}

オンラインでお試しください!

アルゴリズムのバグの説明

基本的な問題は、最短パスが共通の祖先を直接通過し、実際には共通の祖先の祖先を通過できなかったと考えたことです。次の例が示すように、これは正しくありません。

66から5

66  0 2 2 2  0 2 2 2
5   0 1      0 1
       common ancestor

The two ancestors of 0 2 2 2 are:
0 2 2
(empty)

(empty) has the shorter path back to 0 1 as it only needs two forward moves,
while 0 2 2 requires two more backtracks and one more forward move.

299792458から45687まで

299792458  0 2 1 1 0 1 1 2 1 1 1 2 1 0 2 2 2 0
45687      0 2 1 1 0 1 1 1 2 2
                          common ancestor

The three ancestors of 299792458 are:
0 2 1 1 0 1 1 2 1 1 1 2 1 0 2 2 2
0 2 1 1 0 1 1 2 1 1 1 2             choose this one
0 2 1 1 0 1 1 2 1 1 1 2 1 0 2 2

And the three ancestors of 0 2 1 1 0 1 1 2 1 1 1 2 are:
0 2 1 1
0 2 1 1 0 1 1 2 1 1
0 2 1 1 0 1 1 2 1 1 1

0 2 1 1 0 1 1 1 2 2     45687 for reference
              common ancestor

While it seems like `0 2 1 1` is the shorter path,
it actually results in a path that is 8 steps long
(2 backtracks, 6 forward steps to 45687).

Meanwhile, `0 2 1 1 0 1 1 2 1 1` is at an equal distance
to the common ancestor and has the following ancestors:
0 2 1 1
0 2 1 1 0 1 1 2 1
0 2 1 1 0 1 1

0 2 1 1 0 1 1 1 2 2     45687 for reference
              common ancestor

Clearly, this is the superior path, as with three backtracks, we have reached
the point of the common ancestor. With 3 backtracks and 3 forward moves,
we have a path that is 6 steps long.

コードの説明

                         should be an array of 2 integers, x y
SierpinskiPath←{⎕IO0   0-indexing
         P Q←{...}¨⍵   First, the bijective base-3 numeration of x and y
    P Q←{
        n←⌊31+⍵×2   The number of digits in the numeration
        z←+/3*⍳⍵     The number of numerations with  n digits
        (n/3)⊤⍵-z    And a simple decode  (base conversion) of ⍵-z
    }¨⍵              gets us our numerations, our paths

    A←↑1+P Q       We turn (1+P Q) into an 2-by-longest-path-length array 
                    pads with 0s and our alphabet also uses 0s
                   So we add 1 first to avoid erroneous common ancestor matches
    Common←⊃⍸≠⌿A   We find the length of the common ancestor, Common

         I←⍬{...}P   Now we get the shortest backtracking path from P
    I←⍬{
        Common=≢⍵:⍺        If P is shorter than Common, return our backtrack path
        s←⌽⊢.⊢⌸⍵           Get the indices of the most recent N SW SE
        ts[⊃⍋|Common-s]   and keep the index that is closest to Common
                           and favoring the ancestors to the right of
                           Common in a tiebreaker (which is why we reverse ⊢.⊢⌸⍵)
        (⍺,t)∇t↑⍵          Then repeat this with each new index to backtrack
    }P                     and the path left to backtrack through

    BacktrackP[I]    We get our backtrack directions
    Forward←(⊃⌽I)↓Q   and our forward moves to Q
                      starting from the appropriate ancestor
    (∪¨↓6 2'SSNENWNNSWSE')[Backtrack,Forward]     'S' 'NE' 'NW' 'N' 'SW' 'SE'
}                     and return those directions

Dyalog Extendedとdfnsを使用した代替ソリューション

⎕CY 'dfns'adic関数を使用すると、より少ないバイト数で全単射のbase-n数え上げ(使用するバージョンのインスピレーション)を実装します。Dyalog Extendedに切り替えると、かなりのバイト数が節約されるため、ここにあります。これをゴルフで助けてくれたアダムに感謝します。ゴルフの提案を歓迎します!

編集:方法の変更PQ定義により-8バイト。Dyalog Extendedへの切り替えにより、-14バイト。-2完全なプログラムを使用してdfn大括弧を削除するため{}。+17バイトは、私のアルゴリズムが間違っていた問題を修正したためs[⊃⍋|M-s]です。セクションを理解するためのngnのおかげです。バグ修正のために+1バイト。Adámの定義の再編成による-2バイトI と、両方のソリューションにゴルフを入れることを覚えていることによる-1バイトのおかげです。枢軸方向の生成を再配置することでngnのおかげで-3バイト、バギーゴルフを修正することで+1バイト、t定義方法を変更することでngnのおかげで-3バイト(独立した変数ではなくなりました)。-7バイトsは、定義方法を再配置することでngnに感謝します。

APL(Dyalog Extended)123 115 101 99 116 117 114 109 102バイト

M←⊃⍸≠⌿↑1+P Q←(⍳3)∘⌂adic¨⎕⋄(∪¨↓6 2'SSNENWNNSWSE')[P[I],3+Q↓⍨⊃⌽I←⍬{M≥≢⍵:⍺⋄(⍺∘,∇↑∘⍵){⍵[⊃⍋|M-⍵]}⌽⊢.⊢⌸⍵}P]

オンラインでお試しください!


66と1の場合、これは0を経由して最短の道を見つけることができません
クリスチャンのSievers

@ChristianSieversあなたは絶対に正しいです、私はまだこれを修正する方法がわかりません。知らせてくれてありがとうございます。
Sherlock9
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.