Compressão Palíndromo


15

Desafio

Escreva um programa que comprima e descompacte o texto ASCII sem perdas. Deve ser especializado para funcionar bem com palíndromos, incluindo palíndromos que não diferenciam maiúsculas de minúsculas e que não pontuam pontuação. A melhor compactação com a menor fonte ganha.

Pontuação

total_bytes_saved / sqrt(program_size) - Maior pontuação ganha

total_bytes_savedé quantos bytes menores as seqüências compactadas são do que os originais, total nos casos de teste abaixo. program_sizeé o tamanho em bytes do código-fonte dos programas de compactação e descompactação. O código compartilhado entre os dois precisa ser contado apenas uma vez.

Por exemplo, se houver 10 casos de teste e um programa de 100 bytes salvou 5 bytes em 7 casos de teste, 10 cada em 2 deles, mas o último caso de teste tiver 2 bytes mais, a solução terá 5,3. ( (7 * 5 + 10 * 2 - 2) / sqrt(100) = 5.3)

Casos de teste

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

Regras

  1. Aplicam-se brechas padrão.
  2. A compactação deve funcionar em todas as seqüências de texto imprimíveis ASCII (bytes 32-126, inclusive), não apenas nos palíndromos. Na verdade, ele não precisa economizar espaço para nenhuma entrada.
  3. A saída pode ser qualquer sequência de bytes ou caracteres, independentemente de sua implementação ou representação interna (seqüências de caracteres, listas e matrizes são jogos justos, por exemplo). Se estiver codificando para UTF-8, conte bytes, não caracteres. Cadeias de caracteres largas (por exemplo, UTF-16 ou UTF-32) não são permitidas, a menos que os únicos pontos de código possivelmente usados ​​estejam entre 0 e 255.
  4. Os componentes de compactação / descompactação não são permitidos.

Para nosso próprio prazer, publique as strings compactadas com seu código-fonte.

ATUALIZAÇÃO 1: A pontuação mudou de total_bytes_saved / program_sizepara total_bytes_saved / sqrt(program_size)para dar mais peso à melhor compressão e menos peso ao golfe agressivo. Ajuste sua pontuação de acordo.

ATUALIZAÇÃO 2: corrigida wasitacaroraratisaw?para serwasitacaroracatisaw?


2
Se maiúsculas e minúsculas, pontuação e espaçamento forem removidos da entrada, é garantido que as entradas serão palíndromos estritos? Edit: nevermind - Eu vejo o wasitacaroraratisaw?é um contraexemplo disso
Digital Trauma

2
Qual intervalo de caracteres ASCII devemos suportar na entrada? É isso [32-126]?
Arnauld

1
Sim, eu não acho que a 1000 *parte é realmente necessária, e não, eu não acho que isso fará com que a pontuação pareça mais "satisfatória";)
Erik the Outgolfer

1
Podemos usar os componentes de compactação / descompactação?
Lynn

4
Com tão poucas entradas, não há muito espaço para fazer algo inteligente. Seria bom ter pelo menos algumas vezes mais.
precisa saber é o seguinte

Respostas:


16

JavaScript (ES6), 3.143 (81 bytes salvos, programa de 664 bytes)

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

Agora que estou bastante satisfeito com este programa (e o sistema de pontuação), vou escrever uma explicação.

A idéia básica é compactar a entrada em uma sequência de bits e, em seguida, compactar cada conjunto de 8 bits em um byte. Para fins de explicação, manipularei a string de bits.

A cadeia de bits pode ser separada em várias seções:

input  -> Taco Cat.
output -> 0101000000100011011111110100001100100011101011100000000

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

O cabeçalho é um mapeamento muito simples:

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

Os dados das cartas também são bastante simples. Primeiro, todas as não letras são extraídas da sequência e todas as letras são convertidas em maiúsculas. Se a sequência resultante for um palíndromo, a metade invertida será removida. Então esse mapeamento é aplicado:

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

Esta seção termina com 111. Depois disso, vêm os dados de estilo, que armazenam dados em maiúsculas / minúsculas e sem letras. Isso funciona assim:

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

Então, seguindo o exemplo mostrado acima, temos

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

Quando o final da sequência de bits é atingido, todos os caracteres restantes dos dados da letra são anexados ao resultado. Isso nos impede de ter que fazer uma última000...001 e nos permite truncar esses bits da string.

Passando pelos casos de teste:

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

Uau. Estou realmente impressionado com essa abordagem. Eu nunca teria pensado em fazer uma codificação cheia de bits como esta. (Eu pensei no caso de empacotar ASCII em 7 bits, mas não economiza muito espaço para palíndromos). Estou impressionado que você conseguiu economizar espaço Bobtambém.
Beefster

4
Este é um ótimo exemplo dos fundamentos da engenharia. Tomando uma descrição do problema, pensar em maneiras diferentes de resolver isso, e fazendo trocas entre os requisitos (ou seja, quantos bits para se dedicar a vários estilos), etc
Robert Fraser

@Beefster Obrigado :-) Bobrealmente se encaixou - 1 bit para o cabeçalho, 10 + 3 bits para as duas letras e 2 bits para a única letra maiúscula. Não poderia ficar mais curto se eu tentasse o meu melhor ...
ETHproductions

1
@KevinCruijssen o problema é que a coisa que está sendo adicionada é uma string, então ela deve ser convertida em um número primeiro. Desta forma, é um byte menor que-0+9
ETHproductions

1
@ETHproductions Ah, é claro (não percebeu que era uma corda)! +9concatenaria com a string, enquanto -~8faria +9aritmeticamente (já -que não faz nada por strings, por isso a interpreta como um número). Nesse caso, -~8é bastante inteligente. :) Boa resposta btw, +1 de mim! Muito inteligente, armazenando todas as informações em bits assim, economizando até um byte Bob.
Kevin Cruijssen

2

Python 2: 2.765 (70 bytes salvos, programa de 641 bytes)

Mudei de abordagem um pouco. Agora funciona bem em palíndromos imperfeitos. Não há seqüências de caracteres compactadas que serão mais longas que a entrada. Palíndromos perfeitos de comprimento uniforme sempre comprimirão 50% do tamanho original.

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

Resultados

'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)

E como bônus, ele salva 6 bytes no meu palíndromo incorreto que eu tinha antes.

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

Explicação

A descompressão usa uma pilha. Os pontos de código de 32 a 127 são tratados literalmente. Se um caractere é uma letra, um valor também é enviado para a pilha. Os valores 128-192 são usados ​​para letras com maiúsculas e minúsculas; portanto, a letra com maiúsculas e minúsculas (o^32 por causa de como o ASCII é organizado) é empurrada para a pilha e a letra normal é adicionada à sequência. Os valores 192-255 são usados ​​para adicionar letras sem empurrar para a pilha; portanto, isso é usado quando as letras não correspondem e para a letra do meio em palíndromos de comprimento ímpar. Os pontos de código 1 a 15 indicam que a pilha deve ser exibida esse número de vezes. Os pontos de código 17 a 31 são semelhantes, mas primeiro imprimem um espaço antes de sair da pilha. Há também uma instrução implícita "pop até vazia" no final de uma entrada.

O compressor trabalha nas extremidades e dobras em letras correspondentes como valores de 1 a 31. Ele pula sobre não letras. Quando as letras correspondem, mas o caso não corresponde, adiciona 64 à letra esquerda e incrementa a letra direita. Isso permite economizar espaço IManAmRegalAGermanAmI. No meio ou quando as letras não coincidem, ele representa 128 para os dois lados. Não posso adicionar lá porque preciso evitar o caso especial em queleft == right . Ao dobrar marcadores pop vizinhos no lado direito, tenho que verificar se o vizinho não transbordará para o codepoint 16, porque preciso disso para espaços. (Na verdade, isso não é um problema para nenhuma das sequências de casos de teste)

EDIT 1 : Não há mais versão não destruída.


1

Python3, 1.833 (25 bytes salvos, programa de 186 bytes)

Apenas codificação de entropia simples com igual probabilidade de ordem 0. Não há otimizações específicas do palíndromo.

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, pontuação: 1.355 (20 bytes salvos / 218 (107 + 111) bytes)

Função Compactar (contém três caracteres ASCII não imprimíveis):

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

Função descomprimir (contém dois caracteres ASCII não imprimíveis):

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

Explicação:

Experimente online.

Comprime apenas palíndromos perfeitos.

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.