Espiralize uma palavra, triangularmente!

Sep 02 2020

Desafio

Dada uma string de qualquer comprimento, escreva-a como um triângulo, saindo do centro em espiral. Por exemplo, abcdefghijklmnoptorna-se:

   g  
  fah
 edcbi
ponmlkj

Ou mais explicitamente:

Se quiser, você pode fazer uma espiral no sentido anti-horário:

   g  
  haf
 ibcde
jklmnop

Ou adicione espaços uniformemente:

      g   

    f a h 

  e d c b i  

p o n m l k j 

Os caracteres de entrada serão ascii, mas podem incluir espaços. Além disso, o número de caracteres pode não ser um quadrado perfeito ( Hello World!):

  W
  Ho
oller
   !dl

Mais alguns casos extremos. Entrada de 2 letras ab:

 a
  b

E entrada de 3 letras abc:

 a
 cb

Descrição do Procedimento

Caso os exemplos acima não sejam claros, aqui está uma descrição do procedimento do processo:

  1. Anote sua letra inicial.
  2. Mova diagonalmente para baixo e para a direita (ou seja, nesta direção \). Portanto, se você começou em (0,0), agora estará em (1, -1). Coloque sua segunda carta.
  3. Mova para a esquerda um espaço por vez, deixando cair uma letra em cada espaço, para um total de 3 espaços. Isto é, soltar as letras em (0,-1), (-1,-1)e (-2, -1).
  4. Em seguida, mova-se diagonalmente para cima e para a direita /dois espaços, soltando as letras sobre (-1,0)e (0,1).
  5. Agora volte a mover-se diagonalmente para baixo e para a direita, continuando a avançar e soltar letras, desde que sua posição atual seja esquerda-direita adjacente a uma letra existente.
  6. Em seguida, mova para a esquerda novamente, continuando a pisar e soltar letras, desde que você esteja diagonalmente adjacente /a uma letra existente.
  7. Mova diagonalmente para cima e para a direita novamente /, avançando e soltando letras, desde que sua posição atual seja esquerda-direita adjacente a uma letra existente.
  8. Repita as etapas 5 a 7 até que todas as letras sejam usadas.

Mais exemplos

Regras

  • Código de golfe, aplicam-se as regras padrão.
  • Espaços finais ou novas linhas estão ok.
  • Espaços iniciais consistentes ou novas linhas também são aceitáveis, desde que a forma do triângulo seja preservada.

Respostas

8 KevinCruijssen Sep 02 2020 at 15:48

05AB1E , 24 20 15 13 bytes

2Iā¨t·îŽOGsèΛ

-7 bytes por portar @Neil 'resposta carvão s , usando @att ' s fórmula , por isso certifique-se de upvote ambos bem!

Experimente online. Sem suíte de teste, porque o builtin manterá seu conteúdo anterior e não há nenhuma maneira de redefini-lo ( é assim que ele seria .

Explicação:

2              # Push a 2
 I             # Push the input-string
  ā            # Push a list in the range [1,length] (without popping)
   ¨           # Remove the last value to change the range to [1,length)
    t          # Take the square-root of each value
     ·         # Double each
      î        # Ceil each
       ŽOG     # Push compressed integer 6136
          s    # Swap so the list is at the top of the stack again
           è   # Index each value (0-based and modulair) into the 6136
            Λ  # Pop all three and use the Canvas builtin,
               # after which the result is implicitly output immediately afterwards

Veja este 05AB1E ponta do meu (seção Como comprimir grandes inteiros? ) Para entender por que ŽOGé 6136.

O Canvas embutido usa três argumentos para desenhar uma forma:

  • Caractere / string a desenhar: a entrada neste caso
  • Comprimento das linhas que desenharemos: 2neste caso
  • O sentido de desenhar em: [3,6,6,6,1,1,3,3,3,6,6,6,6,6,6,6,1,1,1,1,3,...].

Consulte a resposta original abaixo para obter uma explicação sobre o Canvas embutido. Ao contrário do programa abaixo, onde a lista de comprimentos leva, aqui a lista de direções leva porque usamos um único comprimento de 2.


Resposta original de 24 20 bytes :

ā·Ð·s>ø.ι˜DŠOð׫₆1ªΛ

Contém espaços iniciais / finais e novas linhas (quanto mais longa a entrada, mais espaços / novas linhas)

Experimente online. Sem suíte de teste, porque o embutido manterá seu conteúdo anterior e não há nenhuma maneira de redefini-lo ( é assim que seria , onde os casos de teste são desenhados um sobre o outro).

Explicação:

ā           # Push a list in the range [1, length] of the (implicit) input (without popping)
            #  i.e. "Hello World!" → "Hello World!" and [1,2,3,4,5,6,7,8,9,10,11,12]
 ·          # Double each value in this list
            #  → [2,4,6,8,10,12,14,16,18,20,22,24]
  Ð         # Triplicate it
   ·        # Double each value of the top copy
            #  → [4,8,12,16,20,24,28,32,36,40,44,48]
    s       # Swap to get the other copy
     >      # Increase each by 1
            #  → [3,5,6,9,11,13,15,17,19,21,23,25]
      ø     # Create pairs of the top two lists
            #  → [[4,3],[8,5],[12,7],[16,9],[20,11],[24,13],[28,15],[32,17],[36,19],[40,21],[44,23],[48,25]]
       .ι   # Interleave it with the third list
            #  → [2,[4,3],4,[8,5],6,[12,7],8,[16,9],10,[20,11],12,[24,13],14,[28,15],16,[32,17],18,[36,19],20,[40,21],22,[44,23],24,[48,25]]
         ˜  # Flatten
            #  → [2,4,3,4,8,5,6,12,7,8,16,9,10,20,11,12,24,13,14,28,15,16,32,17,18,36,19,20,40,21,22,44,23,24,48,25]
D           # Duplicate this list of integers
 Š          # Triple-swap, so the stack order is list,input,list
  O         # Pop and sum the top list
            #  → 636
   ð×       # Create a string of that many spaces
     «      # And append it to the string
₆           # Push builtin 36
 1ª         # Convert it to a list of digits, and append 1: [3,6,1]
Λ           # Use the Canvas builtin with these three arguments,
            # after which the result is implicitly output immediately afterwards

O Canvas embutido usa três argumentos para desenhar uma forma:

  • Caractere / string a desenhar: a entrada, neste caso, anexada com espaços à direita
  • Comprimento das linhas que desenharemos: a lista [2,4,3,4,8,5,6,12,7,8,16,9,10,20,11,...]
  • O sentido de desenhar em: [3,6,1]. Os dígitos no intervalo \$[0,7]\$ cada um representa uma certa direção:
7   0   1
  ↖ ↑ ↗
6 ← X → 2
  ↙ ↓ ↘
5   4   3

Portanto [3,6,1], neste caso, traduza para as direções \$[↘,←,↗]\$.

Aqui está uma explicação passo a passo da saída (usaremos a entrada "Hello_World!"como exemplo aqui):

Etapa 1: desenhe 2 caracteres ( "He") na direção 3↘:

H
 e

Etapa 2: desenhe de 4 a 1 caracteres ( "llo") na direção 6←:

  H
olle

Etapa 3: desenhe de 3 a 1 caracteres ( "_W") na direção 1↗:

  W
 _H
olle

Etapa 4: desenhe de 4 a 1 caracteres ( "orl") na direção 3↘:

  W
 _Ho
oller
     l

Etapa 5: desenhe 8-1 caracteres ( "d! ") na direção 6←:

   W
  _Ho
 oller
    !dl

Et cetera para todos os outros espaços à direita.

Veja esta minha dica 05AB1E para uma explicação detalhada sobre o Canvas embutido.

14 att Sep 02 2020 at 11:56

Wolfram Language (Mathematica) , 99 bytes

sPrint@@@Array[s[[4# #-2#+1-#2&@@If[Abs@#2<2#,!##,#-Abs@#2|-#2]]]/._@__->" "&,2{L=Tr[1^s],L},-L]

Experimente online!

Calcula diretamente o índice de cada posição: em coordenadas cartesianas, \$\operatorname{index}(x,y)=\textit{offset}+\begin{cases}2y(2y+1)-x,&|x|<-2y\\ 2\left(y+|x|\right)\left(2\left(y+|x|\right)+1\right)+x,&\text{else}\end{cases}\$

onde \$\textit{offset}\$ é o índice do "primeiro" caractere (1, no Mathematica).

Obtém uma lista de caracteres como entrada.


Abordagem mais antiga, 123 122 109 107 bytes

Print@@@Normal@SparseArray[i=0;p=2Length@#;(p+=ReIm[I[2+I,1-I][[⌈2√i++⌉~Mod~4-1]]-1])->#&/@#,2p," "]&

Experimente online!

A direção do (1-indexado) iº caractere em relação ao caractere anterior pode ser calculada com \$\Big\lceil2\sqrt i\Big\rceil\bmod 4\$:

  • 1: ↗
  • 2: ↘
  • 3,0: ​​←
9 Neil Sep 02 2020 at 07:00

Carvão , 37 36 21 bytes

GH✳✳E⊖LθI§4174⌈⊗₂⊕ι²θ

Experimente online! Nenhum link verboso porque o deverbosifier produz em ✳σvez de ✳✳, gerando código sucinto inválido que não executa corretamente, mas se funcionasse, então seria PolygonHollow(Directions(Map(Decremented(Length(q)), Cast(AtIndex("4174", Ceiling(Doubled(SquareRoot(Incremented(i)))))))), 2, q);. Inspirado na solução 05AB1E de @KevinCruijssen, mas usando a fórmula de @ att para gerar as direções. Explicação:

       θ                Input string
      L                 Length
     ⊖                  Decremented
    E                   Map over implicit range
                  ι     Current index (0-indexed)
                 ⊕      Incremented (i.e. 1-indexed)
                ₂       Square rooted
               ⊗        Doubled
              ⌈         Ceiling
         §4174          Cyclically index to find direction
        I               Cast to integer
  ✳✳                    Convert to directions
GH                 ²θ   Draw path using input string

O comando de desenho de caminho desenha um caractere para o início e depois os n-1caracteres para cada direção da matriz. Infelizmente, não existem cadeias de caracteres únicas que representam direções diagonais, portanto, devo usar inteiros; estes começam à 0direita e aumentam a cada 45 ° no sentido horário.

Solução anterior de 37 bytes:

≔⮌⪪S¹θFLθF³F§⟦⊕⊗ι⁺³×⁴ι⊗⊕ι⟧κ¿θ✳⁻⁷׳κ⊟θ

Experimente online! O link é para a versão detalhada do código. Explicação:

≔⮌⪪S¹θ

Divida a entrada em caracteres e inverta a lista.

FLθ

Faça um loop um número grande o suficiente de vezes.

F³

Faça um loop para cada lado do triângulo.

F§⟦⊕⊗ι⁺³×⁴ι⊗⊕ι⟧κ

Loop para o tamanho da lateral.

¿θ

Verifique se ainda há algo para imprimir.

✳⁻⁷׳κ⊟θ

Imprima o próximo caractere na direção apropriada.

7 MatthewJensen Sep 02 2020 at 07:04

JavaScript (V8) , 222 bytes

s=>(g=(a,b,n=1)=>b?g([(c=(b+' '.repeat(n*8)).slice(0,n*8))[n*6-1],...[...a,c.slice(0,n*4-1)].map((l,i)=>c[n*6+i]+l+c[n*6-2-i])],b.slice(n*8),n+1):a)([s[0]],s.slice(1)).reduce((p,l,i,a)=>p+' '.repeat(a.length-i-1)+l+`
`,'')

Experimente online!

Definitivamente, pode ser mais jogado golfe.
Eu uso um algoritmo recursivo, dividindo a saída em 'camadas' de triângulos, onde cada camada é um envoltório completo (três lados) do triângulo anterior.

Ungolfed

s=>(g=(a,b,n=1)=> // g is a recursive function; a: previous; b: rest; n: increment
  b ? // if there is more string to wrap
    g([ // wrap b around a as a triangle and recurse
        (c=(b+' '.repeat(n*8)).slice(0,n*8))[n*6-1],
        ...[...a,c.slice(0,n*4-1)].map((l,i)=>c[n*6+i]+l+c[n*6-2-i])
      ],
      b.slice(n*8),
      n+1)
  :a // otherwise return the triangle
  )
  ([s[0]],s.slice(1)) // run the function with the first letter and the rest
  .reduce((p,l,i,a)=>p+' '.repeat(a.length-i-1)+l+'\n','') // step the triangle to make it look like it is meant to
7 Arnauld Sep 02 2020 at 19:09

JavaScript (ES8), 137 bytes

Espera uma série de personagens. Retorna uma string.

Esta versão é baseada na fórmula usada por @att , modificada para ser mais amigável ao golfe em JS.

a=>a.map((c,n)=>(m[y+=~(d=2*n**.5-1/n)%4%3?d&++x/x||-1:!x--]=m[y]||[...''.padEnd(x)])[x]=c,m=[],x=y=a.length)&&m.map(r=>r.join``).join`
`

Experimente online! (saída bruta)

Experimente online! (com espaço em branco extra removido)

Quão?

Dada a posição \$n\$do personagem, a direção \$0\le d\le 2\$ pode ser calculado com:

$$d=\left(\left\lfloor2\sqrt{n}+1-\frac{1}{n}\right\rfloor\bmod 4\right)\bmod 3$$

A implementação real de JS é:

~(2 * n ** 0.5 - 1 / n) % 4 % 3

que avalia para \$0\$, \$-1\$ou \$-2\$.


JavaScript (ES8),  163  157 bytes

Espera uma série de personagens. Retorna uma string.

a=>a.map(c=>((m[y]=m[y]||[...''.padEnd(x)])[x]=c,j%3%2?x--:y+=!!++x-j%3,k?k--:k=(n=j/3<<1)+(j++%3||n+2)),m=[],j=k=0,x=y=a.length)&&m.map(r=>r.join``).join`
`

Experimente online! (saída bruta)

Experimente online! (com espaço em branco extra removido)

Quão?

Este é um algoritmo bastante simples que desenha a saída caractere por caractere em uma matriz \$m[\:]\$, acompanhando a posição \$(x,y)\$da caneta, uma direção em \$\{0,1,2\}\$e o numero \$k\$ de caracteres a serem desenhados antes da próxima mudança de direção.

Movemos de acordo com a seguinte tabela:

 direction | moving towards | distance
-----------+----------------+----------
     0     | South-East     |  2t + 1       (t = turn number)
     1     | West           |  4t + 3
     2     | North-East     |  2t + 2

Que dá:

t = 0     t = 1        t = 2            t = 3

                                          2       
                         2               2.       
            2           2.              2..0      
  2        2.          2..0            2....0     
 2X       2.X0        2..X.0          2...X..0    
1110     2....0      2......0        2........0   
        11111110    2........0      2..........0  
                   111111111110    2............0 
                                  1111111111111110

Na implementação JS, não armazenamos a direção explicitamente. Em vez disso, usamos um contador \$j\$indo de \$0\$para \$+\infty\$e usar \$j\bmod 3\$para descobrir a direção atual. Também não armazenamos o número da curva, mas calculamos \$n=2\cdot\lfloor j/3\rfloor\$, usando o valor de \$j\$ antes de ser incrementado para contabilizar a mudança de direção (o que significa que \$n\$é igual a \$2(t-1)\$ao invés de \$2t\$quando a direção envolve \$0\$)

Daí a seguinte tabela:

     j mod 3     |  (j + 1) mod 3  |                     | new starting
 (old direction) | (new direction) |    new distance     | value for k
-----------------+-----------------+---------------------+--------------
        2        |        0        | (n + 2) + 1 = n + 3 |     n + 2
        0        |        1        |      2n + 3         |    2n + 2
        1        |        2        |       n + 2         |     n + 1

E a expressão correspondente a ser atualizada \$k\$:

k = (n = j / 3 << 1) + (j++ % 3 || n + 2)

As coordenadas são atualizadas com:

j % 3 % 2 ?          // if the direction is 1:
  x--                //   decrement x
:                    // else:
  y += !!++x - j % 3 //   increment y if the direction is 0
                     //   or decrement y if it's 2
                     //   increment x in both cases
5 att Sep 04 2020 at 00:19

R , 205 153 147 136 132 126 bytes

-52 de Dominic van Essen.
-4 de Giuseppe.
-4 novamente graças a Giuseppe.
-5 mais graças a Dominic van Essen

function(s,n=nchar(s))for(y in(x=-n:n)*2)cat(ifelse((i=(t=y-2*(r=abs(x))*!r<y)*t-t-2*(r<y)*x+x+1)>n," ",substring(s,i,i)),"
")

Experimente online!

4 DLosc Sep 03 2020 at 09:31

Pip -l , 70 bytes

sMC:Y#ax:w:y/2-/2Ly*2L++i/2{I++v<ys@w@x:a@vi%4%3?++x&i%4=1?--w++w--x}s

Experimente online!

... Não tenho certeza se quero tentar explicar essa monstruosidade em detalhes. A ideia básica é construir um array de espaços 2D superdimensionado ( sMC:#a) e então colocar os caracteres da string de entrada no array com os índices adequados ( s@w@x:a@v). O resto do código descobre quais são os "índices adequados".


Abordagem alternativa, 77 bytes:

a.:sX#aW<|a{UpaWa^@YxNl?v+1++v%2?v*2+1vl:xNl?RV^p.:lv%2?lPEpl.^pAEx}RVsX,#l.l

Experimente online!

Constrói o triângulo como uma lista de linhas, alternando entre adicionar linhas ao início / fim da lista e adicionar caracteres ao início / fim de cada linha. Eu esperava que esse caminho fosse mais curto, mas até agora parece que não é.

4 Bubbler Sep 03 2020 at 13:54

J , 60 bytes

4 :'x(<"1(#x)++/\(3|4|>.2*%:i.#x){<:3 3#:3 2 8)}y',~' '"0/,~

Experimente online!

Resposta obrigatória de J porque é o desafio de Jonas.

Como "substituir uma determinada posição dentro de uma matriz por um valor" não é um verbo, mas um advérbio, ele não pode ser usado em um trem como está, portanto, é encapsulado em um verbo embutido explícito.

Usa a fórmula att para construir as direções.

Como funciona

NB. input: a string (character vector) of length n

,~' '"0/,~  NB. create a large enough canvas (blank matrix of size 2n*2n)
        ,~  NB. concatenate two copies of self
       /    NB. outer product by...
  ' '"0     NB.   a constant function that returns blank per character
,~          NB. concatenate two copies of self

4 :'...'  NB. a dyadic explicit verb, where x is the input string and
          NB. y is the canvas generated above
x(...)}y    NB. replace some places of y by contents of x...
3|4|>.2*%:i.#x  NB. formula by att (gives 0, 1, or 2 per index)
(...){          NB. select the directions based on the above...
<:3 3#:3 2 8    NB. the matrix (0 -1)(-1 1)(1 1) i.e. L/RU/RD
(#x)++/\        NB. take cumulative sum (giving coords to place each char)
                NB. and add n to all elements
<"1             NB. enclose each row to satisfy the input format of }
4 DominicvanEssen Sep 02 2020 at 20:06

R , 270 265 252 243 232 227 225 bytes

Finalmente consegui remover mais 2 caracteres para trazer o total a um número que pode ser representado em forma triangular (como mostrado aqui). O código deve ser formatado convencionalmente para ser executado (como no exemplo em TIO); o '•' representa um \ncaractere de nova linha ( ).

              f
             unc
            tion(
           s,`~`=c
          bind,m=ma
         trix){n=nch
        ar(s)+1;p=m(,
       n^2,2);while(T<
      n){a=4*F;p[T+0:a,
     ]=c(F:-F,(-F:F)[-1]
    )~0:a-2*F;p[T+a+-2:a+
   3,]=(F=F+1)~(b=2*F-1):-
  b;T=T+2*a+4};m=m(" ",n,n)
 ;m[p[2:n-1,]+b+1]=el(strspl
it(s,''));apply(m,1,cat,"•")}

Experimente online!

Observe que esta abordagem foi amplamente superada pela abordagem de att , embora, como consolo, nem essa nem qualquer uma das outras respostas atuais podem ser representadas como um triângulo ...

Funciona construindo as coordenadas para cada letra e, em seguida, usando isso para colocar as letras em uma matriz vazia.

Comentado:

triangle=
function(s){n=nchar(s)          # n is the number of letters
s=el(strsplit(s,''))            # first split the string into individual letters
p=matrix(,2,n^2)                # initialize p as a 2-row matrix to hold the coordinates
                                # (with plenty of columns so that we've enough to go all 
                                # the way round the outermost triangle)
                                # now, F is the current loop, starting at 0
while(T<=n){                    # T is the current letter index
a=4*F+1                         # a=the size of the 'arch' (number of letters going up & over)
p[,T+1:a-1]=                    # set the coordinates for the arch letters...
  rbind(                        # ...(rbind combines rows for y & x coordinates)...
    c(F:-F,(-F:F)[-1]),         # ...to y = F..-F, and then -F+1..F (so: up & then down again)
    1:a-2*F-1)                  # ...and x = across the arch from -2*F to +2*F
a=a+2                           # a=now the width of the base = size of arch + 2
p[,T+a+1:a-3]=                  # now set the coordinates of the base letters...
  rbind(                        #
    F+1,                        # ... base y = row F+1
    (b=2*F+1):-b)               # ... and x = goes (backwards) from 2*F+1..-2*F-1
T=T+2*a-2                       # update the current letter index
F=F+1}                          # increment the loop
p=p[,1:n]                       # delete any excess coordinates
p=p-min(p)+1                    # re-zero the coordinates to remove negatives
m=matrix(" ",b<-max(p),b)       # create a new matrix filled with " "
m[t(p)]=s                       # and fill it with the letters at the right positions
n=apply(m,1,cat,"               # finally, print each row
")}
2 user Sep 03 2020 at 01:33

Scala, 322 318 bytes

s=>((s zip Seq.unfold((0,0,0->0)){case(r,n,y->x)=>Option.when(n<s.size){val(c,t)=(math.sqrt(n).toInt%2,r+1-math.abs(x.sign))
(y->x,(t,n+1,(y+(c-1)*(1-t%2*2),x+1-c*2)))}}groupBy(_._2._1)toSeq)sortBy(_._1)map(_._2.sortBy(_._2._2)map(_._1)mkString)zipWithIndex)map{t=>" "*(math.sqrt(s.size).toInt-t._2)+t._1}mkString "\n"

Experimente no Scastie (não funciona no TIO)

1 KjetilS. Sep 06 2020 at 10:57

Perl 5 , 163 bytes

sub f{                    #newlines and indentation added here for readability. 
  $_=' 'x1e3; @L=(51,$a=-1,-49)x($p=225); for$c(pop=~/./g){
    $P=$p+$L[1]; $a++>0&&s/^(.{$P}) /$1$c/s&&($p=$P,$a=0,shift@L)||substr$_,$p+=$L[0],1,$c
  }
  s/.{50}/$&\n/gr
}

Resumindo, ele adiciona o próximo caractere da entrada na direção atual, a menos que descubra que é hora de mudar de direção.

Experimente online!