Compressione del palindromo


15

Sfida

Scrivi un programma che comprime e decomprime il testo ASCII senza perdita di dati. Dovrebbe essere specializzato a lavorare bene con i palindromi, compresi i palindromi insensibili alle maiuscole e ai segni di punteggiatura. Vince la migliore compressione con la fonte più piccola.

punteggio

total_bytes_saved / sqrt(program_size) - Il punteggio più alto vince

total_bytes_savedè il numero di byte più piccolo delle stringhe compresse rispetto agli originali, totale tra i casi di test di seguito. program_sizeè la dimensione in byte del codice sorgente di entrambi i programmi di compressione e decompressione. Il codice condiviso tra i due deve essere contato una sola volta.

Ad esempio, se ci sono 10 casi di test e un programma da 100 byte ha salvato 5 byte su 7 casi di test, 10 ciascuno su 2 di essi, ma l'ultimo caso di test era più lungo di 2 byte, la soluzione avrebbe ottenuto un punteggio di 5,3. ( (7 * 5 + 10 * 2 - 2) / sqrt(100) = 5.3)

Casi test

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

Regole

  1. Si applicano scappatoie standard.
  2. La compressione deve funzionare su tutte le stringhe di testo ASCII stampabili (byte 32-126, inclusi), non solo sui palindromi. Tuttavia, non è necessario risparmiare spazio per alcun input.
  3. L'output può essere qualsiasi sequenza di byte o caratteri, indipendentemente dalla sua implementazione o rappresentazione interna (stringhe, elenchi e array sono tutti fair game, per esempio). Se si codifica in UTF-8, contare i byte, non i caratteri. Le stringhe larghe (ad esempio UTF-16 o UTF-32) non sono consentite a meno che gli unici punti di codice eventualmente utilizzati non siano compresi tra 0 e 255.
  4. I comandi incorporati di compressione / decompressione non sono consentiti.

Per il nostro divertimento, pubblica le stringhe compresse con il tuo codice sorgente.

AGGIORNAMENTO 1: Il punteggio è cambiato da total_bytes_saved / program_sizea total_bytes_saved / sqrt(program_size)per dare più peso a una migliore compressione e meno peso a golf aggressivo. Regola i tuoi punteggi di conseguenza.

AGGIORNAMENTO 2: fisso wasitacaroraratisaw?per esserewasitacaroracatisaw?


2
Se caso, punteggiatura e spaziatura vengono rimossi dall'input, è garantito che gli input saranno palindromi rigorosi? Edit: nevermind - Vedo che wasitacaroraratisaw?è un controesempio a quello
Digital Trauma

2
Quale gamma di caratteri ASCII dovremmo supportare nell'input? È vero [32-126]?
Arnauld,

1
Sì, non penso che la 1000 *parte sia davvero necessaria, e no, non credo che renderà il punteggio più "soddisfacente";)
Erik the Outgolfer

1
Possiamo usare la compressione / decompressione integrata?
Lynn,

4
Con così pochi input, non c'è molto spazio per fare qualcosa di intelligente. Sarebbe bello averne almeno alcune volte di più.
user1502040

Risposte:


16

JavaScript (ES6), 3.143 (81 byte salvati, programma 664 byte)

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

Ora che sono abbastanza soddisfatto di questo programma (e del sistema di punteggio), scriverò un po 'di spiegazione.

L'idea di base è comprimere l'input in una stringa di bit, quindi comprimere ogni set di 8 bit in un byte. Ai fini della spiegazione, mi limiterò a manipolare la stringa di bit.

La stringa di bit può essere separata in diverse sezioni:

input  -> Taco Cat.
output -> 0101000000100011011111110100001100100011101011100000000

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

L'intestazione è una mappatura molto semplice:

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

Anche i dati delle lettere sono abbastanza semplici. Innanzitutto, tutte le non lettere vengono estratte dalla stringa e tutte le lettere vengono convertite in maiuscolo. Se la stringa risultante è un palindromo, la metà invertita viene rimossa. Quindi viene applicato questo mapping:

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

Questa sezione è terminata con 111. Dopo di ciò arrivano i dati di stile, che memorizzano i dati maiuscoli / minuscoli e non lettere. Funziona così:

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

Quindi, passando attraverso l'esempio mostrato sopra, abbiamo

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 viene raggiunta la fine della stringa di bit, tutti i caratteri rimanenti dei dati della lettera vengono aggiunti al risultato. Questo ci evita di dover fare un ultimo 000...001e ci permette di troncare questi bit dalla stringa.

Passando attraverso i casi di 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

Wow. Sono davvero colpito da questo approccio. Non avrei mai pensato di creare una codifica così compatta. (Ho pensato al caso di impacchettare ASCII in 7 bit, ma non risparmia molto spazio per i palindromi) Sono impressionato dal fatto che tu sia riuscito a risparmiare spazio anche su Bob.
Beefster,

4
Questo è un grande esempio dei fondamenti dell'ingegneria. Prendere una descrizione del problema, pensare a diversi modi per risolverlo e fare dei compromessi tra i requisiti (cioè quanti bit dedicare a vari stili), ecc.
Robert Fraser

@Beefster Grazie :-) Bobè appena andato a posto - 1 bit per l'intestazione, 10 + 3 bit per le due lettere e 2 bit per la singola lettera maiuscola. Non potrei accorciarlo se provassi il mio più duro ...
massimo

1
@KevinCruijssen il problema è che la cosa che viene aggiunta è una stringa, quindi deve prima essere convertita in un numero. In questo modo è un byte più corto di-0+9
ETHproductions

1
@ETHproductions Ah ovviamente (non ho notato che era una stringa)! +9si concederebbe alla stringa, mentre -~8lo farebbe +9aritmeticamente (poiché -non fa nulla per le stringhe, quindi la interpreta come un numero). In quel caso -~8è piuttosto intelligente. :) Bella risposta a proposito, +1 da parte mia! Molto intelligente la memorizzazione di tutte le informazioni in bit come quello, anche salvando un byte Bob.
Kevin Cruijssen,

2

Python 2: 2.765 (70 byte salvati, programma 641 byte)

Ho cambiato un po 'il mio approccio. Ora funziona bene su palindromi imperfetti. Non ci sono stringhe compresse che saranno più lunghe dell'input. I palindromi perfetti di lunghezza uniforme comprimeranno sempre al 50% le dimensioni originali.

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

risultati

'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 come bonus, salva 6 byte sul mio palindromo errato che avevo prima.

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

Spiegazione

La decompressione utilizza uno stack. I punti di codice da 32-127 sono trattati alla lettera. Se un personaggio è una lettera, anche un valore viene inserito nella pila. I valori 128-192 vengono utilizzati per le lettere capovolte, quindi la lettera capovolta (a o^32causa della disposizione di ASCII) viene inserita nello stack e la lettera normale viene aggiunta alla stringa. I valori 192-255 sono usati per aggiungere lettere senza spingere in pila, quindi questo è usato quando le lettere non corrispondono e per la lettera di mezzo in palindromi di lunghezza dispari. I punti di codice 1-15 indicano che lo stack deve essere spuntato quel numero di volte. I punti di codice 17-31 sono simili, ma stampano uno spazio prima di saltar fuori dalla pila. C'è anche un'istruzione implicita "pop fino a vuoto" alla fine di un input.

Il compressore funziona da entrambe le estremità e si piega in lettere corrispondenti come valori 1-31. Salta le non lettere. Quando le lettere corrispondono ma il caso no, aggiunge 64 alla lettera sinistra e incrementa la lettera destra. Ciò consente di risparmiare spazio IManAmRegalAGermanAmI. Nel mezzo o quando le lettere non coincidono, sono 128 su entrambi i lati. Non posso aggiungere lì perché devo evitare il caso speciale in cui left == right. Quando piego gli indicatori pop vicini sul lato destro, devo controllare che quello vicino non trabocchi nel punto di codice 16 perché ne ho bisogno per gli spazi. (Questo non è in realtà un problema per nessuna delle stringhe del test case)

EDIT 1 : non più versione non golfata.


1

Python3, 1.833 (25 byte salvati, programma 186 byte)

Semplicemente codifica entropia di uguale probabilità a 0 ° ordine. Nessuna ottimizzazione specifica per palindromo.

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, punteggio: 1.355 (20 byte salvati / 218 (107 + 111) byte)

Funzione Compress (contiene tre caratteri ASCII non stampabili):

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

Funzione di decompressione (contiene due caratteri ASCII non stampabili):

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

Spiegazione:

Provalo online.

Comprime solo i palindromi perfetti.

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.