Schreiben Sie einen Incident-Tokenizer

24

Hintergrund

Incident ist eine ziemlich ungewöhnliche Programmiersprache, da die Liste der Token nicht vorbestimmt ist, sondern aus der Eingabe abgeleitet wird. Daher kann das Tokenisieren eines Incident-Programms ziemlich schwierig sein, insbesondere wenn Sie dies effizient tun möchten. Bei dieser Aufgabe geht es darum, das selbst zu tun.

Die Aufgabe

Ihr Programm erhält als Eingabe einen String. Hier ist der Algorithmus, den Incident verwendet, um es zu kennzeichnen:

  1. Identifizieren Sie alle Zeichenfolgen, die als Teilzeichenfolge der Eingabe vorkommen, auf genau drei Arten (dh, die Zeichenfolge kommt in der Eingabe genau dreimal vor).
  2. Verwerfen Sie alle diese Zeichenfolgen, die eine Teilzeichenfolge einer anderen solchen Zeichenfolge sind (z. B. für die Eingabe wäre abababdie einzige verbleibende Zeichenfolge ab, nicht aoder b, weil aund bbeide Teilzeichenfolgen von ab).
  3. Verwerfen Sie alle Zeichenfolgen, die sich in der Eingabe überschneiden. ( aaaaEnthält z. B. genau drei Kopien von aa, aber diese Kopien überlappen sich beim zweiten und dritten Zeichen, sodass sie verworfen werden. In ähnlicher Weise abababagibt es drei Kopien von abund drei Kopien von ba, aber das zweite bis sechste Zeichen befinden sich jeweils beim Überlappung von a abund a ba, also beide abund bawürden verworfen).
  4. Alle Zeichenfolgen, die an diesem Punkt verbleiben, sind die vom Programm verwendeten Token. Unterteilen Sie die ursprüngliche Eingabe in eine Sequenz dieser Token (aufgrund des Verwerfens im vorherigen Schritt gibt es nur eine Möglichkeit, dies zu tun). Alle Zeichen in der Eingabe, die nicht Teil eines Tokens sind, werden als Kommentare behandelt und verworfen.

Ihr Programm muss eine Zeichenfolge als Eingabe nehmen und die entsprechende Tokenisierung der Zeichenfolge (eine Liste von Token, die jeweils als Zeichenfolgen ausgedrückt werden) als Ausgabe zurückgeben. Darüber hinaus muss dies zumindest mäßig effizient erfolgen; Insbesondere muss das Programm in quadratischer Zeit ("O (n²)") oder besser ausgeführt werden. (Übrigens ist es mit ziemlicher Sicherheit möglich, schneller als quadratisch zu arbeiten, aber dies ist kein Verwenden Sie also den genauesten Algorithmus, der innerhalb der Komplexitätsgrenzen liegt.)

Klarstellungen

  • Obwohl Incident-Programme theoretisch jedes der 256 Oktette enthalten können, ist es für den Zweck dieser Herausforderung für Ihr Programm akzeptabel, nur Eingaben zu verarbeiten, die aus druckbarem ASCII (einschließlich Leerzeichen) sowie Zeilenumbrüchen und Tabulatoren bestehen. (Alle bekannten Incident-Programme beschränken sich auf diese Untermenge.) Beachten Sie, dass Leerzeichen / Zeilenumbrüche / Tabulatoren nichts Besonderes sind und in der Mitte von Token angezeigt werden können. Incident behandelt alle 256 Oktette als undurchsichtig.
  • Die Definition von "quadratischer Zeit" ist "wenn die Größe der Eingabe verdoppelt wird, läuft das Programm um nicht mehr als eine Konstante plus einen Faktor von 4 langsamer", dh wenn t ( x ) die maximale Zeit ist, die Ihr Programm benötigt Verarbeiten Sie eine Eingabe der Größe x , dann muss es eine Konstante k geben, so dass t (2  x ) <4  t ( x ) + k für alle x gilt . Beachten Sie, dass das Vergleichen von Zeichenfolgen eine Zeit benötigt, die proportional zur Länge der Zeichenfolgen ist.
  • Ihr Programm sollte theoretisch in der Lage sein, Eingabeprogramme beliebiger Länge zu verarbeiten, wenn es in einer (möglicherweise hypothetischen) Variante Ihrer Sprache mit unbegrenztem Speicher und unbegrenzten Ganzzahlen ausgeführt wird (es ist in Ordnung, wenn das Programm dieses Ziel in der Praxis aufgrund von nicht erreicht die ganzen Zahlen oder das Gedächtnis der Sprache sind tatsächlich endlich groß). Sie können davon ausgehen (um die Komplexität zu berechnen), dass ganze Zahlen, die nicht länger als die Länge der Eingabe sind, in konstanter Zeit verglichen werden können (denken Sie jedoch daran, wenn Sie größere Werte verwenden, z. B. aufgrund der Konvertierung der Eingabe in a Bei einer einzelnen Ganzzahl dauert der Vergleich proportional zur Anzahl der Stellen länger.
  • Sie können jeden Algorithmus verwenden, der innerhalb der Komplexitätsgrenzen liegt, auch wenn er nicht denselben Schritten wie der oben angegebene Algorithmus folgt, sofern er dieselben Ergebnisse liefert.
  • In diesem Rätsel geht es darum, die Eingabe zu tokenisieren und nicht wirklich die Ausgabe zu formatieren. Wenn die natürlichste Art, eine Liste in Ihrer Sprache auszugeben, ein mehrdeutiges Format beinhaltet (z. B. durch Zeilenumbrüche getrennt, wenn die Zeichenfolgen wörtliche Zeilenumbrüche enthalten oder ohne Trennzeichen zwischen den Zeichenfolgen), machen Sie sich keine Sorgen darüber, dass die Ausgabe mehrdeutig wird ( solange die Liste tatsächlich aufgebaut ist). Möglicherweise möchten Sie eine zweite Version Ihrer Einreichung erstellen, die eine eindeutige Ausgabe liefert, um das Testen zu erleichtern. Die Originalversion ist jedoch die Version, die für die Bewertung zählt.

Testfall

Für die folgende Eingabezeichenfolge:

aaabcbcbcdefdfefedghijghighjkllkklmmmmonono-nonppqpq-pqprsrsrstststuvuvu

Ihr Programm sollte die folgende Ausgabeliste erzeugen:

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

Siegbedingung

Dies ist , daher gewinnt das kürzeste gültige (dh korrekte Eingabe- / Ausgabeverhalten und ausreichend schnell zum Ausführen) Programm, gemessen in Bytes.


quelle
Für Leute, die gelöschte Beiträge sehen können: Der Sandbox-Beitrag war hier .
16
Wie viele Sprachen haben Sie erstellt? ... Warten Sie, 35 ?!
Luis Mendo

Antworten:

14

C (gcc), 324 Bytes

Die Funktion verwendet feine nullterminierte Zeichenfolge und druckt die Token nach stdout. Alle Zeilenumbrüche können aus dem nachfolgenden Code entfernt werden.

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

Diese ältere 376-Byte-Version ist etwas einfacher zu lesen. Die unten stehende Erklärung gilt dafür.

*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)generiert die tMustertabelle pfür den Knuth-Morris-Pratt-Algorithmus. K(c)Verarbeiten des nächsten Zeichens cder Suchzeichenfolge und Aktualisieren m, wobei die Länge des größten Präfixes, pdas gefunden werden kann, auf das zuletzt verarbeitete Zeichen endet.

In der ersten forSchleife wird für jeden Index ain der Zeichenfolge gezählt, wie oft jeder mögliche Wert von mauftritt, wenn in der gesamten Zeichenfolge nach der Teilzeichenfolge gesucht wird, die bei beginnt a. Dann suchen wir die größte, lso dass die Längensubstring lab agenau dreimal vorkam. Wenn es kurz genug ist, um vollständig in einer Zeichenfolge enthalten zu sein, die für eine frühere Zeichenfolge gefunden wurde a, ignorieren wir es. Wenn es sich überschneidet, löschen wir die vorherige Zeichenfolge aus zdem Array und zeichnen auf, welche Token aufbewahrt werden. Andernfalls wird seine Länge in gespeichert z.

Anschließend verwenden wir KMP erneut, um die Zeichenfolge nach den in aufgezeichneten Token zu durchsuchen z. Wenn einer von ihnen an einem Ort mit einem Eintrag von 0 gefunden wird z, wissen wir, dass dieser Token wegen Überlappung gelöscht wurde. Wenn das Token nicht gelöscht wurde, wird es gedruckt.

Feersum
quelle
1
Was ist die zeitliche Komplexität davon? Muss sein O(n^2)oder schneller. Und warum ist da !!bei !!z[x-m]?
Yytsi
2
@ TuukkaX Es ist genau O (n ^ 2). *Zist die Länge des nächsten Tokens, die 0 werden muss, wenn eines der anderen Vorkommen des Tokens den Wert 0 an seinem Index im Array hat oder andernfalls den gleichen Wert !!z[x-m]
beibehält
In Ordung. Aber ich verstehe immer noch nicht, warum das da !!ist. !!xsollte es immer noch sein x, oder ruft es einen Trick auf, von dem ich nichts weiß?
Yytsi
@ TuukkaX Nun, !!xmacht xeinen Booleschen, der seine "Wahrhaftigkeit" darstellt. Also !!1 == trueund !!0 == false. Ich kenne C nicht genau, aber so läuft es normalerweise
Conor O'Brien
7

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

Vielen Dank an @Kritixi Lithos für die Unterstützung beim Golfspielen.
Vielen Dank an @ User2428118 für das Golfspielen mit 14 Bytes

(Funktioniert nicht in IE7) (Zeilenumbrüche sollten als " \n" und Tabulator als " \t" in der Eingabezeichenfolge eingegeben werden, alle Unicode-Zeichen sollten als " " eingegeben werden. \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``}

Probieren Sie es online

Erklärung der Funktionsweise und ungolfed Code

Zunächst generiert das Programm Knuth Morris Pratt-Arrays für jede mögliche Teilzeichenfolge.

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

Als nächstes ermittelt das Programm die maximale Übereinstimmungslänge für jeden einzelnen Index im Wort mit jeder Teilzeichenfolge. (Dies ist O (n ^ 2) Zeit)

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

Das Programm verwendet diese Daten, um die längsten Teilzeichenfolgen zu finden, die für jedes Anfangszeichen in der Zeichenfolge dreimal vorkommen.

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

Das Programm verwendet diese Daten, um alle Teilzeichenfolgen, die nicht genau dreimal vorkommen, und alle Teilzeichenfolgen der längsten gültigen Teilzeichenfolgen zu entfernen.

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

Als nächstes legt das Programm fest, dass alle überlappenden oder partiellen Teilzeichenfolgen entfernt werden sollen.

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

Für jeden zu entfernenden Wert werden auch alle entsprechenden Teilzeichenfolgen entfernt.

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

Schließlich fügt das Programm das Array von Teilzeichenfolgen zusammen und gibt es aus.

fəˈnəˈtɛk
quelle
1
Sie haben einige whileund ifBlöcke, in denen nur 1 Anweisung enthalten ist. Sie können die Klammern {}um diese Anweisung entfernen . Zum Beispiel if(word.charAt(index+i)==word.charAt(index+j)){j++;}kann werdenif(word.charAt(index+i)==word.charAt(index+j))j++;
Kritixi Lithos
Ich habe &&s verwendet, um ifAnweisungen zu ersetzen . Ich habe Anweisungen in Schleifen verschoben, damit sie eine Anweisung unter sich haben, damit ich Klammern entfernen kann. Ich habe Ternaries verwendet, um einige if-Anweisungen zu ersetzen. Ich bewegte Sachen herum und landete bei 946 Bytes . Wenn Sie etwas nicht verstehen, was ich getan habe, können Sie mich
gerne
An dieser Stelle ist mein Hauptproblem beim Golfen, zu verstehen, was ich dort geschrieben habe. Ich weiß auch nicht, welche Optimierungen ich für das Golfspielen in Javascript machen kann.
Freitag,
Möchten Sie dies in einem separaten Chatroom diskutieren?
Kritixi Lithos
14 Bytes gespeichert .
user2428118