インシデントトークナイザーを書く


24

バックグラウンド

インシデントは、トークンのリストが事前に決定されておらず、入力から推測されるという点で、かなり珍しいプログラミング言語です。そのため、インシデントプログラムのトークン化は、特に効率的に行う場合は特に困難です。このタスクは、それを自分で行うことです。

タスク

プログラムには入力として文字列が与えられます。インシデントがトークン化に使用するアルゴリズムは次のとおりです。

  1. 入力の部分文字列として発生するすべての文字列を正確に3つの方法で識別します(つまり、入力内にその文字列が正確に3回出現します)。
  2. 別のそのような文字列の部分文字列であり、これらのいずれかの文字列を破棄し(例えば、入力のためにababab、唯一の残りの文字列は次のようになりabません、aまたはbので、abの両方のサブストリングですab)。
  3. 入力内で重複する文字列を破棄します。(たとえば、のaaaa3つのコピーが含まれていますaaが、これらのコピーは2番目と3番目の文字で重複しているため、破棄されます。同様に、のabababa3つのコピーabと3つのコピーがbaありますが、2番目から6番目の文字はそれぞれ重なりabba、両方そうabba)は廃棄されるであろう。
  4. この時点で残っている文字列は、プログラムで使用されるトークンです。元の入力をこれらのトークンのシーケンスにトークン化します(前のステップでの破棄のため、それを行う方法は1つしかありません)。トークンの一部ではない入力の文字はコメントとして扱われ、破棄されます。

プログラムは入力として文字列を受け取り、出力として文字列の対応するトークン化(それぞれが文字列として表されるトークンのリスト)を返す必要があります。さらに、これは少なくとも中程度の効率で実行する必要があります。具体的には、プログラムは2次時間( "O(n²)")以上で実行する必要があります。(ちなみに、2次よりも速くなることはほぼ確実に可能ですが、これはではないため、複雑な範囲内に収まる最高のアルゴリズムを自由に使用してください。)

明確化

  • 理論上、インシデントプログラムには256オクテットのいずれかを含めることができますが、このチャレンジの目的では、プログラムが印刷可能なASCII(スペースを含む)、および改行とタブから形成された入力のみを処理することは許容されます。(すべての既知のインシデントプログラムは、このサブセットに制限されています)。スペース/改行/タブは特別ではなく、トークンの途中に表示されることに注意してください。インシデントは256オクテットすべてを不透明として扱います。
  • 「二次時間」の定義は、「入力のサイズが2倍になった場合、プログラムは定数に4倍を加えただけ遅くなります」、つまりtx)がプログラムにかかる最大時間サイズxの入力を処理する場合、すべてのxに対してt(2  x)<4  tx)+ kとなるような定数kが必要です。文字列の比較には、文字列の長さに比例した時間がかかることに注意してください。
  • あなたのプログラムは理論的には無制限のメモリを持ち、無制限の整数を使用するあなたの言語の(おそらく仮定の)バリアントで実行される場合、任意の長さの入力プログラムを処理できるはずです言語の整数またはメモリは実際には有限の大きさです)。(複雑さを計算する目的で)入力の長さ以下の整数を一定時間で比較できると仮定することができます(たとえば、入力を単一の整数、彼らは持っている桁数に比例して比較するのに時間がかかります)。
  • 上記のアルゴリズムと同じ手順に従わない場合でも、同じ結果が得られる限り、複雑性の範囲内に収まる任意のアルゴリズムを使用できます。
  • このパズルは、実際に出力をフォーマットすることではなく、入力をトークン化することに関するものです。ご使用の言語でリストを出力する最も自然な方法があいまいな形式を含む場合(たとえば、文字列にリテラル改行が含まれる場合は改行で区切られ、文字列間に区切り文字がない場合)、出力があいまいになるという事実を心配しないでください(リストが実際に構築されている限り)。テストを支援するために、明確な出力を生成するサブミッションの2番目のバージョンを作成することもできますが、元のバージョンはスコアリングにカウントされるバージョンです。

テストケース

次の入力文字列の場合:

aaabcbcbcdefdfefedghijghighjkllkklmmmmonono-nonppqpq-pqprsrsrstststuvuvu

プログラムは次の出力リストを生成する必要があります。

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

勝利条件

これはであるため、バイト単位で測定された最短の有効な(つまり、正しい入出力動作と実行に十分な高速)プログラムが勝ちます。


削除された投稿を見ることができる人向け:Sandboxの投稿はこちらです。

16
いくつの言語を作成しましたか?...待って、35?!
ルイスメンドー

回答:


14

C(gcc)、324バイト

この関数fは、ヌルで終了する文字列を受け取り、トークンを標準出力に出力します。以下のコードからすべての改行を削除できます。

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

この古い376バイトバージョンは少し読みやすくなっています。以下の説明が適用されます。

*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)Knuth–Morris–Prattアルゴリズムのtパターンのテーブルを生成しますp。 検索文字列K(c)の次の文字cを処理し、最新の処理文字で終わるm最大のプレフィックスの長さを更新しますp

最初のforループでaは、文字列内の各インデックスmについて、文字列全体でから始まる部分文字列を検索するときに、可能な各値が発生する回数をカウントしaます。次にl、でl始まるlength- substring aが正確に3回発生するような最大のものを探します。previous aで見つかった文字列に完全に含まれるほど短い場合は、無視します。重複する場合、前の文字列をから削除し、zどのトークンが保持されるかを記録します。それ以外の場合、その長さはに格納されzます。

次に、KMPを再度使用して、文字列でに記録されたトークンを検索しますz。のエントリが0の場所でそれらのいずれかが見つかった場合z、このトークンは重複により削除されたことがわかります。トークンが削除されなかった場合は、印刷されます。


1
これの時間の複雑さは何ですか?O(n^2)より速くなければなりません。そして、なぜそこ!!にいるの!!z[x-m]ですか?
Yytsi

2
@TuukkaXまさにO(n ^ 2)です。*Z他の出現のいずれかが、配列中のそれらのインデックスの値0を持っている、またはそうでなければ同一の値を保つトークン場合は0になる必要がある次のトークンの長さである(その場合には!!z[x-m]1でなければならない
feersum

わかった。しかし、なぜ!!そこにあるのかはまだわかりません。!!xまだである必要がありますかx、それは私が知らないトリックを呼び出しますか?
イッツィー

@TuukkaXまあ、!!x作るxその「truthiness」を表すブール値を。!!1 == trueそして、そして!!0 == false。私は特にCを知りません、しかし、それはそれが通常行く方法です
コナー・オブライエン

7

JavaScript、878 867 842 825 775 752 717 712 704 673 664 650 641バイト

コードのゴルフを手伝ってくれた@Kritixi Lithosに感謝します。14
バイトのゴルフをしてくれた@ User2428118に感謝します。

(IE7では動作しません)(入力文字列では改行を「\n」、タブを「\t」として入力し、Unicode文字はとして入力する必要があります\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``}

オンラインで試す

仕組みとコードの説明

まず、プログラムは、可能なすべての部分文字列に対してKnuth Morris Pratt配列を生成します。

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

次に、プログラムは、各部分文字列を持つ単語のすべてのインデックスで一致する最大長を見つけます。(これは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);
}

プログラムはこのデータを使用して、文字列の各開始文字に対して3回出現する最も長い部分文字列を見つけます。

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

プログラムはこのデータを使用して、正確に3回出現しないすべての部分文字列と、最も長い有効な部分文字列のすべての部分文字列を削除します。

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

次に、プログラムはすべての重複または部分的なサブストリングを削除するように設定します。

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

削除する値ごとに、同等の部分文字列もすべて削除されます。

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

最後に、プログラムは部分文字列の配列を結合して出力します。


1
あなたはいくつか持っているwhileif彼らの唯一の1文の内側を持っているブロックを。{}これらのステートメントを囲む中括弧を削除できます。たとえば、次のif(word.charAt(index+i)==word.charAt(index+j)){j++;}ようになりますif(word.charAt(index+i)==word.charAt(index+j))j++;
Kritixi Lithos

ステートメント&&を置き換えるためにs を使用ifし、ループ内でステートメントを移動して、それらの下に1つのステートメントがあり、ブレースを削除できるようにしました。私はいくつかのif文を置き換えるために3項を使用しました。私はものを動かして、946バイトで終わりました。あなたが私がやったことを理解していないなら、私に尋ねてください:)
Kritixi Lithos

この時点でゴルフに関する私の主な問題は、そこに書いたことを理解しようとすることです。また、javascriptでゴルフをするためにどのような最適化ができるかわかりません。
-fəˈnɛtɪk

別のチャットルームでこれについて議論しますか?
クリチキシリトス

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.