เขียน tokeniser ของเหตุการณ์


24

พื้นหลัง

เหตุการณ์เป็นภาษาการเขียนโปรแกรมที่ค่อนข้างแปลกในรายการของโทเค็นไม่ได้กำหนดไว้ล่วงหน้า แต่อนุมานจากอินพุต ดังนั้นการโทเค็นโปรแกรมเหตุการณ์อาจค่อนข้างยากโดยเฉพาะอย่างยิ่งถ้าคุณต้องการทำอย่างมีประสิทธิภาพ งานนี้เกี่ยวกับการทำด้วยตัวเอง

งาน

โปรแกรมของคุณจะได้รับสตริงเป็นอินพุต นี่คืออัลกอริทึมที่เหตุการณ์ใช้เพื่อทำเครื่องหมาย:

  1. ระบุสตริงทั้งหมดที่เกิดขึ้นเป็นสตริงย่อยของอินพุตในสามวิธีอย่างแน่นอน (เช่นมีสตริงที่เกิดขึ้นสามครั้งภายในอินพุต)
  2. ยกเลิกสตริงใด ๆ ที่เป็นสตริงย่อยของสตริงอื่น (เช่นสำหรับอินพุตabababสตริงที่เหลือเท่านั้นจะabไม่ใช่aหรือbเพราะaและbเป็นสตริงย่อยของทั้งสองab)
  3. ยกเลิกสตริงใด ๆ ที่ทับซ้อนกันภายในอินพุต (ยกตัวอย่างเช่นaaaaมีเพียงแค่สามเล่มaaแต่สำเนาเหล่านี้ทับซ้อนกันที่ตัวละครสองและสามนั้นจะถูกยกเลิก. ในทำนองเดียวกันในabababaมีสามสำเนาabและสามสำเนาของbaแต่สองตัวละครที่หกแต่ละที่ ทับซ้อนของabและbaดังนั้นทั้งสองabและbaจะถูกทิ้ง)
  4. สตริงใด ๆ ที่ยังคงอยู่ ณ จุดนี้เป็นโทเค็นที่ใช้โดยโปรแกรม โทเค็นอินพุตต้นฉบับเป็นลำดับโทเค็นเหล่านี้ (เนื่องจากการละทิ้งในขั้นตอนก่อนหน้าจะมีเพียงวิธีเดียวเท่านั้นที่ทำได้) อักขระใด ๆ ในอินพุตที่ไม่ได้เป็นส่วนหนึ่งของโทเค็นจะถือว่าเป็นความคิดเห็นและถูกละทิ้ง

โปรแกรมของคุณจะต้องใช้สตริงเป็นอินพุตและส่งกลับโทเค็นที่สอดคล้องกันของสตริง (รายการโทเค็นซึ่งแต่ละรายการจะแสดงเป็นสตริง) เป็นเอาต์พุต นอกจากนี้สิ่งนี้จะต้องทำอย่างน้อยในระดับปานกลางอย่างมีประสิทธิภาพ โดยเฉพาะโปรแกรมจะต้องทำงานในเวลากำลังสอง ("O (n²)") หรือดีกว่า (บังเอิญมันเป็นไปได้ที่จะเร็วกว่าสมการกำลังสอง แต่นี่ไม่ใช่ดังนั้นอย่าลังเลที่จะใช้อัลกอริธึมที่ยากที่สุดที่คุณจะพบว่าเหมาะกับขอบเขตความซับซ้อน)

ชี้แจง

  • แม้ว่าในทางทฤษฎีแล้วโปรแกรมสามารถมี 256 octets ใด ๆ ก็ตามมันเป็นที่ยอมรับได้สำหรับวัตถุประสงค์ของการท้าทายนี้สำหรับโปรแกรมของคุณที่จะจัดการเฉพาะอินพุตที่เกิดขึ้นจาก ASCII ที่พิมพ์ได้ (รวมถึงช่องว่าง) รวมถึงบรรทัดใหม่และแท็บ (โปรแกรมเหตุการณ์ที่รู้จักทั้งหมด จำกัด ตัวเองไว้ในชุดย่อยนี้) โปรดทราบว่าช่องว่าง / บรรทัดใหม่ / แท็บไม่ได้พิเศษและสามารถปรากฏในกลางโทเค็น เหตุการณ์ถือว่า 256 octets ทั้งหมดเป็นทึบแสง
  • คำจำกัดความของ "กำลังสองกำลังสอง" คือ "ถ้าขนาดของอินพุตเป็นสองเท่าโปรแกรมจะทำงานช้าลงโดยไม่เกินค่าคงที่บวกกับปัจจัย 4" คือถ้าt ( x ) เป็นเวลาสูงสุดที่โปรแกรมของคุณใช้เพื่อ ดำเนินการป้อนข้อมูลของขนาดxแล้วจะต้องมีอย่างต่อเนื่องบางkดังกล่าวว่าที (2  x ) <4  T ( x ) + kสำหรับทุกx โปรดจำไว้ว่าการเปรียบเทียบสตริงต้องใช้เวลาเป็นสัดส่วนกับความยาวของสตริง
  • โปรแกรมของคุณในทางทฤษฎีควรสามารถจัดการกับโปรแกรมอินพุตที่มีความยาวเท่าใดก็ได้หากใช้งานในภาษาของคุณที่มีหน่วยความจำไม่ จำกัด และใช้จำนวนเต็มที่ไม่ จำกัด (มันก็โอเคถ้าโปรแกรมล้มเหลวในการบรรลุเป้าหมายนี้ จำนวนเต็มหรือหน่วยความจำของภาษามีขนาดใหญ่มากจริง ๆ ) คุณอาจจะสมมติ (สำหรับจุดประสงค์ในการคำนวณความซับซ้อน) ว่าจำนวนเต็มที่ไม่เกินความยาวของอินพุตสามารถเปรียบเทียบได้ในเวลาคงที่ (แม้ว่าโปรดทราบว่าหากคุณใช้ค่าที่มีขนาดใหญ่กว่าเช่นเนื่องจากการแปลงอินพุตเป็น จำนวนเต็มเดียวพวกเขาจะใช้เวลาในการเปรียบเทียบสัดส่วนกับจำนวนหลักที่พวกเขามี)
  • คุณสามารถใช้อัลกอริทึมใดก็ได้ที่เหมาะกับขอบเขตความซับซ้อนแม้ว่ามันจะไม่ปฏิบัติตามขั้นตอนเดียวกับอัลกอริทึมที่โพสต์ข้างต้นตราบใดที่มันให้ผลลัพธ์ที่เหมือนกัน
  • ปริศนานี้เกี่ยวกับการโทเค็นอินพุตไม่ใช่การจัดรูปแบบเอาต์พุตจริง ๆ หากวิธีที่เป็นธรรมชาติที่สุดในการส่งออกรายการในภาษาของคุณเกี่ยวข้องกับรูปแบบที่คลุมเครือ (เช่นการขึ้นบรรทัดใหม่คั่นเมื่อสตริงมีการขึ้นบรรทัดใหม่ตามตัวอักษรหรือไม่มีตัวคั่นระหว่างสตริง) ไม่ต้องกังวลเกี่ยวกับข้อเท็จจริงที่ว่า ตราบใดที่รายการถูกสร้างขึ้นจริง) คุณอาจต้องการสร้างเวอร์ชันที่สองของการส่งของคุณที่สร้างผลลัพธ์ที่ไม่น่าสงสัยเพื่อช่วยในการทดสอบ แต่เวอร์ชันดั้งเดิมเป็นเวอร์ชันที่นับสำหรับการให้คะแนน

กรณีทดสอบ

สำหรับสตริงอินพุตต่อไปนี้:

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 ?!
Luis Mendo

คำตอบ:


14

C (gcc), 324 ไบต์

ฟังก์ชันfใช้สตริงที่สิ้นสุดด้วยค่า null และพิมพ์โทเค็นไปยัง 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)สร้างตารางtสำหรับรูปแบบpสำหรับอัลกอริทึม Knuth – Morris – Pratt K(c)ประมวลผลอักขระถัดไปcของสตริงการค้นหาและการอัพเดตmความยาวของคำนำหน้าที่ใหญ่ที่สุดpซึ่งสามารถพบได้สิ้นสุดที่อักขระที่ประมวลผลล่าสุด

ในครั้งแรกที่forห่วงสำหรับแต่ละดัชนีaในสตริงเราจะนับจำนวนครั้งที่แต่ละค่าที่เป็นไปได้ของการmเกิดขึ้นเมื่อการค้นหาในสตริงทั้งหมดสำหรับ substring aเริ่มต้นที่ จากนั้นเรามองหาสิ่งที่ใหญ่ที่สุดlเช่นความยาวlสตริงย่อยเริ่มต้นที่aเกิดขึ้น 3 ครั้ง หากมันสั้นพอที่จะมีสตริงทั้งหมดที่ค้นพบก่อนหน้านี้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

Alright แต่ฉันก็ยังไม่เข้าใจว่าทำไมถึง!!มี !!xควรจะยังคงอยู่xหรือมันก่อให้เกิดเคล็ดลับที่ฉันไม่รู้จัก?
Yytsi

@TuukkaX เอ่อ!!xทำxบูลีนแทน "ความจริง" ของมัน ดังนั้นและ!!1 == true !!0 == falseฉันไม่รู้จัก C โดยเฉพาะ แต่นั่นเป็นวิธีที่ปกติแล้ว
Conor O'Brien

7

JavaScript, 878 867 842 825 775 752 717 712 704 673 664 650 641 ไบต์

ขอบคุณ @Kritixi Lithos ที่ช่วยให้ตีกอล์ฟได้
ขอบคุณ @ User2428118 สำหรับการตีกอล์ฟขนาด 14 ไบต์

(จะไม่ทำงานใน IE7) (ควรป้อนบรรทัดใหม่เป็น " \n" และแท็บเป็น " \t" ในสตริงอินพุตอักขระยูนิโค้ดใด ๆ ควรป้อนเป็น\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

ฉันใช้&&s เพื่อแทนที่ifประโยคคำสั่งฉันย้ายข้อความไปรอบ ๆ ในลูปเพื่อที่ว่าพวกเขาจะจบลงด้วยคำสั่งเดียวภายใต้คำสั่งนั้นเพื่อที่ฉันจะได้ลบวงเล็บปีกกา ฉันใช้ ternaries เพื่อแทนที่คำสั่ง if สิ่งที่ผมย้ายไปรอบ ๆ และลงเอยที่946 ไบต์ ถ้าคุณไม่เข้าใจสิ่งที่ฉันไม่รู้สึกลังเลที่จะถามฉัน :)
Kritixi Lithos

ณ จุดนี้ปัญหาหลักของฉันกับการเล่นกอล์ฟก็พยายามที่จะเข้าใจสิ่งที่ฉันเขียนไว้ที่นั่น ฉันยังไม่รู้ด้วยว่าการเพิ่มประสิทธิภาพที่ฉันสามารถทำได้เพื่อเล่นกอล์ฟในจาวาสคริปต์
fəˈnɛtɪk

คุณต้องการพูดคุยเรื่องนี้ในห้องสนทนาแยกต่างหากหรือไม่?
Kritixi Lithos

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.