Viết mã thông báo sự cố


24

Lý lịch

Sự cố là một ngôn ngữ lập trình khá bất thường, trong đó danh sách mã thông báo của nó không được xác định trước, mà được suy ra từ đầu vào. Do đó, việc mã hóa chương trình Sự cố có thể khá khó khăn, đặc biệt nếu bạn muốn thực hiện một cách hiệu quả. Nhiệm vụ này là về việc tự làm điều đó.

Nhiệm vụ

Chương trình của bạn sẽ được cung cấp một chuỗi làm đầu vào. Đây là thuật toán mà Incident sử dụng để mã hóa nó:

  1. Xác định tất cả các chuỗi xảy ra dưới dạng một chuỗi con của đầu vào theo đúng ba cách (nghĩa là có chính xác ba lần xuất hiện của chuỗi đó trong đầu vào).
  2. Loại bỏ bất kỳ chuỗi nào trong số các chuỗi này là một chuỗi con của một chuỗi khác như vậy (ví dụ: đối với đầu vào ababab, chuỗi còn lại sẽ là ab, không ahoặc b, bởi vì abcả hai chuỗi con của ab).
  3. Hủy bỏ bất kỳ chuỗi trùng lặp trong đầu vào. (Ví dụ: aaaachứa chính xác ba bản sao aa, nhưng các bản sao này trùng nhau ở các ký tự thứ hai và thứ ba, do đó sẽ bị loại bỏ. Tương tự, trong abababađó, có ba bản sao abvà ba bản sao ba, nhưng mỗi ký tự thứ hai đến thứ sáu chồng chéo của một abvà a ba, vì vậy cả hai abbasẽ bị loại bỏ).
  4. Bất kỳ chuỗi nào còn lại tại thời điểm này là mã thông báo được sử dụng bởi chương trình. Mã thông báo đầu vào ban đầu thành một chuỗi các mã thông báo này (do loại bỏ trong bước trước đó, sẽ chỉ có một cách để làm điều đó). Bất kỳ ký tự nào trong đầu vào không phải là một phần của bất kỳ mã thông báo nào đều được coi là nhận xét và bị loại bỏ.

Chương trình của bạn phải lấy một chuỗi làm đầu vào và trả về mã thông báo tương ứng của chuỗi (danh sách các mã thông báo, mỗi mã được thể hiện dưới dạng chuỗi) làm đầu ra. Ngoài ra, điều này phải được thực hiện ít nhất là hiệu quả vừa phải; cụ thể, chương trình phải chạy trong thời gian bậc hai ("O (n²)") hoặc tốt hơn. (Ngẫu nhiên, gần như chắc chắn có thể đi nhanh hơn bậc hai, nhưng đây không phải là , vì vậy hãy sử dụng thuật toán khó nhất mà bạn có thể tìm thấy phù hợp trong giới hạn phức tạp.)

Làm rõ

  • Mặc dù về mặt lý thuyết, các chương trình sự cố có thể chứa bất kỳ trong số 256 octet, nhưng mục đích của thách thức này là chấp nhận được cho chương trình của bạn để xử lý chỉ các đầu vào được hình thành từ ASCII có thể in (bao gồm cả không gian), cộng với dòng mới và tab. (Tất cả các chương trình sự cố đã biết giới hạn bản thân trong tập hợp con này). Lưu ý rằng dấu cách / dòng mới / tab không đặc biệt và có thể xuất hiện ở giữa các mã thông báo; Sự cố coi tất cả 256 octet là mờ đục.
  • Định nghĩa của "thời gian bậc hai" là "nếu kích thước của đầu vào được nhân đôi, chương trình sẽ chạy chậm hơn không quá một hằng số cộng với hệ số 4", tức là nếu t ( x ) là thời gian tối đa mà chương trình của bạn đạt được xử lý một đầu vào có kích thước x , sau đó phải có một số k không đổi sao cho t (2  x ) <4  t ( x ) + k với mọi x . Hãy nhớ rằng việc so sánh các chuỗi cần thời gian tỷ lệ thuận với độ dài của các chuỗi.
  • Về mặt lý thuyết, chương trình của bạn sẽ có thể xử lý các chương trình đầu vào có độ dài bất kỳ nếu chạy trong một biến thể (có thể là giả thuyết) của ngôn ngữ của bạn có bộ nhớ không giới hạn và sử dụng các số nguyên không giới hạn (không sao nếu chương trình không đạt được mục tiêu này khi chạy trong thực tế do số nguyên hoặc bộ nhớ của ngôn ngữ thực sự rất lớn). Bạn có thể giả định (với mục đích tính toán độ phức tạp) rằng các số nguyên không lớn hơn độ dài của đầu vào có thể được so sánh trong thời gian không đổi (mặc dù lưu ý rằng nếu bạn sử dụng các giá trị lớn hơn, ví dụ do chuyển đổi đầu vào thành số nguyên đơn, chúng sẽ mất một khoảng thời gian để so sánh tỷ lệ với số chữ số chúng có).
  • Bạn có thể sử dụng bất kỳ thuật toán nào phù hợp với giới hạn phức tạp, ngay cả khi nó không tuân theo các bước giống như thuật toán được đăng ở trên, miễn là nó tạo ra kết quả tương tự.
  • Câu đố này là về tokenising đầu vào, không thực sự về định dạng đầu ra. Nếu cách tự nhiên nhất để xuất danh sách bằng ngôn ngữ của bạn liên quan đến định dạng không rõ ràng (ví dụ: phân tách dòng mới khi chuỗi chứa dòng mới bằng chữ hoặc không có dấu phân cách giữa các chuỗi), đừng lo lắng về việc đầu ra kết thúc không rõ ràng ( miễn là danh sách thực sự được xây dựng). Bạn có thể muốn tạo một phiên bản thứ hai của bài nộp của mình để tạo đầu ra rõ ràng, để hỗ trợ kiểm tra, nhưng phiên bản gốc là phiên bản được tính cho việc ghi điểm.

Trường hợp thử nghiệm

Đối với chuỗi đầu vào sau:

aaabcbcbcdefdfefedghijghighjkllkklmmmmonono-nonppqpq-pqprsrsrstststuvuvu

chương trình của bạn sẽ tạo ra danh sách đầu ra sau đây:

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

Điều kiện chiến thắng

Đây là , vì vậy chương trình hợp lệ đầu vào / đầu ra hợp lệ ngắn nhất và đủ nhanh để thực hiện), được đo bằng byte, thắng.


Đối với những người có thể xem các bài đăng đã bị xóa: bài đăng Sandbox đã ở đây .

16
Làm thế nào nhiều ngôn ngữ bạn đã tạo ra? ... Đợi đã, 35 ?!
Luis Mendo

Câu trả lời:


14

C (gcc), 324 byte

Hàm flấy một chuỗi kết thúc null và in các mã thông báo ra thiết bị xuất chuẩn. Tất cả các dòng mới có thể được loại bỏ khỏi mã dưới đây.

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

Phiên bản cũ hơn 376 byte này dễ đọc hơn một chút; lời giải thích dưới đây áp dụng cho nó.

*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ạo bảng tcho mẫu pcho thuật toán Knuth của Morris Morris Pratt. K(c)xử lý ký tự tiếp theo ccủa chuỗi tìm kiếm và cập nhật m, độ dài của tiền tố lớn nhất pcó thể được tìm thấy kết thúc tại ký tự được xử lý gần đây nhất.

Trong forvòng lặp đầu tiên , đối với mỗi chỉ mục atrong chuỗi, chúng tôi đếm số lần mỗi giá trị có thể mxảy ra khi tìm kiếm trong toàn bộ chuỗi cho chuỗi con bắt đầu tại a. Sau đó, chúng tôi tìm kiếm lớn nhất lsao cho lchuỗi con dài bắt đầu axảy ra đúng 3 lần. Nếu nó đủ ngắn để được chứa hoàn toàn bởi một chuỗi được tìm thấy trước đó a, chúng ta sẽ bỏ qua nó. Nếu nó trùng lặp, chúng tôi sẽ xóa chuỗi trước đó z, bản ghi mảng sẽ được lưu giữ. Nếu không, chiều dài của nó được lưu trữ trong z.

Sau đó, chúng tôi sử dụng KMP một lần nữa để tìm kiếm chuỗi cho các mã thông báo được ghi lại z. Nếu một trong số chúng được tìm thấy tại một vị trí có mục 0 z, chúng tôi biết rằng mã thông báo này đã bị xóa do trùng lặp. Nếu mã thông báo không bị xóa, nó được in.


1
Sự phức tạp thời gian của điều này là gì? Phải O(n^2)hoặc nhanh hơn. Và tại sao có !!tại !!z[x-m]?
Yytsi

2
@TuukkaX Chính xác là O (n ^ 2). *Zlà độ dài của mã thông báo tiếp theo cần trở thành 0 nếu bất kỳ sự xuất hiện nào khác của mã thông báo có giá trị 0 tại chỉ mục của chúng trong mảng hoặc giữ nguyên giá trị khác (trong trường hợp đó !!z[x-m]phải là 1.
frageum

Ổn thỏa. Nhưng tôi vẫn không hiểu tại sao !!nó ở đó. !!xvẫn nên x, hoặc nó gọi một mánh khóe mà tôi không biết?
Yytsi

@TuukkaX Vâng, !!xlàm cho xmột boolean đại diện cho "tính trung thực" của nó. Vì vậy, !!1 == true!!0 == false. Tôi không biết cụ thể về C, nhưng đó là cách nó thường diễn ra
Conor O'Brien

7

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

Cảm ơn @Kritixi Lithos đã giúp chơi mã số
Cảm ơn @ User2428118 vì đã chơi golf 14 byte

(Sẽ không hoạt động trong IE7) (Dòng mới nên được nhập dưới dạng " \n" và tab là " \t" trong chuỗi đầu vào, bất kỳ ký tự unicode nào cũng phải được nhập dưới dạng \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``}

Dùng thử trực tuyến

Giải thích về cách thức hoạt động và mã không mã hóa

Đầu tiên, chương trình tạo ra các mảng Knuth Morris Pratt cho mọi chuỗi con có thể;

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

Tiếp theo, chương trình tìm thấy độ dài phù hợp tối đa tại mỗi chỉ mục trong từ với mỗi chuỗi con. (đây là thời gian 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);
}

Chương trình sử dụng dữ liệu này để tìm các chuỗi con dài nhất xuất hiện ba lần cho mỗi ký tự bắt đầu trong chuỗi.

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

Chương trình sử dụng dữ liệu này để loại bỏ tất cả các chuỗi con không xuất hiện chính xác ba lần và tất cả các chuỗi con của các chuỗi con hợp lệ dài nhất.

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

Tiếp theo, chương trình đặt tất cả các chuỗi con chồng chéo hoặc một phần sẽ được loại bỏ.

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

Đối với mỗi giá trị được loại bỏ, tất cả các chuỗi con tương đương cũng được loại bỏ.

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

Cuối cùng, chương trình nối các mảng con lại với nhau và xuất ra nó.


1
Bạn có một số whileifcác khối chỉ có 1 câu lệnh bên trong chúng. Bạn có thể loại bỏ các dấu ngoặc nhọn {}xung quanh các tuyên bố đó. Ví dụ: if(word.charAt(index+i)==word.charAt(index+j)){j++;}có thể trở thànhif(word.charAt(index+i)==word.charAt(index+j))j++;
Kritixi Lithos

Tôi đã sử dụng &&s để thay thế các ifcâu lệnh, tôi di chuyển các câu lệnh trong các vòng lặp để chúng kết thúc với một câu lệnh bên dưới chúng để tôi có thể loại bỏ dấu ngoặc nhọn. Tôi đã sử dụng chim nhạn để thay thế một số câu lệnh if. Tôi đã di chuyển công cụ xung quanh và kết thúc ở mức 946 byte . Nếu bạn không hiểu điều gì tôi đã làm, hãy hỏi tôi :)
Kritixi Lithos

Tại thời điểm này, vấn đề chính của tôi với việc chơi golf là cố gắng hiểu những gì tôi đã viết ở đó. Tôi cũng không biết những gì tối ưu hóa tôi có thể làm để chơi gôn trong javascript.
fnɛtɪk

Bạn có muốn thảo luận điều này trong một phòng chat riêng biệt?
Kritixi Lithos

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.