Escribir un tokeniser de incidentes

24

Fondo

Incident es un lenguaje de programación bastante inusual, ya que su lista de tokens no está predeterminada, sino que se infiere de la entrada. Como tal, tokenizar un programa de Incidentes puede ser bastante difícil, especialmente si desea hacerlo de manera eficiente. Esta tarea se trata de hacerlo tú mismo.

La tarea

Su programa recibirá una cadena como entrada. Aquí está el algoritmo que Incident usa para tokenizarlo:

  1. Identifique todas las cadenas que ocurren como una subcadena de la entrada de exactamente tres maneras (es decir, hay exactamente tres ocurrencias de esa cadena dentro de la entrada).
  2. Descarte cualquiera de estas cadenas que sean una subcadena de otra cadena (por ejemplo, para la entrada ababab, la única cadena restante sería ab, no ao b, porque ay bson ambas subcadenas de ab).
  3. Deseche cualquier cadena que se superponga dentro de la entrada. (Por ejemplo, aaaacontiene exactamente tres copias de aa, pero estas copias se superponen en el segundo y tercer caracteres, por lo que se descartarán. Del mismo modo, en abababa, hay tres copias de aby tres copias de ba, pero los caracteres del segundo al sexto están en el la superposición de una aby ba, por lo tanto ab, y baserían descartados).
  4. Las cadenas que quedan en este punto son los tokens utilizados por el programa. Tokenice la entrada original en una secuencia de estos tokens (debido al descarte en el paso anterior, solo habrá una forma de hacerlo). Los caracteres de la entrada que no forman parte de ningún token se tratan como comentarios y se descartan.

Su programa tiene que tomar una cadena como entrada y devolver la tokenización correspondiente de la cadena (una lista de tokens, cada uno de los cuales se expresa como cadenas) como salida. Además, esto debe hacerse al menos de manera moderadamente eficiente; específicamente, el programa tiene que ejecutarse en tiempo cuadrático ("O (n²)") o mejor. (Por cierto, es casi seguro que sea posible ir más rápido que el cuadrático, pero este no es el , así que siéntase libre de usar el algoritmo más exhaustivo que pueda encontrar que se ajuste a los límites de complejidad).

Aclaraciones

  • Aunque los programas de incidentes pueden contener, en teoría, cualquiera de los 256 octetos, es aceptable para este desafío que su programa maneje solo entradas formadas de ASCII imprimible (incluyendo espacio), además de nueva línea y tabulación. (Todos los programas de incidentes conocidos se limitan a este subconjunto). Tenga en cuenta que space / newline / tab no son especiales y pueden aparecer en medio de tokens; El incidente trata los 256 octetos como opacos.
  • La definición de "tiempo cuadrático" es "si el tamaño de la entrada se duplica, el programa se ejecutará más lentamente en no más de una constante más un factor de 4", es decir, si t ( x ) es el tiempo máximo que tarda su programa en procesar una entrada de tamaño x , entonces debe haber alguna constante k tal que t (2  x ) <4  t ( x ) + k para todo x . Tenga en cuenta que comparar cadenas requiere un tiempo proporcional a la longitud de las cadenas.
  • Teóricamente, su programa debería ser capaz de manejar programas de entrada de cualquier longitud si se ejecuta en una variante (posiblemente hipotética) de su idioma que tiene memoria ilimitada y utiliza enteros ilimitados (está bien si el programa no logra este objetivo cuando se ejecuta en la práctica debido a los enteros o la memoria del lenguaje en realidad son finitamente grandes). Puede suponer (con el propósito de calcular la complejidad) que los enteros que no son mayores que la longitud de la entrada se pueden comparar en tiempo constante (aunque tenga en cuenta que si usa valores más grandes, por ejemplo, debido a la conversión de la entrada en un entero único, tomarán un tiempo para comparar proporcionalmente al número de dígitos que tienen).
  • Puede usar cualquier algoritmo que se ajuste a los límites de complejidad, incluso si no sigue los mismos pasos que el algoritmo publicado anteriormente, siempre que produzca los mismos resultados.
  • Este rompecabezas se trata de tokenizar la entrada, no se trata de formatear la salida. Si la forma más natural de generar una lista en su idioma implica un formato ambiguo (por ejemplo, separado por una nueva línea cuando las cadenas contienen nuevas líneas literales, o sin delimitadores entre las cadenas), no se preocupe por el hecho de que la salida termina siendo ambigua ( siempre y cuando la lista esté realmente construida). Es posible que desee hacer una segunda versión de su envío que produzca resultados inequívocos, para ayudar en las pruebas, pero la versión original es la versión que cuenta para la puntuación.

Caso de prueba

Para la siguiente cadena de entrada:

aaabcbcbcdefdfefedghijghighjkllkklmmmmonono-nonppqpq-pqprsrsrstststuvuvu

su programa debe producir la siguiente lista de resultados:

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

Condición de victoria

Este es el , por lo que gana el programa válido más corto (es decir, el comportamiento correcto de entrada / salida y lo suficientemente rápido para ejecutarse), medido en bytes.


fuente
Para las personas que pueden ver publicaciones eliminadas: la publicación de Sandbox estaba aquí .
16
¿Cuántos idiomas has creado? ... Espera, 35 ?
Luis Mendo

Respuestas:

14

C (gcc), 324 bytes

La función ftoma una cadena terminada en nulo e imprime los tokens en stdout. Todas las líneas nuevas se pueden eliminar del siguiente código.

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 versión anterior de 376 bytes es un poco más fácil de leer; la explicación a continuación se aplica a ello.

*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)genera la tabla tpara el patrón ppara el algoritmo Knuth – Morris – Pratt. K(c)procesa el siguiente carácter cde la cadena de búsqueda y las actualizaciones m, pcuya longitud del prefijo más grande se puede encontrar terminando en el carácter procesado más recientemente.

En el primer forbucle, para cada índice aen la cadena, contamos el número de veces que cada valor posible mocurre cuando se busca en la cadena completa la subcadena que comienza en a. Luego buscamos el más grande de ltal manera que la lsubcadena de longitud que comienza en aexactamente 3 veces. Si es lo suficientemente corto como para estar completamente contenido por una cadena encontrada para un anterior a, lo ignoramos. Si se superpone, eliminamos la cadena anterior de zla matriz que registra los tokens que se guardarán. De lo contrario, su longitud se almacena en z.

Luego, usamos KMP nuevamente para buscar en la cadena los tokens registrados z. Si uno de ellos se encuentra en una ubicación con una entrada 0 z, sabemos que este token se eliminó debido a una superposición. Si el token no se eliminó, se imprime.

Feersum
fuente
1
¿Cuál es la complejidad temporal de esto? Tiene que ser O(n^2)o más rápido. Y ¿por qué hay !!en !!z[x-m]?
Yytsi
2
@TuukkaX Es exactamente O (n ^ 2). *Zes la longitud del próximo token que debe convertirse en 0 si alguna de las otras ocurrencias del token tiene el valor 0 en su índice en la matriz, o de lo contrario mantiene el mismo valor (en ese caso !!z[x-m]debería ser 1.
feersum
Bien. Pero todavía no entiendo por qué !!está ahí. !!xdebería seguir siendo x, o invoca un truco que no conozco?
Yytsi
@TuukkaX Bueno, !!xhace xun booleano que representa su "veracidad". Entonces, !!1 == truey !!0 == false. No sé específicamente C, pero así es como suele ser
Conor O'Brien el
7

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

Gracias a @Kritixi Lithos por ayudar a jugar golf con el código
Gracias a @ User2428118 por jugar golf con 14 bytes

(No funcionará en IE7) (Las líneas nuevas se deben ingresar como " \n" y la pestaña como " \t" en la cadena de entrada, los caracteres Unicode se deben ingresar 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``}

Pruébalo en línea

Explicación de cómo funciona y código no protegido

Primero, el programa genera matrices Knuth Morris Pratt para cada subcadena posible;

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);
}

Luego, el programa encuentra las longitudes máximas de coincidencia en cada índice de la palabra con cada subcadena. (este es el tiempo 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);
}

El programa usa estos datos para encontrar las subcadenas más largas que aparecen tres veces para cada carácter inicial de la cadena.

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);
}

El programa utiliza estos datos para eliminar todas las subcadenas que no aparecen exactamente tres veces y todas las subcadenas de las subcadenas válidas más largas.

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];
    }
  }
}

A continuación, el programa establece todas las subcadenas superpuestas o parciales que se eliminarán.

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 uno de los valores a eliminar, también se eliminan todas las subcadenas equivalentes.

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, el programa une la matriz de subcadenas y la genera.

fəˈnɛtɪk
fuente
1
Tiene alguna whiley ifbloques que tienen sólo un 1 dentro de la declaración de ellos. Puede quitar las llaves {}alrededor de esas declaraciones. Por ejemplo, if(word.charAt(index+i)==word.charAt(index+j)){j++;}puede llegar a serif(word.charAt(index+i)==word.charAt(index+j))j++;
Kritixi Lithos
Solía &&s para reemplazar las ifdeclaraciones, movía las declaraciones en bucles para que terminaran con una declaración debajo de ellas para poder quitar llaves. Usé ternaries para reemplazar algunas declaraciones if. Moví cosas y terminé en 946 bytes . Si no entiendes algo que hice, no
dudes
En este punto, mi principal problema con el golf es tratar de entender lo que escribí allí. Tampoco sé qué optimizaciones puedo hacer para jugar golf en javascript.
fəˈnɛtɪk
¿Quieres discutir esto en una sala de chat separada?
Kritixi Lithos
Guardado 14 bytes .
user2428118