Escreva um tokenizador de incidente

24

fundo

O incidente é uma linguagem de programação bastante incomum, pois sua lista de tokens não é predeterminada, mas inferida a partir da entrada. Como tal, tokenizar um programa de incidentes pode ser bastante difícil, especialmente se você quiser fazer isso com eficiência. Esta tarefa é sobre fazer você mesmo.

A tarefa

Seu programa receberá uma string como entrada. Aqui está o algoritmo que o Incident usa para tokenizá-lo:

  1. Identifique todas as strings que ocorrem como uma substring da entrada de exatamente três maneiras (ou seja, existem exatamente três ocorrências dessa string na entrada).
  2. Descarte qualquer uma dessas cadeias de caracteres que sejam uma subcadeia de outra cadeia de caracteres (por exemplo, para entrada ababab, a única cadeia restante seria ab, não aou b, porque ae bsão as duas subseqüências de ab).
  3. Descarte quaisquer seqüências que se sobreponham na entrada. (Por exemplo, aaaacontém exatamente três cópias de aa, mas essas cópias se sobrepõem ao segundo e terceiro caracteres, seria descartada. Da mesma forma, em abababa, existem três cópias abe três cópias de ba, mas o segundo ao sexto caracteres estão cada um no sobreposição de an abe a ba, então ambos abe baseriam descartados).
  4. Quaisquer strings que permanecem nesse momento são os tokens usados ​​pelo programa. Tokenize a entrada original em uma sequência desses tokens (devido ao descarte na etapa anterior, haverá apenas uma maneira de fazê-lo). Quaisquer caracteres na entrada que não façam parte de nenhum token são tratados como comentários e descartados.

Seu programa precisa pegar uma string como entrada e retornar a tokenização correspondente da string (uma lista de tokens, cada um dos quais é expresso como strings) como saída. Além disso, isso deve ser feito pelo menos moderadamente com eficiência; especificamente, o programa deve ser executado em tempo quadrático ("O (n²)") ou melhor. (Aliás, é quase certamente possível ir mais rápido que o quadrático, mas esse não é o , portanto, sinta-se à vontade para usar o algoritmo tersest que você achar que se encaixa dentro dos limites da complexidade.)

Esclarecimentos

  • Embora os programas de incidentes possam, em teoria, conter qualquer um dos 256 octetos, é aceitável, para o objetivo deste desafio, que seu programa manipule apenas entradas formadas em ASCII imprimível (incluindo espaço), além de nova linha e guia. (Todos os programas de incidentes conhecidos se restringem a esse subconjunto). Observe que espaço / nova linha / guia não são especiais e podem aparecer no meio dos tokens; O incidente trata todos os 256 octetos como opacos.
  • A definição de "tempo quadrático" é "se o tamanho da entrada for duplicado, o programa será executado mais devagar não mais que uma constante mais um fator de 4", ou seja, se t ( x ) for o tempo máximo que seu programa leva para processar uma entrada de tamanho x , então deve haver alguma constante k tal que t (2  x ) <4  t ( x ) + k para todo x . Lembre-se de que comparar cordas leva um tempo proporcional ao comprimento das cordas.
  • Teoricamente, seu programa deve ser capaz de lidar com programas de entrada de qualquer tamanho, se for executado em uma variante (possivelmente hipotética) do seu idioma, com memória ilimitada e usar números inteiros ilimitados (tudo bem se o programa falhar em atingir esse objetivo quando for executado na prática devido a os números inteiros ou a memória do idioma são realmente finitos). Você pode supor (com o objetivo de calcular a complexidade) que números inteiros que não sejam maiores que o comprimento da entrada podem ser comparados em tempo constante (embora tenha em mente que, se você usar valores maiores, por exemplo, devido à conversão da entrada em um inteiro inteiro, eles levarão um tempo para comparar proporcionalmente ao número de dígitos que possuem).
  • Você pode usar qualquer algoritmo que se enquadre nos limites da complexidade, mesmo que não siga as mesmas etapas do algoritmo postado acima, desde que produza os mesmos resultados.
  • Este quebra-cabeça é sobre tokenizar a entrada, não realmente sobre formatar a saída. Se a maneira mais natural de gerar uma lista em seu idioma envolver um formato ambíguo (por exemplo, separados por novas linhas quando as strings contiverem novas linhas literais ou sem delimitadores entre as strings), não se preocupe com o fato de que a saída acaba sendo ambígua ( desde que a lista seja realmente construída). Convém fazer uma segunda versão do seu envio que produza uma saída inequívoca, para ajudar nos testes, mas a versão original é a que conta para a pontuação.

Caso de teste

Para a seguinte sequência de entrada:

aaabcbcbcdefdfefedghijghighjkllkklmmmmonono-nonppqpq-pqprsrsrstststuvuvu

seu programa deve produzir a seguinte lista de saída:

a a a bc bc bc d e f d f e f e d gh gh gh k l l k k l pq pq pq u u u

Condição de vitória

Este é o , portanto o programa mais curto válido (isto é, comportamento correto de entrada / saída e suficientemente rápido para executar), medido em bytes, vence.


fonte
Para pessoas que podem ver postagens excluídas: a postagem da Sandbox estava aqui .
16
Quantas línguas você criou? ... Espere, 35 anos !
Luis Mendo

Respostas:

14

C (gcc), 324 bytes

A função fpega uma sequência terminada em nulo e imprime os tokens em stdout. Todas as novas linhas podem ser removidas do código abaixo.

f(char*s){
int n=strlen(s),b=0,z[n-~n],F[n+1],u,a,x=0,l,m,*t=z+n;
int K(i){~m&&s[i]^s[a+m]?m=t[m],K(i):++m;}
for(;b<2*n;){
for(a=b++%n,m=l=-1;a+l<n;K(a+l))t[++l]=m;
for(l=0;l<n;++F[m])K(l++),F[l]=z[a]*=b>n?m^z[a]||~(m=t[z[l-m]]):0;
for(printf("%.*s",z[a],s+a);n/b*l&&a+l>x;l--)F[l]^3?F[t[l]]+=F[l]:(a<x?z[u]=0:(z[u=a]=l),x=a+l);
}
}

Esta versão antiga de 376 bytes é um pouco mais fácil de ler; a explicação abaixo se aplica a ele.

*t,m;
char*p;
K(c){for(;~m&&c^p[m];)m=t[m];++m;}
k(i){for(*t=m=-1;p[i];t[++i]=m)K(p[i]);m=0;}
f(char*s){
int n=strlen(s),z[n-~n],F[n+1],u,*Z=z,a=0,x=0,l;
for(t=z+n;a<n;a++){
p=s+a;
for(k(l=z[a]=0);l<n;++F[m])K(s[l++]),F[l]=0;
for(;l&&a+l>x;l--)F[l]^3?F[t[l]]+=F[l]:(a<x?z[u]=0:(z[u=a]=l),x=a+l);
}
for(p=s;*p;printf("%.*s",*Z++,p++))
for(k(x=0);x<n;m==*Z?*Z*=!!z[x-m],m=t[m]:0)
K(s[x++]);
}

k(0)gera a tabela tpara o padrão pdo algoritmo Knuth – Morris – Pratt. K(c)processe o próximo caractere cda sequência de pesquisa e as atualizações m, cujo comprimento do maior prefixo ppode ser encontrado terminando no caractere processado mais recentemente.

No primeiro forloop, para cada índice ada string, contamos o número de vezes que cada valor possível mocorre ao pesquisar na string inteira a substring começando em a. Em seguida, procuramos o maior, de lmodo que a lsubseqüência do comprimento iniciada aocorreu exatamente 3 vezes. Se for curto o suficiente para ser totalmente contido por uma string encontrada para uma anterior a, nós a ignoramos. Se ela se sobrepuser, excluiremos a string anterior z, a matriz de gravação da qual os tokens serão mantidos. Caso contrário, seu comprimento é armazenado em z.

Em seguida, usamos o KMP novamente para pesquisar na sequência os tokens registrados z. Se um deles for encontrado em um local com uma entrada 0 z, sabemos que esse token foi excluído devido à sobreposição. Se o token não foi excluído, ele é impresso.

feersum
fonte
11
Qual é a complexidade temporal disso? Tem que ser O(n^2)ou mais rápido. E porque há !!em !!z[x-m]?
Yytsi 22/01
2
@TuukkaX É exatamente O (n ^ 2). *Zé o tamanho do próximo token que precisa se tornar 0 se alguma das outras ocorrências do token tiver o valor 0 em seu índice na matriz ou manter o mesmo valor caso contrário (nesse caso, !!z[x-m]deve ser 1.
feersum
Bem. Mas ainda não entendo o porquê !!disso. !!xainda deve ser xou invoca um truque que não conheço?
Yytsi 23/01
@TuukkaX Bem, !!xfaz xum booleano representando sua "veracidade". Então !!1 == truee !!0 == false. Eu não sei C especificamente, mas é assim que costuma acontecer #
Conor O'Brien
7

JavaScript, 878 867 842 825 775 752 717 712 704 673 664 650 641 bytes

Obrigado a @Kritixi Lithos por ajudar no código de golfe
Obrigado a @ User2428118 por jogar 14 bytes

(Não funcionará no IE7) (as novas linhas devem ser inseridas como " \n" e a tabulação como " \t" na sequência de entrada, os caracteres unicode devem ser inseridos como \u####)

w=>{for(a=[],b=[],c=[],d=[],f=[],e=[],k=0;k<(g=w.length);a[k++]=h)for(b[R='push']([]),h=[d[k]=f[k]=j=i=0];i++<g-k;){while(j&&w[k+i]!=w[k+j])j=h[j-1];w[k+i]==w[k+j]&&j++,h[R](j)}for(k=0;k<g;k++)for(j=i=0;i<g;i++)if(w[i]!=w[k+j]){while(j&&w[i]!=w[k+j])j=a[k][j-1];w[i]==w[k+j]?i--:b[k][R](j)}else b[k][R](++j);for(k=0;k<g;c[k++]=l){for(h=f.map(Q=>i=l=0);i<g;)h[b[k][i++]]++;for(;i;)h[i]==3?(l=i,i=0):a[k][--i]?h[a[k][i]]+=h[i+1]:0}for(k=0;g>k++;)for(i=0;(S=c[k])&&i<g;)b[k][i++]==S?d[i-S]=S:0;for(k=0;k<g;k++)for(e[R](w.slice(k,(S=d[k])+k)),i=1;i<S;)f[k+i]=1,f[k]|=S<d[k+i]+i++;f.map((X,i)=>(P=e[i],X?e=e.map(Y=>P==Y?"":Y):0));return e.join``}

Experimente Online

Explicação de como funciona e código não destruído

Primeiro, o programa gera matrizes Knuth Morris Pratt para cada substring possível;

for(index=0;index<word.length;index++){
  kmpArray=[0];
  j=0;
  for(i=1;i<word.length-index;i++){
    while(j&&word.charAt(index+i)!=word.charAt(index+j)){
      j=kmpArray[j-1];
    }
    if(word.charAt(index+i)==word.charAt(index+j)){
      j++;
    }
    kmpArray.push(j);
  }
  kmpArrays.push(kmpArray);
}

Em seguida, o programa encontra os comprimentos máximos correspondentes em cada índice da palavra com cada substring. (este é o tempo O (n ^ 2))

for(index=0;index<word.length;index++){
  j=0;
  matchLength=[];
  for(i=0;i<word.length;i++){
    if(word.charAt(i)!=word.charAt(index+j)){
      while(j&&word.charAt(i)!=word.charAt(index+j)){
        j=kmpArrays[index][j-1];
      }
      if(word.charAt(i)==word.charAt(index+j)){
        i--;
      }else{
        matchLength.push(j);
      }
    }else{
      j++;
      matchLength.push(j);
      if(j==kmpArrays[index].length){
        j=kmpArrays[index][j-1];
      }
    }
  }
  matchLengths.push(matchLength);
}

O programa usa esses dados para encontrar as substrings mais longas que aparecem três vezes para cada caractere inicial da string.

for(index=0;index<word.length;index++){
  counts=[]
  max=0;
  for(i=0;i<=word.length;i++){
    counts.push(0);
  }
  for(i=0;i<word.length;i++){
    counts[matchLengths[index][i]]++;
  }
  for(i=word.length-1;i>0;i--){
    if(counts[i]==3){
      max=i;
      break;
    }
    if(kmpArrays[index][i-1]){ //if this value has a smaller value it could be as well
      counts[kmpArrays[index][i]]+=counts[i-1];
    }
  }
  maxLengths.push(max);
}

O programa usa esses dados para eliminar todas as substrings que não aparecem exatamente três vezes e todas as substrings das substrings válidas mais longas.

for(index=0;index<word.length;index++){
  if(!maxLengths[index])
    continue;
  for(i=0;i<word.length;i++){
    if(matchLengths[index][i]==maxLengths[index]){
      tokens[i-maxLengths[index]+1]=maxLengths[index];
    }
  }
}

Em seguida, o programa define todas as substrings parciais ou sobrepostas a serem removidas.

for(index=0;index<word.length;index++){
  sStrs.push(word.substring(index,tokens[index]+index));
  for(i=1;i<tokens[index];i++){
    toRemove[index+i]=1;
    if(tokens[index]<tokens[index+i]+i){
      toRemove[index]=1;
    }
  }
}

Para cada um dos valores a serem removidos, todas as substrings equivalentes também são removidas.

for(index=0;index<word.length;index++){
  if(toRemove[index]){
    removal=sStrs[index];
    for(i=0;i<3;i++){
      indxOf=sStrs.indexOf(removal);
      sStrs[indxOf]="";
      toRemove[indxOf]=0;
    }
  }
}

Finalmente, o programa une a matriz de substrings e a produz.

fəˈnɛtɪk
fonte
11
Você tem alguns blocos whilee ifque possuem apenas 1 instrução dentro deles. Você pode remover os aparelhos {}ao redor dessas instruções. Por exemplo, if(word.charAt(index+i)==word.charAt(index+j)){j++;}pode se tornarif(word.charAt(index+i)==word.charAt(index+j))j++;
Kritixi Lithos 31/01
Usei &&s para substituir ifinstruções, movi-as em loops para que elas terminassem com uma instrução abaixo delas, para que eu pudesse remover chaves. Eu usei ternários para substituir algumas declarações if. Mudei as coisas e acabei em 946 bytes . Se você não entender algo que eu fiz, não hesite em perguntar-me :)
Kritixi Lithos
Neste ponto, meu principal problema com o golfe é tentar entender o que escrevi lá. Também não sei quais otimizações posso fazer para jogar golfe em javascript.
fəˈnɛtɪk 31/01
Deseja discutir isso em uma sala de bate-papo separada?
Kritixi Lithos
14 bytes salvos .
user2428118