Aiuto, sono intrappolato in un triangolo Sierpinski!


44

Disegnare il triangolo Sierpinski è stato fatto a morte . Ci sono altre cose interessanti che possiamo fare con esso però. Se strizziamo gli occhi abbastanza forte nel triangolo, possiamo vedere i triangoli capovolti come nodi di un grafico frattale. Troviamo il nostro modo di aggirare quel grafico!

Innanzitutto, assegniamo un numero a ciascun nodo. Il triangolo capovolto più grande sarà il nodo zero, e quindi scendiamo strato per strato (larghezza prima), assegnando numeri consecutivi nell'ordine in alto a sinistra-a destra:

inserisci qui la descrizione dell'immagine
Fare clic per una versione più grande in cui i numeri piccoli sono un po 'meno sfocati.

(Naturalmente, questo modello continua all'infinito all'interno dei triangoli blu). Un altro modo per definire la numerazione è che il nodo centrale ha indice 0, ei figli di nodo i(triangoli adiacenti del seguente scala ridotta) hanno indici 3i+1, 3i+2e 3i+3.

Come ci muoviamo in questo grafico? Ci sono fino a sei passaggi naturali che uno può prendere da un dato triangolo:

  • È sempre possibile spostarsi attraverso il punto medio di uno dei bordi fino a uno dei tre figli del nodo corrente. Indicheremo queste mosse come N, SWe SE. Ad esempio, se siamo attualmente sul nodo 2, questi porterebbe a nodi 7, 8, 9rispettivamente. Altri movimenti attraverso i bordi (verso discendenti indiretti) non sono consentiti.
  • Si può anche spostarsi attraverso uno dei tre angoli, purché non tocchi il bordo del triangolo, verso il genitore diretto o uno dei due antenati indiretti. Indicheremo queste mosse come S, NEe NW. Ad esempio, se siamo attualmente sul nodo 31, Sporterebbe a 10, non NEsarebbe valido e NWporterebbe a 0.

La sfida

Dati due numeri interi non negativi xe y, trova il percorso più breve da xa y, usando solo le sei mosse descritte sopra. Se esistono diversi percorsi più brevi, emettere uno di essi.

Nota che il tuo codice dovrebbe funzionare per più di solo i 5 livelli illustrati nel diagramma sopra. Si può presumere che x, y < 1743392200. Ciò garantisce che si inseriscano in un numero intero con segno a 32 bit. Nota che questo corrisponde a 20 livelli dell'albero.

Il codice deve elaborare qualsiasi input valido in meno di 5 secondi . Mentre questo esclude una bruta forza all'ampiezza della ricerca, dovrebbe essere un vincolo abbastanza allentato: la mia implementazione di riferimento gestisce input arbitrari per profondità 1000 in mezzo secondo (che sono numeri di ~ 480 cifre per i nodi).

È possibile scrivere un programma o una funzione, prendendo l'input tramite STDIN (o l'alternativa più vicina), l'argomento della riga di comando o l'argomento della funzione e producendo il risultato tramite STDOUT (o l'alternativa più vicina), il valore di ritorno della funzione o il parametro della funzione (out).

L'output dovrebbe essere piatta, elenco inequivocabile delle stringhe N, S, NE, NW, SE, SW, utilizzando qualsiasi separatore ragionevole (spazi, linefeeds, virgole, ","...).

Si applicano le regole standard del .

Casi test

I primi pochi casi di test possono essere elaborati a mano utilizzando lo schema sopra. Gli altri assicurano che le risposte siano sufficientemente efficienti. Per quelli, potrebbero esserci altre soluzioni della stessa lunghezza che non sono elencate.

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

Risposte:


5

Rubino, 195 194 190 184 byte

Originale: con le scuse per Ell , in quanto si tratta essenzialmente di una porta della loro risposta e con molte grazie a Doorknob per il loro aiuto nel debug di questa risposta. Probabilmente c'è un altro algoritmo per questo problema - qualcosa a che fare con *f[x[0,**however far x matches with y**],y]- ma lo salverò per un'altra volta.

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: l'algoritmo goloso non funziona per h[299792458, 1000000]. Ho restituito il conteggio dei byte a 195, mentre riparo il mio algoritmo ancora una volta. Risolto il problema per cui il conteggio dei byte aumentava a 203. Sospiro

In costruzione: questo programma utilizza un avido algoritmo per trovare antenati comuni x[0,j]==y[0,j](nota: potrebbero esserci diversi antenati comuni). L'algoritmo si basa molto liberamente sulla ricerca ricorsiva degli antenati di Ell . La prima metà delle istruzioni risultanti sono come raggiungere questo antenato comune, e la seconda metà è di arrivare a Y sulla base y[j..-1].

Nota: a[n]qui restituisce una numerazione biiettiva base-3 usando le cifre 2,1,0anziché 1,2,3.

Ad esempio, corriamo attraverso f[59,17]o f[[2,0,2,1],[2,1,1]]. Qui, j == 1. Per arrivare x[0,j], andiamo 0o NW. Quindi, per arrivare y, vai [1,1]oSW 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 byte

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)

Una funzione, che faccetta una coppia di numeri di nodo e restituisce il percorso più breve come un elenco di stringhe.

Spiegazione

Iniziamo impiegando un diverso schema di indirizzamento per i triangoli; l'indirizzo di ciascun triangolo è una stringa, definita come segue:

  • L'indirizzo del triangolo centrale è la stringa vuota.

  • Gli indirizzi del nord, sud-ovest, ed i bambini sud-est di ogni triangolo si formano aggiungendo 0, 1e 2, rispettivamente, l'indirizzo del triangolo.

In sostanza, l'indirizzo di ciascun triangolo codifica il percorso (più breve) dal triangolo centrale ad esso. La prima cosa che fa il nostro programma è tradurre i numeri del triangolo di input negli indirizzi corrispondenti.

Figura 1

Clicca l'immagine per una versione più larga.

Le possibili mosse su ciascun triangolo sono facilmente determinate dall'indirizzo:

  • Per spostarsi a nord, sud-ovest, ed i bambini del sud-est, abbiamo semplicemente di aggiunta 0, 1e 2, rispettivamente, l'indirizzo.

  • Per spostarsi a sud, nord-est, e gli antenati del nord-ovest, troviamo l'ultimo (più a destra) verificarsi di 0, 1e 2, rispettivamente, e tagliare il discorso alla sinistra di esso. Se non c'è 0, 1o 2nell'indirizzo, allora l'antenato corrispondente non esiste. Ad esempio, per spostarci nell'antenato nord-occidentale di 112(cioè il suo genitore), troviamo l'ultima occorrenza di 2in 112, che è l'ultimo carattere, e tagliamo l'indirizzo a sinistra di esso, dandoci 11; per spostarci verso l'antenato nord-orientale, troviamo l'ultima occorrenza di 1in 112, che è il secondo carattere, e tagliamo l'indirizzo a sinistra di esso, dandoci 1; tuttavia, 112non ha antenati del sud, poiché non esiste0 nel suo indirizzo.

Nota alcune cose su una coppia di indirizzi xe y:

  • Se xè una sottostringa iniziale di y, allora yè una discendente di x, e quindi il percorso più breve da xa ysegue semplicemente il figlio corrispondente di ciascun triangolo tra xe y; in altre parole, si può sostituire ogni 0, 1e 2in y[len(x):]con N, SWe SE, rispettivamente.

  • Altrimenti, isia l'indice della prima discrepanza tra xe y. Non esiste un percorso da xa yche non passi x[:i](che è lo stesso di y[:i]), cioè il primo antenato comune di xe y. Quindi, qualsiasi percorso da xa ydeve arrivare a x[:i], o uno dei suoi antenati, chiamiamo questo triangolo z, e quindi continuiamo a y. Per arrivare da xa z, seguiamo gli antenati come descritto sopra. Il percorso più breve da za yè indicato dal precedente punto elenco.

Se xè una sottostringa iniziale di y, allora il percorso più breve da xa yè facilmente indicato dal primo punto elenco sopra. Altrimenti, lasciamo jstare il più piccolo degli indici delle ultime occorrenze di 0, 1e 2in x. Se jè maggiore di, o uguale a, l'indice del primo disallineamento tra xe y, i, bisogna semplicemente aggiungere movimento corrispondente ( S, NEo NW, rispettivamente) al percorso, tagliare xa sinistra di j, e continuare. Le cose diventano più complicate se jè inferiore a i, da allora potremmo arrivare al ypiù veloce ascendendo x[:j]direttamente all'antenato comune e scendendo fino ay, o potremmo essere in grado di raggiungere un diverso antenato comune di xe yquesto è più vicino yascendendo a un antenato diverso xa destra di i, e andare da lì a ypiù velocemente. Ad esempio, per ottenere da 1222a 1, il percorso più breve è prima salire al triangolo centrale (il cui indirizzo è la stringa vuota), quindi scendere a 1, cioè la prima mossa ci porta a sinistra del punto di mancata corrispondenza. tuttavia, per arrivare da 1222a 12, il percorso più breve è ascendere a 122, e quindi a 12, cioè, la prima mossa ci tiene alla destra del punto di disallineamento.

Quindi, come possiamo trovare il percorso più breve? Il programma "ufficiale" usa un approccio di forza bruta, provando tutte le possibili mosse verso uno degli antenati ogni volta che xnon è una sottostringa iniziale di y. Non è così male come sembra! Risolve tutti i casi di test, combinati, entro un secondo o due.

Ma poi, ancora una volta, possiamo fare molto meglio: se c'è più di un antenato direttamente raggiungibile a sinistra del punto di disallineamento, abbiamo solo bisogno di testare quello più giusto e se c'è più di un antenato direttamente raggiungibile a destra del punto di disadattamento, dobbiamo solo testare quello più a sinistra. Questo produce un algoritmo di tempo lineare, che scrive la lunghezza di x(cioè la profondità del triangolo sorgente o un tempo proporzionale al logaritmo del numero del triangolo sorgente), che ingrandisce casi di test anche molto più grandi. Il seguente programma implementa questo algoritmo, almeno in sostanza - a causa del golf, la sua complessità è, in effetti, peggiore di quella lineare, ma è ancora molto veloce.

Python 2, 271 266 261 byte

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

Si noti che, a differenza della versione più breve, questa versione è scritta appositamente per non utilizzare la ricorsione nella conversione dei valori di input nei loro indirizzi corrispondenti, in modo che possa gestire valori molto grandi senza traboccare lo stack.

risultati

Il seguente frammento può essere utilizzato per eseguire i test, per entrambe le versioni, e generare i risultati:

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   )

Versione golfizzata

         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

Versione efficiente

         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
Accidenti è stato veloce. Non posso dirti quanto mi renda felice ogni volta che riesco a rispondere a una delle mie sfide. :)
Martin Ender,

2
@ MartinBüttner Grazie, è un grande complimento! FWIW, mi diverto moltissimo a risolvere le tue sfide. Potrei, o no, aver iniziato a lavorare su questo mentre era ancora nella sandbox ... :)
Ell

2
Lo schema di indirizzamento è eccezionale. Questo e spettacolare.
BrainSteel,

1
@BrainSteel la prima cosa che mi è venuta in mente è stata quella di provare quello schema di indirizzamento, ma vedere tutto concettualizzato, implementato e scritto in meno di un'ora è fantastico. +1
Level River St,

1
@Zymus Non sono sicuro di seguirlo, ma se ti riferisci all'immagine, non dovrebbe corrispondere all'OP - questo è uno schema di indirizzamento diverso, come descritto nel post.
Ell

3

APL (Dyalog Unicode) , 144 132 129 118 133 132 130 124 117 byte SBCS

Grazie mille a Ven e ngn per il loro aiuto nel giocare a golf in The APL Orchard , un ottimo posto per imparare la lingua APL. ⎕IO←0. Suggerimenti di golf benvenuti.

Modifica: -12 byte grazie a Ven e ngn cambiando il modo in cui nè definito e passando da 1-indicizzazione a 0-indicizzazione. -3 a causa della correzione di un bug in cui non tutto era passato all'indicizzazione 0. -11 byte a causa della modifica di come Pe Qsono definiti. +15 byte a causa della risoluzione di un problema in cui il mio algoritmo non era corretto con molte grazie a ngn per l'aiuto nella creazione della s[⊃⍋|M-s]sezione. -2 byte dalla riorganizzazione del metodo di ricerca del percorso di backtracking e +1 byte alla correzione dei bug. -2 byte grazie ad Adám dal riordinare la definizione di I. -6 byte grazie a ngn dalla riorganizzazione della definizione 'S' 'NE' 'NW' 'N' 'SW' 'SE'e dalla riorganizzazione della tdefinizione (non è più una variabile separata). -7 byte grazie a ngn dal golf come sviene definito.

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

Provalo online!

Una spiegazione del bug nell'algoritmo

Il problema di base è che sono andato pensando che il percorso più breve passasse direttamente attraverso l'antenato comune e, di fatto, non potevo attraversare un antenato dell'antenato comune. Ciò non è corretto, come dimostrano i seguenti esempi.

Dal 66 al 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.

Dal 299792458 al 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.

Spiegazione del codice

                         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

Soluzione alternativa che utilizza Dyalog Extended e dfns

Se usiamo ⎕CY 'dfns'la adicfunzione, implementa la nostra numerazione base-n biiettiva (che è stata l'ispirazione per la versione che uso) per molti meno byte. Passare a Dyalog Extended fa risparmiare parecchi byte e quindi eccoci qui. Mille grazie ad Adám per il suo aiuto nel golf. Suggerimenti di golf benvenuti!

Modifica: -8 byte a causa della modifica di come Pe Qsono definiti. -14 byte a causa del passaggio a Dyalog Extended. -2 a causa dell'utilizzo di un programma completo per rimuovere le parentesi graffe dfn {}. +17 byte a causa della risoluzione di un problema in cui il mio algoritmo non era corretto con molte grazie a ngn per l'aiuto nella creazione della s[⊃⍋|M-s]sezione. +1 byte alla correzione di bug. -2 byte grazie ad Adám dal riordinare la definizione I e -1 byte dal ricordare di mettere i miei campi da golf in entrambe le soluzioni . -3 byte grazie a ngn riorganizzando la generazione delle direzioni cardinali, +1 byte dalla correzione di un golf con buggy e -3 byte grazie a ngn riorganizzando il modo in cui tè definito (non è più una variabile separata). -7 byte grazie a ngn riorganizzando il modo in cui sè definito.

APL (Dyalog Extended) , 123 115 101 99 116 117 114 109 102 102 byte

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

Provalo online!


Per 66 e 1, questo non trova il modo più breve tramite 0.
Christian Sievers,

@ChristianSievers Hai perfettamente ragione e non sono ancora sicuro di come risolvere questo problema. Grazie per avermi fatto sapere.
Sherlock9,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.