Сжатие палиндрома


15

Вызов

Напишите программу, которая сжимает и распаковывает текст ASCII без потерь. Он должен быть специализированным, чтобы хорошо работать с палиндромами, в том числе с нечувствительным к регистру и с пунктуацией палиндромами. Лучшее сжатие с наименьшим источником выигрывает.

счет

total_bytes_saved / sqrt(program_size) - выигрывает самый высокий балл

total_bytes_savedНа сколько байт меньше сжатых строк, чем оригиналов, всего в тестовых примерах ниже. program_sizeэто размер в байтах исходного кода программ сжатия и распаковки. Код, общий для двух, должен учитываться только один раз.

Например, если есть 10 тестовых случаев и 100-байтовая программа сохранила 5 байтов на 7 тестовых случаях, по 10 на 2 из них, но последний тестовый случай был на 2 байта длиннее, решение получило бы 5,3. ( (7 * 5 + 10 * 2 - 2) / sqrt(100) = 5.3)

Тестовые случаи

  • tacocat
  • toohottohoot
  • todderasesareddot
  • amanaplanacanalpanama
  • wasitacaroracatisaw?
  • Bob
  • IManAmRegalAGermanAmI
  • DogeeseseeGod
  • A Santa at NASA
  • Go hang a salami! I'm a lasagna hog.

правила

  1. Применяются стандартные лазейки.
  2. Сжатие должно работать со всеми печатаемыми текстовыми строками ASCII (байты 32-126 включительно), а не только с палиндромами. Однако на самом деле не нужно экономить место для каких-либо входов.
  3. Вывод может быть любой последовательностью байтов или символов, независимо от ее реализации или внутреннего представления (например, строки, списки и массивы - это честная игра). При кодировании в UTF-8 считайте байты, а не символы. Широкие строки (например, UTF-16 или UTF-32) не допускаются, если только возможные кодовые точки находятся в диапазоне от 0 до 255.
  4. Встроенные функции сжатия / распаковки не допускаются.

Для собственного удовольствия опубликуйте сжатые строки с вашим исходным кодом.

ОБНОВЛЕНИЕ 1: Оценка изменена с total_bytes_saved / program_sizeна total_bytes_saved / sqrt(program_size), чтобы придать больший вес лучшей компрессии и меньший вес агрессивным играм в гольф. Скорректируйте свои оценки соответственно.

UPDATE 2: фиксированный , wasitacaroraratisaw?чтобы бытьwasitacaroracatisaw?


2
Если регистр, пунктуация и интервал удалены со входа, гарантируется ли, что ввод будет строгим палиндромом? Редактировать: неважно - я вижу, wasitacaroraratisaw?это контрпример к этому
Цифровая травма

2
Какой диапазон символов ASCII мы должны поддерживать при вводе? Это [32-126]?
Арно

1
Да, я не думаю, что 1000 *роль действительно нужна, и нет, я не думаю, что это заставит счет чувствовать себя более «удовлетворительным»;)
Эрик Аутгольфер

1
Можем ли мы использовать встроенные модули сжатия / распаковки?
Линн

4
С таким небольшим количеством входов не так много возможностей сделать что-нибудь умное. Было бы неплохо иметь хотя бы в несколько раз больше.
user1502040

Ответы:


16

JavaScript (ES6), 3,143 (81 байт сохранен, программа 664 байт)

R='replace',S=String.fromCharCode,T=c=>c.charCodeAt(),U='toUpperCase',V='0000000',W=(a,b,c=2)=>a.toString(c).slice(b),X=x=>'0b'+x,Y=a=>[...a].reverse().join``,Z=/[^]/g
C=s=>S(...((Y(q=s[U]()[R](/[^A-Z]/g,m=''))==q?(q=q.slice(0,p=-~q.length/2),p%1&&10):11)+q[R](Z,x=>W(T(x),2))+111+s[R](Z,c=>/[a-z]/.test(c)?W("00",m,m=1):m+(/[A-Z]/.test(c,m='')?"01":W(c<'!'?2:T(c)+384)))+V).match(/(?!0+$).{8}/g).map(X))
D=s=>{s=s[R](Z,c=>W(256+T(c),1))+V;M=r=>(s=s[R](p=s.match(`^${r}|`)[0],''),p);for([,a]=M`1.|0`,t=u=i='';!M`111`;)t+=W(X(M`.{5}`)-~8,0,36);for(t+=W(Y(t),a?a/0:1);p;)u+=M`0(?=00)|00?1`?(c=t[i++])?+p[1]?c[U]():c:'':M`10`?' ':M`11`&&S(X(M`.{7}`));return u+W(t,i)}

Теперь, когда я вполне доволен этой программой (и системой подсчета очков), я напишу немного объяснения.

Основная идея состоит в том, чтобы сжать входные данные в строку битов, а затем сжать каждый набор из 8 битов в байт. В целях объяснения я просто манипулирую битовой строкой.

Строка битов может быть разделена на несколько разделов:

input  -> Taco Cat.
output -> 0101000000100011011111110100001100100011101011100000000

0      | 10100 00001 00011 01111 111 | 01 00001 10 01 0001 110101110
header | letter data                 | styling data

Заголовок очень простое отображение:

0  -> odd-length palindrome
10 -> even-length palindrome
11 -> non-palindrome

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

A -> 00001
B -> 00010
C -> 00011
D -> 00100
...
Z -> 11010

Этот раздел заканчивается с 111. После этого идут данные стилей, в которых хранятся данные верхнего и нижнего регистра и не буквы. Это работает так:

01 -> next letter as uppercase
0...01 (n 0s) -> next (n-1) letters as lowercase
10 -> space
11xxxxxxx -> character with code point 0bxxxxxxx

Итак, пройдя через пример, показанный выше, мы имеем

header: 0 -> palindrome
letter data: 10100 00001 00011 01111 111 -> taco
styling data:
  01        -> T
  00001     -> aco
  10        -> <space>
  01        -> C
  0001      -> at
  110101110 -> .

Когда достигнут конец строки битов, все остальные символы из буквенных данных добавляются к результату. Это избавляет нас от необходимости делать последнее 000...001и позволяет нам обрезать эти биты из строки.

Проходя тестовые случаи:

tacocat -> 3 bytes (-4)
    24 bits: 010100000010001101111111
toohottohoot -> 5 bytes (-7)
    35 bits: 10101000111101111010000111110100111
todderasesareddot -> 7 bytes (-10)
    49 bits: 0101000111100100001000010110010000011001100101111
amanaplanacanalpanama -> 8 bytes (-13)
    59 bits: 00000101101000010111000001100000110000001011100000100011111
wasitacaroracatisaw? -> 11 bytes (-9)
    84 bits: 010111000011001101001101000000100011000011001001111111000000000000000000001110111111
Bob -> 2 bytes (-1)
    16 bits: 0000100111111101
IManAmRegalAGermanAmI -> 13 bytes (-8)
    98 bits: 00100101101000010111000001011011001000101001110000101100111010100010100101000001010100000010100101
DogeeseseeGod -> 7 bytes (-6)
    54 bits: 000100011110011100101001011001100101111010000000000101
A Santa at NASA -> 8 bytes (-7)
    63 bits: 100000110011000010111010100000011110110010000011000011001010101
Go hang a salami! I'm a lasagna hog. -> 20 bytes (-16)
   154 bits: 1000111011110100000001011100011100001100110000101100000010110101001111010011000000110001100000000111010000110011101001110011000110000000001100000111010111

Вау. Я действительно впечатлен этим подходом. Я бы никогда не подумал сделать кодированную битку, подобную этой. (Я подумал о том, чтобы упаковать ASCII в 7 бит, но не сэкономил много места для палиндромов) Я впечатлен тем, что вам также удалось сэкономить место Bob.
Говядина

4
Это отличный пример основ машиностроения. Принимая описание проблемы, думая о различных способах ее решения и находя компромиссы между требованиями (то есть, сколько бит нужно выделить для различных стилей) и т. Д.
Роберт Фрейзер,

@Beefster Спасибо :-) Bobдействительно просто встал на свои места - 1 бит для заголовка, 10 + 3 бита для двух букв и 2 бита для одной заглавной буквы. Я не смог бы сделать это
немного

1
@KevinCruijssen проблема в том, что добавляемая вещь является строкой, поэтому сначала ее нужно преобразовать в число. Этот путь на байт короче-0+9
ETHproductions

1
@ETHproductions А, конечно (не заметил, что это была строка)! +9будет соответствовать строке, а -~8будет делать +9арифметически (поскольку -ничего не делает для строк, поэтому он интерпретирует его как число). В этом случае -~8довольно умный. :) Хороший ответ, кстати, +1 от меня! Очень умно хранить всю информацию в таких битах, даже сохраняя байт Bob.
Кевин Круйссен,

2

Python 2: 2.765 (70 байт сохранено, программа 641 байт)

Я немного изменил свой подход. Теперь он хорошо работает на несовершенных палиндромах. Там нет сжатых строк, которые будут длиннее, чем ввод. Идеальный палиндром ровной длины всегда сжимает до 50% от исходного размера.

A=lambda x:chr(x).isalpha()
def c(s):
 r=bytearray(s);q=len(r);L=0;R=q-1;v=lambda:R+1<q and r[R+1]<15
 while L<=R:
  while not A(r[L])and L<R:L+=1
  while not A(r[R])and R:
   if v()and r[R]==32:r[R]=16+r.pop(R+1)
   R-=1
  j=r[L];k=r[R]
  if A(j)*A(k):
   if L!=R and j&31==k&31:
    r[L]+=(j!=k)*64;r[R]=1
    if v():r[R]+=r.pop(R+1)
   else:r[L]|=128;r[R]|=128
  L+=1;R-=1
 while r[-1]<16:r.pop()
 return r
def d(s):
 r='';t=[]
 for o in s:
  if 15<o<32:r+=' ';o-=16
  while 0<o<16:r+=chr(t.pop());o-=1
  if o==0:continue
  if 127<o<192:o-=64;t+=[o^32]
  elif o>192:o-=128
  elif A(o):t+=[o]
  r+=chr(o)
 while t:r+=chr(t.pop())
 return r

Результаты

'tacocat' <==> 'tac\xef'
4/7 (3 bytes saved)
'toohottohoot' <==> 'toohot'
6/12 (6 bytes saved)
'todderasesareddot' <==> 'todderas\xe5'
9/17 (8 bytes saved)
'amanaplanacanalpanama' <==> 'amanaplana\xe3'
11/21 (10 bytes saved)
'wasitacaroracatisaw?' <==> 'wasita\xe3ar\xef\x09?'
12/20 (8 bytes saved)
'Bob' <==> '\x82\xef'
2/3 (1 bytes saved)
'IManAmRegalAGermanAmI' <==> 'I\x8d\xa1n\x81m\x92e\xa7\xa1\xec'
11/21 (10 bytes saved)
'Dogeeseseegod' <==> '\x84ogees\xe5'
7/13 (6 bytes saved)
'A Santa at NASA' <==> 'A S\xa1\xaeta\x12\x14'
9/15 (6 bytes saved)
"Go hang a salami! I'm a lasagna hog." <==> "\x87o hang a salam\xa9!\x11'\x01\x11\x17\x13."
24/36 (12 bytes saved)

И в качестве бонуса он экономит 6 байт на моем неверном палиндроме, который у меня был раньше.

'wasita\xe3ar\xef\x02\xf2\x06?' <==> 'wasitacaroraratisaw?'
6 bytes saved

объяснение

Декомпрессия использует стек. Кодовые точки 32-127 трактуются буквально. Если символ является буквой, значение также помещается в стек. Значения 128-192 используются для букв с перестановкой регистра, поэтому буква с учетом регистра ( o^32из-за того, как ASCII выложен) помещается в стек, а обычная буква добавляется в строку. Значения 192-255 используются для добавления букв без добавления в стек, поэтому это используется, когда буквы не совпадают, и для средней буквы в палиндромах нечетной длины. Кодовые точки 1-15 указывают, что стек должен выталкиваться такое количество раз. Кодовые точки 17-31 похожи, но они сначала печатают пробел перед тем, как выскочить из стека. Существует также неявная инструкция «pop to empty» в конце ввода.

Компрессор работает с обоих концов и складывает соответствующие буквы в значения 1-31. Он пропускает не-буквы. Когда буквы совпадают, но регистр не соответствует, он добавляет 64 к левой букве и увеличивает правую букву. Это позволяет сэкономить место на IManAmRegalAGermanAmI. В середине или когда буквы не совпадают, он поворачивается на 128 с обеих сторон. Я не могу добавить туда, потому что мне нужно избегать особого случая, когда left == right. При сворачивании соседних поп-маркеров на правой стороне я должен убедиться, что соседний не перетечет в кодовую точку 16, потому что это нужно для пробелов. (На самом деле это не проблема ни для одной из строк тестового примера)

РЕДАКТИРОВАТЬ 1 : Нет больше безголовой версии.


1

Python3, 1,833 (25 байт сохранено, программа 186 байт)

Простое энтропийное кодирование с равной вероятностью 0-го порядка. Нет палиндром-специфических оптимизаций.

def C(s):
    u=0
    for c in s:u=u*96+ord(c)-31
    return u.to_bytes((u.bit_length()+7)//8,'big')
def D(a):
    u,s=int.from_bytes(a,'big'),''
    while u:s,u=s+chr((u%96)+31),u//96
    return s[::-1]

0

Java 8, оценка: 1.355 (20 байтов сохранено / 218 (107 + 111) байтов)

Функция сжатия (содержит три непечатаемых символа ASCII):

s->{int l=s.length();return s.contains(new StringBuffer(s).reverse())?s.substring(l/2)+(l%2<1?"":""):s;}

Функция распаковки (содержит два непечатаемых символа ASCII):

s->{return s.contains("")?new StringBuffer((s=s.replaceAll("","")).substring(s.length()&1^1)).reverse()+s:s;}

Объяснение:

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

Сжимает только идеальные палиндромы.

s->{                      // Method with String as both parameter and return-type
  int l=s.length();       //  Get the length of the input
  return s.contains(new StringBuffer(s).reverse())?
                          //  If the input is a palindrome:
    s.substring(l/2)      //   Only return the second halve of the String
    +(l%2<1?"":"")        //   + either one (if even) or two (if odd) unprintables 
   :                      //  Else:
    s;}                   //   Simply return the input again

s->{                      // Method with String as both parameter and return-type
  return s.contains("")?  //  If the input contains an unprintable:
    new StringBuffer((s=s.replaceAll("",""))
                          //   Remove the unprintables
                     .substring(s.length()&1^1))
                          //   And take either the full string (if even),
                          //   or minus the first character (if odd)
    .reverse()            //    And reverse that part
    +s                    //   And append the rest of the input (minus the unprintables)
   :                      //  Else:
    s;}                   //   Simply return the input again
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.