Faça um alphabeTrie

31

Considere a seguinte lista de palavras classificadas em ordem alfabética:

balderdash
ballet
balloonfish
balloonist
ballot
brooding
broom

Todas as palavras começam com b, e as 5 primeiras começam com bal. Se apenas olharmos para as 2 primeiras palavras:

balderdash
ballet

nós poderíamos escrever:

balderdash
  +let

onde ' 'é usado onde uma palavra compartilha um caractere de prefixo com a palavra anterior; exceto o '+'caractere que indica o ÚLTIMO caractere em que a segunda palavra compartilha um prefixo com a palavra anterior.

Esta é uma espécie de visualização 'trie' : o pai é ' bal' e tem 2 descendentes: 'derdash'e 'let'.

Com uma lista mais longa, como:

balderdash
ballet
brooding

além disso, podemos usar o caractere de barra vertical '|'para deixar mais claro onde o prefixo compartilhado termina, da seguinte maneira:

balderdash
| +let
+rooding

e a árvore equivalente teria a raiz de 'b'ter dois filhos: a subárvore com raiz ee 'al'seus dois filhos 'derdash'e 'let'; e 'rooding'.

Se aplicarmos essa estratégia à nossa lista original,

balderdash
ballet
balloonfish
balloonist
ballot
brooding
broom

obtemos resultados parecidos com:

balderdash    
| +let     
|  +oonfish
|   | +ist 
|   +t     
+rooding   
   +m 

Se duas palavras consecutivas na lista não tiverem prefixo compartilhado, nenhum caractere especial será substituído; por exemplo, para a lista:

broom
brood
crude
crumb

queremos a saída:

broom
   +d
crude
  +mb

Entrada

As palavras na entrada serão compostas apenas por alfanuméricos (sem espaços ou pontuação); isso pode estar na forma de uma lista de strings, uma única string ou qualquer outra abordagem razoável, desde que você especifique o formato escolhido. Não há duas palavras consecutivas iguais. A lista será classificada em ordem alfabética.

Saída

Sua saída pode conter espaços em branco à direita por linha ou no total, mas nenhum espaço em branco à esquerda. Uma lista de strings ou similar também seria aceitável.

Isso é ; o código mais curto em cada idioma mantém os direitos de se gabar. Aplicam-se as proibições usuais contra brechas.

Casos de teste

Input:
apogee
apology
app
apple
applique
apply
apt

Output:
apogee     
 |+logy    
 +p        
 |+le      
 | +ique   
 | +y      
 +t        

Input:
balderdash
ballet
balloonfish
balloonist
ballot
brooding
broom
donald
donatella
donna
dont
dumb

Output:
balderdash 
| +let     
|  +oonfish
|   | +ist 
|   +t     
+rooding   
   +m      
donald     
| |+tella  
| +na      
| +t       
+umb 
Chas Brown
fonte
E o caso em que tenho a palavra balldepois balloon. Que saída devemos esperar?
Don Thousand
@RushabhMehta Suponho que você teria um +menos do que o primeiro o, mas não escrevi o desafio, então não tenho certeza.
Theo
5
@RushabhMehta As palavras são classificadas em ordem alfabética, portanto isso não acontecerá.
Neil
@ Neil Oh, bom ponto #
Don Thousand
2
As palavras na entrada serão compostas apenas por alfanuméricos : isso realmente inclui dígitos ou você quis dizer alfabético?
Arnauld

Respostas:

11

Retina 0.8.2 , 58 57 bytes

^((.*).)(?<=\b\1.*¶\1)
$.2$* +
m)+`^(.*) (.*¶\1[+|])
$1|$2

Experimente online! O link inclui um caso de teste. Edit: Salvo 1 byte graças a @FryAmTheEggman, apontando que eu ignorei uma mudança de \bpara ^tornada possível pelo m). Explicação:

m)

Ative por linha ^para todo o programa.

^((.*).)(?<=^\1.*¶\1)
$.2$* +

Para cada palavra, tente corresponder o máximo possível desde o início da palavra anterior. Altere a correspondência para espaços, exceto o último caractere, que se torna a +.

+`^(.*) (.*¶\1[+|])
$1|$2

Substitua repetidamente todos os espaços imediatamente acima de +s ou |s por |s.

Neil
fonte
@FryAmTheEggman De fato, eu adicionei o m)especificamente para poder fazer isso, então estou chateado por ter perdido uma instância.
Neil
Ugh, por que eu ainda incomoda responder aos comentários que as pessoas estão indo só para excluí-los ...
Neil
9

JavaScript (ES6), 128 bytes

Espera e retorna uma lista de listas de caracteres.

a=>a.map((w,y)=>a[~y]=w.map(m=(c,x)=>(p=a[y-1]||0,m|=c!=p[x])?c:p[x+1]==w[x+1]?' ':(g=y=>a[y][x]<1?g(y+1,a[y][x]='|'):'+')(-y)))

Experimente online!

Quão?

Os espaços e +'s podem ser inseridos percorrendo a primeira até a última palavra em ordem, mas |só podem ser inseridos a posteriori uma vez+ identificação. Isso pode ser conseguido com duas passagens distintas, mas, em vez disso, salvamos um ponteiro em cada entrada modificada a[~y]para que ela possa ser atualizada mais tarde na mesmamap() loop.

Em teoria, uma solução mais simples seria percorrer as palavras em ordem inversa e reverter a saída também no final do processo. Mas isso é um pouco caro em JS e não encontrei uma maneira de obter uma versão mais curta com esse método.

a =>                           // a[] = input array
  a.map((w, y) =>              // for each word w at position y in a[]:
    a[~y] =                    //   save a pointer to the current entry in a[~y]
    w.map(m =                  //   initialize m to a non-numeric value
      (c, x) => (              //   for each character c at position x in w:
        p = a[y - 1] || 0,     //     p = previous word or a dummy object
        m |= c != p[x]         //     set m = 1 as soon as w differs from p at this position
      ) ?                      //     if w is no longer equal to p:
        c                      //       append c
      :                        //     else:
        p[x + 1] == w[x + 1] ? //       if the next characters are still matching:
          ' '                  //         append a space
        : (                    //       else:
            g = y =>           //         g() = recursive function to insert pipes
            a[y][x] < 1 ?      //           if a[y][x] is a space:
              g(               //             do a recursive call to g()
                y + 1,         //               with y + 1
                a[y][x] = '|'  //               and overwrite a[y][x] with a pipe
              )                //             end of recursive call
            :                  //           else:
              '+'              //             make the whole recursion chain return a '+'
                               //             which will be appended in the current entry
          )(-y)                //         initial call to g() with -y (this is ~y + 1)
    )                          //   end of map() over the characters
  )                            // end of map() over the words
Arnauld
fonte
Se você olhar para a minha solução, eu mesmo a criei, mas ela lembra a sua solução. por isso, se o seu demasiado perto você pode enviá-lo como o seu (ou não) e mal excluí-lo :)
DanielIndie
@DanielIndie Não se preocupe. É diferente o suficiente.
Arnauld
2

JavaScript (Node.js) , 125 122 118 117 bytes

a=>a.map((w,i)=>a[i]=w.map((T,k)=>+i&&l[k]==T?l[-~k]<w[-~k]?(g=_=>a[--i][k]<"+"?g(a[i][k]="|"):"+")():" ":(i=l=w,T)))

Experimente online!

  • obrigado a @Arnauld pelo teste de tio :)
DanielIndie
fonte
1

Python, 263 260 bytes

- 3 bytes graças a Jonathan Frech

Código:

p=lambda t,f,g:"\n".join([(f[:-1]+"+"if(a!=min(t))*g else"")+a+p(t[a],(f+" "if len(t[a])>1or a==max(t)else f[:-1]+"| "),1)for a in t])if t else""
def a(t,x):
 if x:c=x[0];t[c]=c in t and t[c]or{};a(t[c],x[1:])
def f(*s):t={};[a(t,i)for i in s];return p(t,"",0)

Experimente Online!

Explicação:

Essa solução cria um trie das palavras de entrada e a analisa recursivamente na saída necessária. A função a pega um teste e uma string se adiciona x a t. As tentativas são implementadas como dicionários aninhados. Cada dicionário representa um nó na árvore. Por exemplo, o dicionário que representa o trie gerado pelo primeiro caso de teste é assim:

{'b': {'a': {'l': {'d': {'e': {'r': {'d': {'a': {'s': {'h': {}}}}}}}, 'l': {'e': {'t': {}}, 'o': {'o': {'n': {'f': {'i': {'s': {'h': {}}}}, 'i': {'s': {'t': {}}}}}, 't': {}}}}}, 'r': {'o': {'o': {'d': {'i': {'n': {'g': {}}}}, 'm': {}}}}}}

A função p se repete através dessa estrutura e gera a representação em cadeia da árvore esperada pelo desafio. A função f pega um monte de strings como argumentos, adiciona todos eles a um trie com a e, em seguida, retorna o resultado da chamada p no trie.

Zachary Cotton
fonte
1
Possíveis 252 bytes .
Jonathan Frech
1

C (gcc) , 165 155 bytes

Leva três argumentos:

  • char** a : uma matriz de palavras terminadas em nulo
  • char* m : uma matriz do comprimento de cada palavra
  • int n : o número de palavras na matriz
f(a,m,n,i,j)char**a,*m;{for(i=n;--i;)for(j=0;j<m[i]&j<m[i-1]&a[i][j]==a[i-1][j];j++)a[i][j]=a[i][j+1]^a[i-1][j+1]?43:++i<n&j<m[i]&a[i--][j]%81==43?124:32;}

Experimente online!

Curtis Bechtel
fonte
@ Arnauld Claro! Embora não seja um ++i<n&j<m[i]&a[i--]comportamento indefinido? Posso confiar na avaliação do gcc da esquerda para a direita?
Curtis Bechtel
É muito provável que seja um comportamento indefinido. Mas nós definimos as linguagens por sua implementação, portanto, desde que funcione de forma consistente com esta versão do gcc, acho que está bem.
Arnauld
1

Perl 6 , 149 144 142 bytes

{1 while s/(\n.*)\s(.*)$0(\+|\|)/$0|$1$0$2/;$_}o{$=({.[1].subst(/^(.+)<?{.[0].index($0)eq 0}>/,{' 'x$0.ords-1~'+'})}for '',|$_ Z$_).join("
")}

Experimente online!

Tenho certeza de que isso pode ser mais importante, especialmente porque não sou especialista em regexes. Isso usa o mesmo processo da resposta Retina de Neil .

Brincadeira
fonte
0

Python 2 , 191 bytes

def f(w,r=['']):
 for b,c in zip(w[1:],w)[::-1]:
	s='';d=0
	for x,y,z in zip(r[0]+b,b,c+b):t=s[-1:];s=s[:-1]+[['+'*(s>'')+y,t+' |'[x in'+|']][y==z],t+y][d];d=d|(y!=z)
	r=[s]+r
 return[w[0]]+r

Experimente online!

Chas Brown
fonte
0

Ruby , 118 bytes

->a{i=1;a.map{s="";a[i+=j=-1].chars{|c|a[i][j+=1]=i<0&&a[i-1][/^#{s+=c}/]?a[i+1][j]=~/[|+]/??|:?\s:c}[/[| ]\b/]&&=?+}}

Experimente online!

Aceita uma matriz de seqüências de caracteres, saídas modificando a matriz de entrada original no local.

Explicação

A transformação básica de strings não é muito complexa, mas para inserir corretamente os tubos verticais, precisamos iterar na ordem inversa e, como o reversemétodo é bastante detalhado, faremos isso de uma maneira mais complicada. Aqui, usamos mapapenas para executar o loop, deixar a primeira palavra em paz e, em seguida, iterar a partir do final usando índices negativos:

->a{
 i=1;                   #Initialize word indexer
 a.map{                 #Loop
  s="";                 #Initialize lookup string
  a[i+=j=-1]            #Initialize char indexer and decrement i
  .chars{|c|            #Loop through each char c of current word
   a[i][j+=1]=          #Mofify current word at position j 
    i<0&&               #If it's not the first word and
    a[i-1][/^#{s+=c}/]? #Word above matches current one from start to j
     a[i+1][j]=~/[|+]/? #Then if char below is | or +
      ?|:?\s:c          #Then set current char to | Else to Space Else leave as is
  }[/[| ]\b/]&&=?+      #Finally, replace Space or | at word boundary with +
 }
}
Kirill L.
fonte