编写事件令牌发布器


24

背景

事件是一种非常不常见的编程语言,它的令牌列表不是预先确定的,而是从输入中推断出来的。这样,对事件程序进行标记可能会非常困难,尤其是如果您想高效地这样做。这项任务是您自己做的。

任务

您的程序将被提供一个字符串作为输入。这是Incident用于标记它的算法:

  1. 完全以三种方式识别作为输入的子字符串出现的所有字符串(即,该字符串在输入中恰好出现了三个)。
  2. 丢弃所有这些字符串中的任何一个,这些字符串是另一个此类字符串的子字符串(例如,对于input ababab,剩下的唯一字符串将是ab,not ab,因为ab都是的子字符串ab)。
  3. 丢弃输入中重叠的所有字符串。(例如,aaaa正好包含的三个副本aa,但是这些副本在第二个和第三个字符处重叠,因此将被丢弃。同样,在中abababa,存在的三个副本ab和的三个副本ba,但第二个至第六个字符分别位于的重叠abba,这样既abba将被丢弃)。
  4. 此时剩下的任何字符串都是程序使用的标记。将原始输入标记为这些标记的序列(由于上一步中的丢弃,只有一种方法可以做到)。输入中不属于任何标记的任何字符都将被视为注释并被丢弃。

您的程序必须将字符串作为输入,并返回该字符串的相应标记化(标记列表,每个标记均表示为字符串)作为输出。此外,这必须至少适度地有效地完成;具体来说,程序必须在二次时间(“ O(n²)”)或更短的时间内运行。(顺便提一下,几乎肯定有可能比平方更快,但这不是,因此可以随意使用最复杂的算法,找到适合复杂度范围的算法。)

澄清说明

  • 尽管理论上事件程序可以包含256个八位字节中的任何一个,但出于此挑战的目的,您的程序只能处理由可打印ASCII(包括空格),换行符和制表符组成的输入,这是可以接受的。(所有已知的突发事件程序都将自己限制在此子集中)。请注意,空格/换行符/制表符不是特殊的,可以出现在标记的中间。事件将所有256个八位位组视为不透明。
  • “二次时间”的定义是“如果输入的大小加倍,则程序将以不超过常数加4的速度慢运行”,即,如果tx)是程序花费的最长时间。处理一个大小为x的输入,则必须有一个常数k,使得所有x的t(2  x)<4  tx)+ k。请记住,比较字符串所花费的时间与字符串的长度成正比。
  • 如果以具有无限内存并使用无界整数的语言(可能是假设的)变体运行,则从理论上讲,您的程序应该能够处理任何长度的输入程序(如果在实践中由于以下原因而无法达到此目标,则可以:语言的整数或内存实际上是有限的)。您可能会假设(出于计算复杂度的目的)可以在恒定时间内比较不大于输入长度的整数(尽管请记住,如果使用较大的值,例如由于将输入转换为整数)整数),它们将花费一定的时间与它们所拥有的位数成正比。
  • 您可以使用适合复杂性范围的任何算法,即使它没有遵循与上面发布的算法相同的步骤,只要它能产生相同的结果即可。
  • 这个难题是关于标记输入,而不是真正格式化输出。如果以您的语言输出列表的最自然的方式是模棱两可的格式(例如,当字符串包含文字换行符或字符串之间没有定界符时,则用换行符分隔),请不必担心输出结果最终会模棱两可( (只要列表是实际构建的)。您可能需要制作第二个版本的提交文件,以产生明确的输出,以帮助测试,但是原始版本是计分的版本。

测试用例

对于以下输入字符串:

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

胜利条件

这是,因此以字节为单位的最短有效(即正确的输入/输出行为和足够快的执行速度)程序获胜。


对于可以看到已删除帖子的人:沙盒帖子在此处

16
您创建了多少种语言?...等等35吗?!
Luis Mendo

Answers:


14

C(gcc),324个字节

该函数f采用以空值结尾的字符串,并将令牌输出到stdout。可以从下面的代码中删除所有换行符。

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模式表pK(c)处理c搜索字符串的下一个字符并进行更新mp可以找到最大前缀的长度以最近处理的字符结尾。

在第一个for循环中,对于a字符串中的每个索引,我们计算m在整个字符串中搜索以开头的子字符串时每个可能出现的值的次数a。然后,我们寻找最大l的长度,使得length- lsubstring从a3 开始恰好出现。如果它足够短,不能完全被上一个找到的字符串包含a,我们将忽略它。如果重叠,我们将从中删除前一个字符串z,该数组记录将保留的令牌。否则,其长度存储在中z

然后,我们再次使用KMP在字符串中搜索记录的令牌z。如果在中的0项所在的位置找到其中一个z,则我们知道此令牌由于重叠而被删除。如果未删除令牌,则将其打印出来。


1
时间的复杂度是多少?必须O(n^2)更快。而为什么!!!!z[x-m]呢?
Yytsi '17

2
@TuukkaX正是O(n ^ 2)。*Z是下一个令牌的长度,如果该令牌的任何其他出现在数组的索引处都为0,或者需要保持相同的值,则该令牌必须变为0(否则!!z[x-m]应为1。)
feersum

好的。但我仍然不明白为什么会在!!那里。!!x应该仍然是x,还是会引发我不知道的窍门?
Yttsi '17

@TuukkaX好吧,!!x创建x一个表示其“真实性”的布尔值。因此,!!1 == true!!0 == false。我不知道的C具体地说,但这就是通常它
康纳尔奥布莱恩

7

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

感谢@Kritixi Lithos帮助编写了代码,
感谢@ 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);
}

程序使用此数据来查找最长的子字符串,该子字符串对于字符串中的每个起始字符都会出现三次。

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

程序使用此数据来消除未完全出现三次的所有子字符串以及最长的有效子字符串中的所有子字符串。

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
您有一些while并且if其中只有1条语句的块。您可以删除{}这些语句周围的花括号。例如,if(word.charAt(index+i)==word.charAt(index+j)){j++;}可以成为if(word.charAt(index+i)==word.charAt(index+j))j++;
Kritixi Lithos'1

我用&&s替换了if语句,我在循环中移动了语句,以便它们最终在它们下面有一条语句,以便删除括号。我用三元数代替了一些if语句。我四处移动,最终达到946字节。如果您不明白我的所作所为,请随时问我:)
Kritixi Lithos

在这一点上,我打高尔夫球的主要问题是试图了解我在那写的东西。我也不知道我可以在javascript中做些什么优化。
fəˈnɛtɪk

您要在单独的聊天室中讨论吗?
Kritixi Lithos

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.