Написать токенайзер инцидентов

24

Задний план

Инцидент - довольно необычный язык программирования, в котором его список токенов не предопределен, а скорее выведен из входных данных. Таким образом, токенизация программы «Инцидент» может быть довольно сложной, особенно если вы хотите сделать это эффективно. Эта задача о том, чтобы сделать это самостоятельно.

Задание

Ваша программа получит строку в качестве ввода. Вот алгоритм, который использует Incident для токенизации:

  1. Идентифицируйте все строки, которые встречаются как подстрока ввода, точно тремя способами (то есть есть ровно три вхождения этой строки во входных данных).
  2. Откажитесь от любой из этих строк, которые являются подстрокой другой такой строки (например, для ввода abababединственной оставшейся строкой будет ab, а не aили b, потому что aи bобе являются подстроками ab).
  3. Откажитесь от любых строк, которые перекрываются внутри ввода. (Например, aaaaсодержит ровно три копии aa, но эти копии пересекаются со вторым и третьим символами, поэтому будут отброшены. Аналогично abababa, существует три копии abи три копии ba, но каждый со второго по шестой символы находятся на перекрытия a abи a ba, поэтому оба abи baбудут отброшены).
  4. Любые строки, которые остаются в этой точке, являются токенами, используемыми программой. Токенизируйте исходный вход в последовательность этих токенов (из-за сброса на предыдущем шаге будет только один способ сделать это). Любые символы на входе, которые не являются частью какого-либо токена, рассматриваются как комментарии и отбрасываются.

Ваша программа должна принять строку в качестве входных данных и вернуть соответствующий токенизацию строки (список токенов, каждый из которых выражен в виде строк) в качестве выходных данных. Кроме того, это должно быть сделано по крайней мере умеренно эффективно; в частности, программа должна запускаться за квадратичное время («O (n²)») или лучше. (Между прочим, почти наверняка возможно пойти быстрее, чем квадратичный, но это не , поэтому не стесняйтесь использовать самый краткий алгоритм, который вы можете найти, который вписывается в пределы сложности.)

Разъяснения

  • Хотя программы Incident теоретически могут содержать любой из 256 октетов, для этой задачи для вашей программы приемлемо обрабатывать только входные данные, сформированные из печатного ASCII (включая пробел), плюс перевод строки и табуляцию. (Все известные программы Incident ограничиваются этим подмножеством). Обратите внимание, что пробел / новая строка / табуляция не являются особыми и могут появляться в середине токенов; Инцидент рассматривает все 256 октетов как непрозрачные.
  • Определение «квадратичного времени»: «если размер входных данных удвоится, программа будет работать медленнее не более чем на константу плюс коэффициент 4», т. Е. Если t ( x ) - максимальное время, необходимое вашей программе для обработать ввод размера x , тогда должна быть некоторая константа k такая, что t (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

Состояние победы

Это , поэтому выигрывает самая короткая действующая (то есть правильная программа ввода-вывода и достаточно быстрая для выполнения) программа, измеренная в байтах.


источник
Для людей, которые могут видеть удаленные сообщения: сообщение Песочницы было здесь .
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)генерирует таблицу tдля шаблона pдля алгоритма Кнута-Морриса-Пратта. K(c)обрабатывает следующий символ cстроки поиска и обновлений m, длина наибольшего префикса pкоторых может быть найдена и заканчивается на последнем обработанном символе.

В первом forцикле для каждого индекса aв строке мы подсчитываем, сколько раз каждое возможное значение mвстречается при поиске во всей строке подстроки, начинающейся с a. Затем мы ищем наибольшее lтакое, чтобы длина lподстроки начиналась aровно 3 раза. Если он достаточно короткий, чтобы полностью содержаться в строке, найденной для предыдущего a, мы игнорируем ее. Если оно перекрывается, мы удаляем предыдущую строку из zмассива, запись которого токены будут сохранены. В противном случае его длина сохраняется в z.

Затем мы снова используем KMP для поиска в строке токенов, записанных в z. Если один из них найден в местоположении с записью 0 z, мы знаем, что этот токен был удален из-за перекрытия. Если токен не был удален, он печатается.

feersum
источник
1
Какова временная сложность этого? Должно быть O(n^2)или быстрее. И почему там !!на !!z[x-m]?
Yytsi
2
@TuukkaX Это точно O (n ^ 2). *Zэто длина следующего токена, который должен стать 0, если любое из других вхождений токена имеет значение 0 в своем индексе в массиве, или оставить то же значение иначе (в этом случае !!z[x-m]должно быть 1.
feersum
Хорошо. Но я до сих пор не понимаю, почему !!это так. !!xдолжно быть x, или это вызывает трюк, о котором я не знаю?
Yytsi
@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 за то, что вы играли в гольф 14 байтов.

(Не будет работать в 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``}

Попробуйте онлайн

Объяснение того, как это работает, и разглаженный код

Во-первых, программа генерирует массивы Кнута Морриса Пратта для каждой возможной подстроки;

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

Наконец, программа объединяет массив подстрок и выводит его.

fənɛtɪk
источник
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операторов, я перемещал операторы в циклах, чтобы они заканчивались одним оператором, чтобы я мог удалить фигурные скобки. Я использовал троичные, чтобы заменить некоторые операторы if. Я переместил вещи вокруг и закончил в 946 байтах . Если вы не понимаете, что я сделал, не стесняйтесь спрашивать меня :)
Kritixi Lithos
На данный момент моя главная проблема с гольфом - это попытка понять, что я там написал. Я также не знаю, какие оптимизации я могу сделать для игры в гольф в javascript.
f'n 18:tɪk
Вы хотите обсудить это в отдельном чате?
Kritixi Lithos