Palindrom-Komprimierung


15

Herausforderung

Schreiben Sie ein Programm, das ASCII-Text verlustfrei komprimiert und dekomprimiert. Es sollte darauf spezialisiert sein, gut mit Palindromen zu arbeiten, einschließlich Palindromen, bei denen die Groß- und Kleinschreibung und die Zeichensetzung keine Rolle spielen. Die beste Komprimierung mit der kleinsten Quelle gewinnt.

Wertung

total_bytes_saved / sqrt(program_size) - Höchste Punktzahl gewinnt

total_bytes_savedgibt an, wie viele Bytes die komprimierten Zeichenfolgen kleiner sind als die Originale, insgesamt in den folgenden Testfällen. program_sizeist die Größe des Quellcodes sowohl des Komprimierungs- als auch des Dekomprimierungsprogramms in Byte. Der zwischen den beiden geteilte Code muss nur einmal gezählt werden.

Wenn beispielsweise 10 Testfälle vorhanden sind und ein 100-Byte-Programm 5 Bytes in 7 Testfällen gespeichert hat, jeweils 10 in 2 davon, aber der letzte Testfall 2 Bytes länger war, würde die Lösung 5,3 Punkte erzielen. ( (7 * 5 + 10 * 2 - 2) / sqrt(100) = 5.3)

Testfälle

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

Regeln

  1. Es gelten Standardlücken.
  2. Die Komprimierung muss für alle druckbaren ASCII-Textzeichenfolgen (Bytes 32-126, einschließlich) und nicht nur für Palindrome funktionieren. Tatsächlich muss jedoch kein Platz für Eingaben gespart werden.
  3. Die Ausgabe kann eine beliebige Folge von Bytes oder Zeichen sein, unabhängig von ihrer Implementierung oder internen Darstellung (beispielsweise sind alle Zeichenfolgen, Listen und Arrays ein faires Spiel). Wenn Sie nach UTF-8 codieren, zählen Sie die Bytes, nicht die Zeichen. Breite Zeichenfolgen (z. B. UTF-16 oder UTF-32) sind nur zulässig, wenn die einzigen Codepunkte, die möglicherweise verwendet werden, zwischen 0 und 255 liegen.
  4. Integrierte Komprimierungs- / Dekomprimierungsfunktionen sind nicht zulässig.

Veröffentlichen Sie die komprimierten Zeichenfolgen zu unserem eigenen Vergnügen mit Ihrem Quellcode.

UPDATE 1: Die Wertung wurde von total_bytes_saved / program_sizeauf total_bytes_saved / sqrt(program_size)geändert, um einer besseren Kompression mehr Gewicht und einem aggressiven Golfen weniger Gewicht zu verleihen. Passen Sie Ihre Ergebnisse entsprechend an.

UPDATE 2: behoben wasitacaroraratisaw?zu seinwasitacaroracatisaw?


2
Wenn Satzzeichen und Abstände von der Eingabe entfernt werden, ist dann gewährleistet, dass die Eingaben strenge Palindrome sind? Edit: nevermind - Ich sehe das wasitacaroraratisaw?ist ein Gegenbeispiel dazu
Digitales Trauma

2
Welchen Bereich von ASCII-Zeichen sollen wir bei der Eingabe unterstützen? Ist es [32-126]?
Arnauld

1
Ja, ich glaube nicht, dass der 1000 *Teil wirklich gebraucht wird, und nein, ich glaube nicht, dass sich die Partitur dadurch "befriedigender" anfühlt;)
Erik the Outgolfer

1
Können wir integrierte Komprimierungs- / Dekomprimierungsfunktionen verwenden?
Lynn

4
Bei so wenigen Eingaben gibt es nicht viel Spielraum für geschickte Aktionen. Es wäre schön, wenigstens ein paar Mal mehr zu haben.
user1502040

Antworten:


16

JavaScript (ES6), 3.143 (81 Byte gespeichert, 664 Byte Programm)

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

Nachdem ich mit diesem Programm (und dem Bewertungssystem) ziemlich zufrieden bin, schreibe ich eine kurze Erklärung.

Die Grundidee besteht darin, die Eingabe in eine Folge von Bits zu komprimieren und dann jeden Satz von 8 Bits in ein Byte zu komprimieren. Zu Erklärungszwecken werde ich nur die Bitfolge manipulieren.

Die Bitfolge kann in mehrere Abschnitte unterteilt werden:

input  -> Taco Cat.
output -> 0101000000100011011111110100001100100011101011100000000

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

Der Header ist ein sehr einfaches Mapping:

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

Briefdaten sind auch ziemlich einfach. Zunächst werden alle Nichtbuchstaben aus der Zeichenfolge extrahiert und alle Buchstaben in Großbuchstaben umgewandelt. Wenn die resultierende Zeichenfolge ein Palindrom ist, wird die umgekehrte Hälfte entfernt. Dann wird dieses Mapping angewendet:

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

Dieser Abschnitt endet mit 111. Danach folgen die Styling-Daten, in denen Groß- / Kleinschreibung und Nichtbuchstaben gespeichert sind. Das funktioniert so:

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

Gehen wir also das oben gezeigte Beispiel durch

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

Wenn das Ende der Bitfolge erreicht ist, werden alle verbleibenden Zeichen aus den Buchstabendaten an das Ergebnis angehängt. Dies erspart uns das letzte Mal, 000...001und wir können diese Bits von der Zeichenfolge abschneiden.

Durchlaufen der Testfälle:

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

Beeindruckend. Ich bin sehr beeindruckt von diesem Ansatz. Ich hätte nie gedacht, eine solche Codierung zu machen. (Ich dachte an den Fall des Packens von ASCII in 7 Bits, sparte aber nicht viel Platz für Palindrome.) Ich bin beeindruckt, dass Sie es auch geschafft haben, Platz zu sparen Bob.
Beefster

4
Dies ist ein großartiges Beispiel für die Grundlagen des Ingenieurwesens. Nehmen Sie eine Problembeschreibung, überlegen Sie sich verschiedene Lösungsansätze und machen Sie Kompromisse zwischen Anforderungen (dh wie viele Bits für verschiedene Stile verwendet werden müssen) usw.
Robert Fraser

@Beefster Danke :-), es hat sich Bobwirklich gerade so ergeben - 1 Bit für den Header, 10 + 3 Bit für die beiden Buchstaben und 2 Bit für den einzelnen Großbuchstaben. Könnte es nicht kürzer werden, wenn ich mein
Bestes geben würde

1
@ KevinCruijssen das Problem ist, dass das Ding, das hinzugefügt wird, ein String ist, also muss es zuerst in eine Zahl konvertiert werden. Dieser Weg ist ein Byte kürzer als-0+9
ETHproductions

1
@ETHproductions Ah natürlich (habe nicht bemerkt, dass es ein String war)! +9würde sich auf die Zeichenkette konzentrieren, während -~8dies +9arithmetisch -geschieht (da für Zeichenketten nichts unternommen wird, interpretiert es diese als Zahl). In diesem Fall -~8ist es ziemlich schlau. :) Schöne Antwort übrigens, +1 von mir! Sehr intelligentes Speichern aller Informationen in solchen Bits, sogar das Speichern eines Bytes Bob.
Kevin Cruijssen

2

Python 2: 2.765 (70 Byte gespeichert, 641 Byte Programm)

Ich habe meine Herangehensweise ein wenig geändert. Es funktioniert jetzt gut bei unvollkommenen Palindromen. Es gibt keine komprimierten Zeichenfolgen, die länger als die Eingabe sind. Perfekte Palindrome mit gerader Länge werden immer auf 50% der Originalgröße komprimiert.

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

Ergebnisse

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

Und als Bonus spart es 6 Bytes auf meinem falschen Palindrom, das ich vorher hatte.

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

Erläuterung

Dekomprimierung verwendet einen Stapel. Codepunkte von 32-127 werden wörtlich behandelt. Wenn ein Zeichen ein Buchstabe ist, wird auch ein Wert auf den Stapel geschrieben. Die Werte 128-192 werden für Groß- und Kleinschreibung verwendet, sodass der durch Groß- und Kleinschreibung gekippte Buchstabe ( o^32aufgrund der Anordnung von ASCII) auf den Stapel verschoben und der normale Buchstabe zur Zeichenfolge hinzugefügt wird. Die Werte 192-255 werden verwendet, um Buchstaben hinzuzufügen, ohne sie auf den Stapel zu schieben. Dies wird also verwendet, wenn die Buchstaben nicht übereinstimmen, und für den mittleren Buchstaben in Palindromen ungerader Länge. Die Codepunkte 1-15 geben an, dass der Stapel so oft gepoppt werden soll. Die Codepunkte 17-31 sind ähnlich, drucken jedoch zuerst ein Leerzeichen, bevor sie vom Stapel springen. Es gibt auch eine implizite Anweisung "Pop bis leer" am Ende einer Eingabe.

Der Kompressor arbeitet von beiden Seiten und faltet sich in übereinstimmenden Buchstaben als Werte 1-31. Es werden keine Buchstaben übersprungen. Wenn die Buchstaben übereinstimmen, die Groß- / Kleinschreibung jedoch nicht, wird dem linken Buchstaben 64 hinzugefügt und der rechte Buchstabe erhöht. Dies ermöglicht es, Platz zu sparen IManAmRegalAGermanAmI. In der Mitte oder wenn die Buchstaben nicht übereinstimmen, sind es 128 zu beiden Seiten. Ich kann dort nicht hinzufügen, weil ich den Sonderfall vermeiden muss, in dem left == right. Wenn ich benachbarte Pop-Marker auf der rechten Seite falte, muss ich sicherstellen, dass der benachbarte nicht in Codepoint 16 überläuft, da ich den für Leerzeichen benötige. (Dies ist eigentlich kein Problem für eine der Testfall-Zeichenfolgen.)

EDIT 1 : Keine ungolfed version mehr.


1

Python3, 1.833 (25 Byte gespeichert, 186 Byte Programm)

Nur einfache Entropiecodierung mit gleicher Wahrscheinlichkeit nullter Ordnung. Keine palindromspezifischen Optimierungen.

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, Score: 1.355 (20 Bytes gespeichert / 218 (107 + 111) Bytes)

Komprimierungsfunktion (enthält drei nicht druckbare ASCII-Zeichen):

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

Dekomprimierungsfunktion (enthält zwei nicht druckbare ASCII-Zeichen):

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

Erläuterung:

Probieren Sie es online aus.

Komprimiert nur perfekte Palindrome.

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.