Compression palindrome

15

Défi

Écrivez un programme qui compresse et décompresse le texte ASCII sans perte. Il devrait être spécialisé pour bien fonctionner avec les palindromes, y compris les palindromes insensibles à la casse et à la ponctuation. La meilleure compression avec la plus petite source gagne.

Notation

total_bytes_saved / sqrt(program_size) - Le score le plus élevé gagne

total_bytes_savedest le nombre d'octets plus petit que les chaînes compressées sont que les originaux, total dans les cas de test ci-dessous. program_sizeest la taille en octets du code source des programmes de compression et de décompression. Le code partagé entre les deux ne doit être compté qu'une seule fois.

Par exemple, s'il y a 10 cas de test et qu'un programme de 100 octets a enregistré 5 octets sur 7 cas de test, 10 chacun sur 2 d'entre eux, mais que le dernier cas de test avait 2 octets de plus, la solution obtiendrait 5,3. ( (7 * 5 + 10 * 2 - 2) / sqrt(100) = 5.3)

Cas de test

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

Règles

  1. Des échappatoires standard s'appliquent.
  2. La compression doit fonctionner sur toutes les chaînes de texte ASCII imprimables (octets 32 à 126 inclus), pas seulement sur les palindromes. Cependant, il n'a pas besoin d'économiser de l'espace pour les entrées.
  3. La sortie peut être n'importe quelle séquence d'octets ou de caractères, quelle que soit sa mise en œuvre ou sa représentation interne (les chaînes, les listes et les tableaux sont tous des jeux équitables, par exemple). Si vous encodez en UTF-8, comptez les octets, pas les caractères. Les chaînes larges (par exemple UTF-16 ou UTF-32) ne sont pas autorisées à moins que les seuls points de code éventuellement utilisés soient compris entre 0 et 255.
  4. Les commandes internes de compression / décompression ne sont pas autorisées.

Pour notre plus grand plaisir, postez les chaînes compressées avec votre code source.

MISE À JOUR 1: La notation est passée de total_bytes_saved / program_sizeà total_bytes_saved / sqrt(program_size)afin de donner plus de poids à une meilleure compression et moins de poids au golf agressif. Ajustez vos scores en conséquence.

MISE À JOUR 2: corrigé wasitacaroraratisaw?pour êtrewasitacaroracatisaw?

Beefster
la source
2
Si la casse, la ponctuation et l'espacement sont supprimés de l'entrée, est-il garanti que les entrées seront des palindromes stricts? Edit: nevermind - Je vois que wasitacaroraratisaw?c'est un contre-exemple à cela
Digital Trauma
2
Quelle plage de caractères ASCII sommes-nous censés prendre en charge dans l'entrée? C'est ça [32-126]?
Arnauld
1
Ouais je ne pense pas que la 1000 *partie soit vraiment nécessaire, et non je ne pense pas que cela rendra le score plus "satisfaisant";)
Erik the Outgolfer
1
Pouvons-nous utiliser des fonctions intégrées de compression / décompression?
Lynn
4
Avec si peu d'entrées, il n'y a pas beaucoup de place pour faire quelque chose d'intelligent. Ce serait bien d'en avoir au moins quelques fois plus.
user1502040

Réponses:

16

JavaScript (ES6), 3.143 (81 octets enregistrés, programme de 664 octets)

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

Maintenant que je suis assez satisfait de ce programme (et du système de notation), je vais écrire une petite explication.

L'idée de base est de compresser l'entrée en une chaîne de bits, puis de compresser chaque ensemble de 8 bits en un octet. Aux fins d'explication, je vais simplement manipuler la chaîne de bits.

La chaîne de bits peut être séparée en plusieurs sections:

input  -> Taco Cat.
output -> 0101000000100011011111110100001100100011101011100000000

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

L'en-tête est un mappage très simple:

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

Les données de lettres sont également assez simples. Tout d'abord, toutes les non-lettres sont extraites de la chaîne et toutes les lettres sont converties en majuscules. Si la chaîne résultante est un palindrome, la moitié inversée est supprimée. Ensuite, ce mappage est appliqué:

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

Cette section se termine par 111. Après cela viennent les données de style, qui stockent les données majuscules / minuscules et les non-lettres. Cela fonctionne comme ceci:

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

Donc, en passant par l'exemple ci-dessus, nous avons

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

Lorsque la fin de la chaîne de bits est atteinte, tous les caractères restants des données de lettre sont ajoutés au résultat. Cela nous évite d'avoir à faire un dernier 000...001et nous permet de tronquer ces bits de la chaîne.

En passant par les cas de test:

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
ETHproductions
la source
Sensationnel. Je suis vraiment impressionné par cette approche. Je n'aurais jamais pensé faire un encodage aussi compact comme celui-ci. (J'ai pensé au cas de l'emballage de l'ASCII en 7 bits, mais n'économise pas beaucoup d'espace pour les palindromes) Je suis impressionné que vous ayez également réussi à économiser de l'espace Bob.
Beefster
4
Ceci est un excellent exemple des principes fondamentaux de l'ingénierie. Prendre une description du problème, réfléchir à différentes façons de le résoudre et faire des compromis entre les exigences (c'est-à-dire combien de bits à consacrer à différents styles), etc.
Robert Fraser
@Beefster Merci :-) Bobvraiment juste mis en place - 1 bit pour l'en-tête, 10 + 3 bits pour les deux lettres et 2 bits pour la lettre majuscule unique. Je ne pourrais pas le raccourcir si j'essayais de mon
mieux
1
@KevinCruijssen le problème est que la chose ajoutée est une chaîne, donc elle doit d'abord être convertie en nombre. Cette façon est un octet plus court que-0+9
ETHproductions
1
@ETHproductions Ah bien sûr (je n'ai pas remarqué que c'était une chaîne)! +9serait concaténé à la chaîne, tandis que -~8ferait +9arithmétiquement (puisque -ne fait rien pour les chaînes, donc il l'interprète comme un nombre). Dans ce cas, -~8c'est assez intelligent. :) Belle réponse btw, +1 de ma part! Très intelligent stockant toutes les informations dans des bits comme ça, même en économisant un octet Bob.
Kevin Cruijssen
2

Python 2: 2,765 (70 octets enregistrés, programme 641 octets)

J'ai un peu changé mon approche. Il fonctionne désormais bien sur les palindromes imparfaits. Aucune chaîne compressée ne sera plus longue que l'entrée. Des palindromes parfaits de longueur uniforme comprimeront toujours à 50% la taille d'origine.

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

Résultats

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

Et en bonus, il économise 6 octets sur mon palindrome incorrect que j'avais auparavant.

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

Explication

La décompression utilise une pile. Les points de code de 32 à 127 sont traités littéralement. Si un caractère est une lettre, une valeur est également insérée dans la pile. Les valeurs 128-192 sont utilisées pour les lettres retournées par la casse, de sorte que la lettre enfoncée dans le cas (en o^32raison de la disposition de l'ASCII) est poussée dans la pile et la lettre normale est ajoutée à la chaîne. Les valeurs 192-255 sont utilisées pour ajouter des lettres sans pousser vers la pile, donc cela est utilisé lorsque les lettres ne correspondent pas et pour la lettre du milieu dans les palindromes de longueur impaire. Les points de code 1 à 15 indiquent que la pile doit être sautée autant de fois. Les points de code 17 à 31 sont similaires, mais ils impriment d'abord un espace avant de sortir de la pile. Il y a aussi une instruction implicite "pop jusqu'à vide" à la fin d'une entrée.

Le compresseur fonctionne des deux extrémités et se plie en lettres correspondantes en tant que valeurs 1-31. Il saute les non-lettres. Lorsque les lettres correspondent, mais pas la casse, il ajoute 64 à la lettre de gauche et incrémente la lettre de droite. Cela lui permet d'économiser de l'espace IManAmRegalAGermanAmI. Au milieu ou lorsque les lettres ne correspondent pas, il est 128 des deux côtés. Je ne peux pas y ajouter car je dois éviter le cas spécial oùleft == right . Lors du pliage des marqueurs pop voisins sur le côté droit, je dois vérifier que le marqueur voisin ne débordera pas dans le point de code 16 car j'en ai besoin pour les espaces. (Ce n'est en fait un problème pour aucune des chaînes de scénario de test)

EDIT 1 : Plus de version non golfée.

Beefster
la source
1

Python3, 1,833 (25 octets enregistrés, programme de 186 octets)

Codage entropique à probabilité égale d'ordre 0 simple. Aucune optimisation spécifique au palindrome.

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]
user1502040
la source
0

Java 8, score: 1,355 (20 octets enregistrés / 218 (107 + 111) octets)

Fonction de compression (contient trois caractères ASCII non imprimables):

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

Fonction de décompression (contient deux caractères ASCII non imprimables):

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

Explication:

Essayez-le en ligne.

Compresse uniquement les palindromes parfaits.

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
Kevin Cruijssen
la source