Scrivi un tokeniser incidente


24

sfondo

L'incidente è un linguaggio di programmazione abbastanza insolito, in quanto il suo elenco di token non è predeterminato, ma piuttosto inferito dall'input. Pertanto, il token di un programma di incidente può essere abbastanza difficile, soprattutto se si desidera farlo in modo efficiente. Questo compito consiste nel farlo da soli.

L'obiettivo

Al tuo programma verrà assegnata una stringa come input. Ecco l'algoritmo che Incident utilizza per tokenizzarlo:

  1. Identifica tutte le stringhe che si presentano come una sottostringa dell'input in esattamente tre modi (cioè ci sono esattamente tre occorrenze di quella stringa all'interno dell'input).
  2. Scarta una qualsiasi di queste stringhe che sono una sottostringa di un'altra di queste stringhe (ad esempio per l'input ababab, l'unica stringa rimanente sarebbe ab, non ao b, perché ae bsono entrambe le sottostringhe di ab).
  3. Elimina tutte le stringhe che si sovrappongono all'interno dell'input. (Ad esempio, aaaacontiene esattamente tre copie di aa, ma queste copie si sovrappongono al secondo e al terzo carattere, quindi verrebbero scartate. Allo stesso modo, in abababa, ci sono tre copie di abe tre copie di ba, ma i caratteri dal secondo al sesto sono ciascuno al si sovrappongono an abe a ba, quindi entrambi abe baverrebbero scartati).
  4. Tutte le stringhe che rimangono a questo punto sono i token utilizzati dal programma. Metti in token l'input originale in una sequenza di questi token (a causa dello scarto nel passaggio precedente, ci sarà solo un modo per farlo). Tutti i caratteri nell'input che non fanno parte di alcun token vengono trattati come commenti e scartati.

Il tuo programma deve prendere una stringa come input e restituire la corrispondente tokenizzazione della stringa (un elenco di token, ognuno dei quali sono espressi come stringhe) come output. Inoltre, questo deve essere fatto almeno moderatamente in modo efficiente; in particolare, il programma deve essere eseguito in un tempo quadratico ("O (n²)") o superiore. (Per inciso, è quasi certamente possibile andare più veloce del quadratico, ma questo non è , quindi sentiti libero di usare l'algoritmo più test che puoi trovare che rientri nei limiti della complessità.)

chiarimenti

  • Sebbene i programmi sugli incidenti possano in teoria contenere uno qualsiasi dei 256 ottetti, è accettabile ai fini di questa sfida per il tuo programma gestire solo input formati da ASCII stampabile (incluso lo spazio), più newline e tab. (Tutti i programmi sugli incidenti noti si limitano a questo sottoinsieme). Nota che spazio / newline / tab non sono speciali e possono apparire al centro dei token; L'incidente tratta tutti i 256 ottetti come opachi.
  • La definizione di "tempo quadratico" è "se la dimensione dell'ingresso è raddoppiata, il programma funzionerà più lentamente di non più di una costante più un fattore di 4", cioè se t ( x ) è il tempo massimo impiegato dal programma elaborare un input di dimensione x , quindi deve esserci una costante k tale che t (2  x ) <4  t ( x ) + k per tutte le x . Tieni presente che il confronto delle stringhe richiede un tempo proporzionale alla lunghezza delle stringhe.
  • Il tuo programma dovrebbe teoricamente essere in grado di gestire programmi di input di qualsiasi lunghezza se eseguito in una variante (possibilmente ipotetica) della tua lingua che ha memoria illimitata e utilizza numeri interi illimitati (va bene se il programma non riesce a raggiungere questo obiettivo quando eseguito nella pratica a causa di gli interi o la memoria del linguaggio sono in realtà finiti in modo molto grande). Si può presumere (ai fini del calcolo della complessità) che numeri interi non superiori alla lunghezza dell'input possano essere confrontati in tempo costante (sebbene si tenga presente che se si utilizzano valori più grandi, ad esempio a causa della conversione dell'input in un singolo intero, impiegheranno un certo tempo per confrontare proporzionale al numero di cifre che hanno).
  • Puoi utilizzare qualsiasi algoritmo che rientri nei limiti di complessità, anche se non segue gli stessi passaggi dell'algoritmo pubblicato sopra, purché produca gli stessi risultati.
  • Questo enigma riguarda il tokenizzazione dell'input, non in realtà la formattazione dell'output. Se il modo più naturale di generare un elenco nella tua lingua prevede un formato ambiguo (ad esempio separato da una nuova riga quando le stringhe contengono nuove righe letterali o senza delimitatori tra le stringhe), non preoccuparti del fatto che l'output risulti ambiguo ( fintanto che l'elenco è effettivamente costruito). Potresti voler creare una seconda versione del tuo invio che produca risultati inequivocabili, per facilitare i test, ma la versione originale è la versione che conta per il punteggio.

Caso di prova

Per la seguente stringa di input:

aaabcbcbcdefdfefedghijghighjkllkklmmmmonono-nonppqpq-pqprsrsrstststuvuvu

il programma dovrebbe produrre il seguente elenco di output:

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

Condizione di vittoria

Si tratta di , quindi vince il programma più breve valido (ovvero corretto comportamento di input / output e sufficientemente veloce da eseguire), misurato in byte.


Per le persone che possono vedere i post eliminati: il post Sandbox era qui .

16
Quante lingue hai creato? ... Aspetta, 35 ?!
Luis Mendo,

Risposte:


14

C (gcc), 324 byte

La funzione faccetta una stringa con terminazione null e stampa i token su stdout. Tutte le nuove righe possono essere rimosse dal codice seguente.

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

Questa versione precedente di 376 byte è un po 'più semplice da leggere; la spiegazione che segue si applica ad esso.

*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 tabella tper il modello pper l'algoritmo Knuth – Morris – Pratt. K(c)elabora il carattere successivo cdella stringa di ricerca e gli aggiornamenti m, la lunghezza del prefisso più grande pche può essere trovata che termina con il carattere elaborato più di recente.

Nel primo forciclo, per ogni indice anella stringa, contiamo il numero di volte in cui si verifica ciascun valore possibile mquando si cerca nell'intera stringa la sottostringa a partire da a. Quindi cerchiamo il più grande in modo ltale che la lunghezza della lsottostringa a partire da sia aavvenuta esattamente 3 volte. Se è abbastanza corto per essere interamente contenuto da una stringa trovata per una precedente a, la ignoriamo. Se si sovrappone, eliminiamo la stringa precedente da z, l'array che registra quali token verranno conservati. Altrimenti, la sua lunghezza è memorizzata in z.

Quindi, utilizziamo nuovamente KMP per cercare nella stringa i token registrati in z. Se uno di questi viene trovato in una posizione con una voce 0 in z, sappiamo che questo token è stato eliminato a causa della sovrapposizione. Se il token non è stato eliminato, viene stampato.


1
Qual è la complessità temporale di questo? Deve essere O(n^2)o più veloce. E perché c'è !!in !!z[x-m]?
Yytsi,

2
@TuukkaX È esattamente O (n ^ 2). *Zè la lunghezza del token successivo che deve diventare 0 se una qualsiasi delle altre occorrenze del token ha il valore 0 nel proprio indice nell'array o mantenere lo stesso valore altrimenti (in tal caso !!z[x-m]dovrebbe essere 1.
feersum

Tutto apposto. Ma ancora non capisco perché !!ci sia. !!xdovrebbe ancora essere xo invoca un trucco che non conosco?
Yytsi,

@TuukkaX Bene, !!xfa xun booleano che rappresenta la sua "verità". Quindi, !!1 == truee !!0 == false. Non conosco C in modo specifico, ma è così che va di solito
Conor O'Brien il

7

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

Grazie a @Kritixi Lithos per aver aiutato a giocare a golf con il codice
Grazie a @ User2428118 per giocare a golf con 14 byte

(Non funzionerà in IE7) (Newlines deve essere inserito come " \n" e tab come " \t" nella stringa di input, tutti i caratteri unicode devono essere inseriti come \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``}

Provalo online

Spiegazione di come funziona e codice non salvato

Innanzitutto, il programma genera array Knuth Morris Pratt per ogni possibile sottostringa;

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

Successivamente, il programma trova le lunghezze massime corrispondenti in ogni singolo indice della parola con ogni sottostringa. (questo è O (n ^ 2) tempo)

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

Il programma utilizza questi dati per trovare le sottostringhe più lunghe che compaiono tre volte per ogni carattere iniziale nella stringa.

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

Il programma utilizza questi dati per eliminare tutte le sottostringhe che non compaiono esattamente tre volte e tutte le sottostringhe delle sottostringhe valide più lunghe.

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

Successivamente, il programma imposta tutte le sottostringhe sovrapposte o parziali da rimuovere.

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

Per ciascuno dei valori da rimuovere, vengono rimosse anche tutte le sottostringhe equivalenti.

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

Infine, il programma unisce insieme l'array di sottostringhe e lo emette.


1
Hai alcuni whilee ifblocchi che hanno solo 1 istruzione al loro interno. È possibile rimuovere le parentesi graffe {}attorno a tale istruzione. Ad esempio, if(word.charAt(index+i)==word.charAt(index+j)){j++;}può diventareif(word.charAt(index+i)==word.charAt(index+j))j++;
Kritixi Lithos il

Ho usato &&s per sostituire le ifistruzioni, ho spostato le istruzioni in giro in modo che finissero con una frase sotto di loro in modo da poter rimuovere le parentesi graffe. Ho usato ternari per sostituire alcune dichiarazioni if. Ho spostato le cose e sono finito a 946 byte . Se non capisci qualcosa che ho fatto, sentiti libero di chiedermelo :)
Kritixi Lithos

A questo punto il mio problema principale con il golf sta cercando di capire cosa ho scritto lì. Inoltre non so quali ottimizzazioni posso fare per giocare a golf in javascript.
fəˈnɛtɪk

Vuoi discuterne in una chat room separata?
Kritixi Lithos

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.