Kto chce zostać zwycięzcą złożoności Kołmogorowa?

22

Twoim zadaniem dzisiaj jest wynalezienie kompresora tekstu.

Zadanie

Napisz dwie funkcje:

  • Paker jest funkcją, która przyjmuje ciąg znaków ASCII (U, U + 0000 + 007F) i wysyła unikodowego (U + 0000 U + 10FFFF) zawierający najmniejszą liczbę znaków możliwe.

  • Unpacker to funkcja, która akceptuje zakodowany ciąg Unicode i wyprowadza dokładnie oryginalny ciąg ASCII.

Wkład

Jedynym autoryzowanym wejściem jest ciąg ASCII (dla pakującego) i spakowany ciąg Unicode (dla rozpakowacza). Bez wkładu użytkownika, bez połączenia z Internetem, bez użycia systemu plików.

Twoje funkcje mogą mieć dostęp do tej listy angielskich słów . Możesz użyć tej listy jako lokalnego pliku txt lub skopiować jej zawartość do kodu źródłowego jako ciąg lub tablicę ciągów .

W swoich funkcjach nie można zakodować na stałe poniższych fragmentów.

Wydajność

Jedynym autoryzowanym wyjściem dla obu funkcji jest ciąg.

Dane wyjściowe rozpakowywacza muszą zawierać dokładnie takie same znaki, jak dane wejściowe pakowacza.

Twoje dane wejściowe i wyjściowe mogą korzystać z dowolnego kodowania znaków obsługującego wszystkie znaki Unicode (UTF-8/16/32, GB18030, ...), ponieważ wynik będzie zależał tylko od liczby znaków Unicode na wyjściu. Proszę jednak dokładnie określić, jakiego kodowania używasz.

Aby policzyć liczbę znaków Unicode w danych wyjściowych, możesz użyć tego narzędzia: http://mothereff.in/byte-counter

Punktacja

Wpis musi umożliwiać pakowanie i rozpakowywanie 10 następujących fragmentów tekstu (które wziąłem na tym forum).

Twój wynik będzie sumą rozmiarów 10 spakowanych ciągów znaków (w znakach Unicode) + rozmiaru twoich dwóch funkcji (również w znakach Unicode)

Nie licz rozmiaru słownika, jeśli go używasz.

Podaj we wpisach „wynik” każdego fragmentu i jego zapakowanej wersji.

Najniższy wynik wygrywa.

Dane

Oto fragmenty do zakodowania w celu obliczenia wyniku:

Tekst piosenki 1: Rick Roll (1870b): Nie jesteśmy obcy w kodowaniu golfa, znasz zasady i ja też

Nie jesteśmy obcymi ludźmi do kochania
Znasz zasady i ja też
Myślę o pełnym zaangażowaniu
Nie dostałbyś tego od żadnego innego faceta
Chcę ci tylko powiedzieć, jak się czuję
Muszę cię zrozumieć

Nigdy się nie poddawaj
Nigdy cię nie zawiodę
Nigdy nie będę biegał i cię opuszczał
Nigdy nie sprawię, że będziesz płakać
Nigdy się nie pożegnam
Nigdy nie kłamię i nie skrzywdzę cię

Znamy się od tak dawna
Twoje serce boli, ale
Jesteś zbyt nieśmiały, żeby to powiedzieć
Wewnątrz oboje wiemy, co się dzieje
Znamy tę grę i zagramy w nią
A jeśli zapytasz mnie, jak się czuję
Nie mów mi, że jesteś zbyt ślepy, żeby to zobaczyć

Nigdy się nie poddawaj
Nigdy cię nie zawiodę
Nigdy nie będę biegał i cię opuszczał
Nigdy nie sprawię, że będziesz płakać
Nigdy się nie pożegnam
Nigdy nie kłamię i nie skrzywdzę cię

Nigdy się nie poddawaj
Nigdy cię nie zawiodę
Nigdy nie będę biegał i cię opuszczał
Nigdy nie sprawię, że będziesz płakać
Nigdy się nie pożegnam
Nigdy nie kłamię i nie skrzywdzę cię

(Ooh, poddaj się)
(Ooh, poddaj się)
(Ooh)
Nigdy nie dam, nigdy nie dam
(Poddaj się)
(Ooh)
Nigdy nie dam, nigdy nie dam
(Poddaj się)

Znamy się od tak dawna
Twoje serce boli, ale
Jesteś zbyt nieśmiały, żeby to powiedzieć
Wewnątrz oboje wiemy, co się dzieje
Znamy tę grę i zagramy w nią

Chcę ci tylko powiedzieć, jak się czuję
Muszę cię zrozumieć

Nigdy się nie poddawaj
Nigdy cię nie zawiodę
Nigdy nie będę biegał i cię opuszczał
Nigdy nie sprawię, że będziesz płakać
Nigdy się nie pożegnam
Nigdy nie kłamię i nie skrzywdzę cię

Nigdy się nie poddawaj
Nigdy cię nie zawiodę
Nigdy nie będę biegał i cię opuszczał
Nigdy nie sprawię, że będziesz płakać
Nigdy się nie pożegnam
Nigdy nie kłamię i nie skrzywdzę cię

Nigdy się nie poddawaj
Nigdy cię nie zawiodę
Nigdy nie będę biegał i cię opuszczał
Nigdy nie sprawię, że będziesz płakać
Nigdy się nie pożegnam
Nigdy nie kłamię i nie skrzywdzę cię

2: The Golfer (412b): Gra w golfa ASCII-art

      „\. . |> 18 >>
        \. „. |
       O >>. „o |
        \. |
        / \. |
       / /. ” |
 jgs ↑ ^^^^^^^^^^

3: liczba diamentów (233b): wydrukuj ten diament

        1
       121
      12321
     1234321
    123454321
   12345654321
  1234567654321
 123456787654321
12345678987654321
 123456787654321
  1234567654321
   12345654321
    123454321
     1234321
      12321
       121
        1

4: alfabet cztery razy (107b): Wydrukuj alfabet cztery razy

ABCDEFGHIJKLMNOPQRSTU VWXYZ
qwertyuiopasdfghjklzxcvbnm
pyfgcrlaoeuidhtnsqjkxbmwvz
zyxwvutsrqponmlkjihgfedcba

5: Tekst Old McDonald's (203b): Funkcja Old MacDonald

Stary MacDonald miał farmę, EIEIO,
A na tej farmie miał krowę, EIEIO,
Z muczeniem tutaj i muczeniem tutaj,
Tutaj muczenie, muczenie, wszędzie muczenie muczenie,
Stary MacDonald miał farmę, EIEIO!

6: Rock around the clock lyrics (144b): Rock Around the Clock

1, 2, 3 godzina, 4 godzina rock,
5, 6, 7 godzina, 8 godzina rock,
9, 10, 11 godzina, 12 godzina rock,
Będziemy dziś kołysać się przez całą dobę.

7: Hello World (296b): Powiedz światu „Hello” w sztuce ASCII

 _ _ _ _ _ _ _ _
| | | | ___ | | | ___ __ _____ _ __ | | __ | | |
| | _ | | / _ \ | | / _ \ \ \ / \ / / _ \ | „__ | | / _` | |
| _ | __ / | | (_) | \ VV / (_) | | | | (_ | | _ |
| _ | | _ | \ ___ | _ | _ | \ ___ () \ _ / \ _ / \ ___ / | _ | | _ | \ __, _ (_)
                    | /

8: Irlandzkie błogosławieństwo (210b): Stare irlandzkie błogosławieństwo

Niech droga wzniesie się, by cię poznać
Niech wiatr będzie zawsze na twoich plecach
Niech słońce świeci ciepło na twoją twarz
Deszcze padają na twoje pola
I dopóki się nie spotkamy
Niech Bóg trzyma cię w zagłębieniu swojej ręki

9: Tekst piosenki old lady (1208b): There Was an Old Lady

Była starsza pani, która połknęła muchę.  
Nie wiem, dlaczego połknęła tę muchę,  
Może umrze.

Była starsza pani, która połknęła pająka,  
Kręciło się to w niej i igrało.  
Połknęła pająka, aby złapać muchę,  
Nie wiem, dlaczego połknęła tę muchę,  
Może umrze.

Była starsza dama, która połknęła ptaka,  
Jak absurdalne jest połykanie ptaka.  
Połknęła ptaka, aby złapać pająka,  
Połknęła pająka, aby złapać muchę,  
Nie wiem, dlaczego połknęła tę muchę,  
Może umrze.

Była starsza pani, która połknęła kota,  
Wyobraź sobie, że połkniesz kota.  
Połknęła kota, aby złapać ptaka,  
Połknęła ptaka, aby złapać pająka,  
Połknęła pająka, aby złapać muchę,  
Nie wiem, dlaczego połknęła tę muchę,  
Może umrze.

Była starsza pani, która połknęła psa,  
Co za świnia do połknięcia psa.  
Połknęła psa, aby złapać kota,  
Połknęła kota, aby złapać ptaka,  
Połknęła ptaka, aby złapać pająka,  
Połknęła pająka, aby złapać muchę,  
Nie wiem, dlaczego połknęła tę muchę,  
Może umrze.

Była starsza pani, która połknęła konia,  
Oczywiście umarła.

10: adres gettysburg (1452b): Jak losowy jest adres Gettysburg

Cztery dziesiątki i siedem lat temu nasi ojcowie zrodzili na tym kontynencie nowy naród, poczęty w wolności i poświęcony twierdzeniu, że wszyscy ludzie są równi. Teraz jesteśmy zaangażowani w wielką wojnę domową, sprawdzając, czy ten naród lub jakikolwiek naród tak pomyślany i oddany może przetrwać długo. Spotkaliśmy się na wielkim polu bitwy tej wojny. Przybyliśmy, aby poświęcić część tego pola, jako ostateczne miejsce spoczynku dla tych, którzy tutaj oddali życie, aby ten naród mógł żyć. Zasadniczo i słusznie powinniśmy to zrobić. Ale w szerszym znaczeniu nie możemy poświęcić, nie możemy poświęcić, nie możemy poświęcić tej ziemi. Odważni ludzie, żywi i martwi, którzy tu walczyli, poświęcili ją, znacznie ponad naszą słabą moc dodawania lub niszczenia. Świat niewiele zauważy, ani długo nie zapamięta tego, co tu mówimy, ale nigdy nie zapomni, co tutaj zrobili. To my, żywi, powinniśmy poświęcić się tutaj niedokończonemu dziełu, które walczyli tutaj tak szlachetnie. Raczej jesteśmy tutaj, aby poświęcić się wielkiemu zadaniu, które przed nami stoi - że od tych czcigodnych umarłych poświęcamy się temu celowi, dla którego oddali ostatnią miarę oddania - że tutaj bardzo postanawiamy, że ci umarli nie będą umarli na próżno - aby naród ten, pod Bogiem, narodził się na nowo w wolności - i aby lud rządził ludem, bo lud nie zginie z ziemi.

Łącznie (nieskompresowane): 6135 znaków / bajtów.

Baw się dobrze!

Xem
źródło
7
To nie jest wyzwanie wymyślić język, to wyzwanie, aby coś skompresować.
Justin
2
Myślę, że nie uwzględnienie wielkości kompilatora / executora (kompresora / dekompresora) w partyturze powoduje, że to wyzwanie jest trochę otwarte. W pewnym momencie granica między słownikiem a kodowaniem stanie się bardzo cienka.
Dennis
2
Cholera, a tutaj już pisałem private static final String RICK_ROLL_RETURN = "We're no strangers to love...
teoria grafów
1
Nie sądzę, żebyś odniósł się do obserwacji Dennisa.
Peter Taylor
1
@xem Myślę, że post można poprawić, organizując informacje w sekcje, takie jak #Task, #Input, #Output, #Scoring. Myślę również, że rozmiar kodu źródłowego kompresora i dekompresora powinien być uwzględniony w wyniku. To nic nie boli, ale rozwiązuje problem, na który zwrócił uwagę Dennis.
Rainbolt

Odpowiedzi:

6

Haskell - 5322 punkty

Bajty kodu: 686

Oryginalny rozmiar : 6147 = 1871+415+234+108+204+145+297+211+1209+1453

Zakodowany rozmiar: 4636 = 1396+233+163+92+153+115+197+164+979+1144

Ocena: 686+ 4636

Kompresja liczby znaków: ~25%

Kod

Pomijając optymalizacje, przechowuje wartości pomiędzy 0i7f w znakach Unicode jako ich główne czynniki.

Nie obniża liczby bajtów zakodowanego wyjścia, tylko obniża liczbę znaków Unicode. Na przykład, test nr 4 zawiera 108znaki i wyjście kodowane 92. Ich rozmiary są jednak odpowiednie 108i 364bajtów.

import Data.Bits
import Data.List
import Data.Numbers.Primes
import qualified Data.Text as T
a=nub$concat$map(primeFactors)[0..127]
d(a:r)c=let s=shift c 5in if s<=0x10ffffthen d r(s+a)else c:d r a
d[]c=[c]
f(a:r)=let o=a.&.0x1fin(if o/=a then f((shiftR a 5):r)else f r)++[o]
f[]=[]
g=T.pack.map(toEnum).(\(a:r)->d r a).concatMap j.map(map(\x->head$elemIndices x a)).map(primeFactors.fromEnum).T.unpack
h=T.pack.map(toEnum.product.map((!!)a)).i.f.reverse.map(fromEnum).T.unpack
i(a:r)=let z=a`clearBit`4;x=if a`testBit`4then(take z$repeat$head r,tail r)else splitAt z r in[fst x]++i(snd x)
i[]=[]
j x@(a:_)=let l=length x in if(take l(repeat a))==x then[l`setBit`4,a]else[l]++x
j[]=[0]

Jak to działa

  • Kodowanie

    1. Każdy znak jest konwertowany na jego numeryczny odpowiednik, nazwijmy ten numer n.
    2. nnastępnie przeprowadza się wykaz jego głównych czynników ps.
      • Dogodnie zdarza się, że liczby od 0 do 127 mają 32 wspólne czynniki pierwsze, z wyłączeniem 1. Oznacza to, że one, czynniki, mogą być przechowywane jako indeksy na zaledwie 5 bitach.
      • 1 jest szczególnym przypadkiem i jest reprezentowany przez pustą listę.
    3. Dzięki pskodowaniu można teraz rozpocząć.
      1. Każda liczba psjest konwertowana na swój indeks na liście 32 unikalnych czynników (w powyższym kodzie ta lista jest oznaczona jako a).
      2. (Należy pamiętać, że w tym momencie mamy do czynienia z listą indeksów czynników głównych). Aby przejść do następnego kroku, psnależy spłaszczyć. Aby zachować integralność danych, każda lista jest konwertowana na inną listę dwóch części
        1. Pierwszy element przechowuje swoją długość i jeśli składa się z tego samego czynnika.
          • Na liście jest maksymalnie 6 czynników pierwszych, informacja ta jest przechowywana w skrajnych prawych 3 bitach. Piąty bit jest używany jako flaga do wskazania, czy lista składa się z jednego czynnika.
        2. Pozostałe elementy to same indeksy lub pojedynczy indeks, jeśli na liście występują mniej niż dwa różne czynniki.
      3. Wykazy te są następnie łączone w jedną listę spłaszczony fs.
    4. Elementy fsmożna następnie upakować w znaki Unicode przy użyciu przesunięcia bitowego.
  • Rozszyfrowanie

    • Wykonaj kroki kodowania w odwrotnej kolejności.
    • Jeśli zastanawiasz się, jak to 1pasuje, chciałbym ci to przypomnieć product [] == 1.

Testy

Używanie tego interfejsu do testowania byłoby bolesne, więc skorzystałem z tej funkcji, aby przedstawić poniższe wyniki.

edTest f = do
    t <- readFile f
    let txt = T.pack t
        enc = g txt
        dec = h enc
        tst = txt == dec
    putStrLn $ (show $ T.length txt) ++ "," ++ (show $ T.length enc) ++ "," ++ (show $ T.length dec)++","++(show tst)
    putStrLn $ if not tst then T.unpack txt ++ "\n---NEXT---\n" ++ T.unpack dec else ""


λ> edTest "1"
1871,1396,1871,True

λ> edTest "2"
412,233,412,True

λ> edTest "3"
234,163,234,True

λ> edTest "4"
108,92,108,True

λ> edTest "5"
204,153,204,True

λ> edTest "6"
145,115,145,True

λ> edTest "7"
297,197,297,True

λ> edTest "8"
211,164,211,True

λ> edTest "9"
1209,979,1209,True

λ> edTest "10"
1453,1144,1453,True

Próba

Wyjście funkcji kodowania gdla testu nr 4 jest takie
"\99429\582753\135266\70785\35953\855074\247652\1082563\68738\49724\164898\68157\99429\67973\1082404\587873\73795\298017\330818\198705\69861\1082435\595009\607426\36414\69873\855074\265249\346275\67779\68738\77985\1082513\821353\132131\101410\247652\1082562\49724\164898\67649\594977\34915\67746\50273\135265\103997\563265\103457\1086021\99399\584802\70753\73889\34882\582722\411459\67779\68740\1084516\1082563\1091681\103491\313282\49724\164897\68705\135741\69858\50241\607426\35905\608421\1082435\69858\50274\71777\43075\298018\280517\1082404\67971\36017\955425\67665\919600\100452\132129\214883\35057\856097\101474\70753\135737"
lub jeśli jesteś biegły w bełkocie, to
𘑥򎑡𡁢𑒁豱󐰢𼝤􈓃𐲂숼𨐢𐨽𘑥𐦅􈐤򏡡𒁃񈰡񐱂𰠱𑃥􈑃򑑁򔓂踾𑃱󐰢񀰡񔢣𐣃𐲂𓂡􈒑󈡩𠐣𘰢𼝤􈓂숼𨐢𐡁򑐡衣𐢢쑡𡁡𙘽򉡁𙐡􉉅𘑇򎱢𑑡𒂡衂򎑂񤝃𐣃𐲄􈱤􈓃􊡡𙑃񌟂숼𨐡𐱡𡈽𑃢쑁򔓂豁򔢥􈑃𑃢쑢𑡡ꡃ񈰢񄟅􈐤𐦃貱󩐡𐡑󠠰𘡤𠐡𴝣裱󑀡𘱢𑑡𡈹

Dodatkowe uwagi

  • Korzystanie z http://mothereff.in/byte-counter , list katalogów iedTest rozmiar testów są spójne, ale nadal różnią się od wskazanego rozmiaru w pytaniu.
  • Test nr 10 zawiera parę kresek (EM ), że zastąpione -, ponieważ są one na zewnątrz 0- 7fzakres.
  • Dalszą kompresję można uzyskać za pomocą pozostałego czwartego bitu podczas spłaszczania, na przykład, 00przypadek podstawowy, 01powtórz wszystko, 10powtórz oprócz ostatniego,11 powtórz oprócz dwóch ostatnich.
  • Pliki testowe i kod są dostępne tutaj https://github.com/gxtaillon/codegolf/tree/master/Kolmogorov
gxtaillon
źródło
Witam, dziękuję za tę odpowiedź! :) Nie zrozumiałem, co dzieje się w systemie binarnym, kiedy konwertujesz abcdefghijklm...na. 𘑥򎑡𡁢𑒁豱󐰢𼝤...Czy mógłbyś wyjaśnić to trochę bardziej, proszę? Ponadto poprawiłem zliczanie znaków i zamieniłem myślniki na # 10 w pytaniu. Jednak moje liczby są nadal inne niż twoje. Nie wiem, dlaczego użyłem narzędzia mothereff.in.
xem
@xem Skomplikowane szczegóły zostały ujawnione.
gxtaillon
Mój umysł jest (w przenośni) dosłownie rozwiewany przez pomysł, że liczby 0 i 2-127 mogą być kodowane na 5 bitach. Znalazłeś to sam, czy było to coś znanego? Dodatkowe pytanie: ile bitów potrzebujesz, aby przechowywać tylko drukowalne znaki ascii, tj. 95 różnych znaków?
xem.
@xem Liczby nie są kodowane na 5 bitach, każdy z ich czynników to. Byłbym bardzo szczęśliwy, gdybym znalazł sposób na zakodowanie 7 bitów tylko na 5. Jeśli chodzi o znaki ascii, używając tej metody nadal potrzebowaliby 5 bitów.
gxtaillon
1
Ponieważ w podanym zakresie jest maksymalnie 6 czynników na liczbę, długość wykorzystuje 3 z 5-bitowego „bloku”. Następnie indeksy są kodowane na 5 bitach, tak. W tej implementacji jeden z 2 nieużywanych bitów w bloku długości jest wykorzystywany do uzyskania dodatkowej kompresji.
gxtaillon
4

C ++ (C ++ 11), 2741 punktów

Ta odpowiedź używa UTF-32 jako kodowania skompresowanego tekstu.

#include <cstdio>
#include <iostream>
#include <locale>
#include <string>
#define L locale
using namespace std;long b,n,i,l;void c(){string d;char x;while((x=cin.get())!=EOF)d+=x;b=(d.size()*7)%20;n=5;wcout.imbue(L());for(char y:d){b=b<<7|y&127;n+=7;if(n>=20)wcout.put(b>>(n-=20)&0xFFFFF);}if(n)wcout.put(b<<20-n&0xFFFFF);}void d(){wstring d;wchar_t w;wcin.imbue(L());while((w=wcin.get())!=EOF)d+=w;l=-1;for(wchar_t y:d){b=b<<20|y;n+=20;if(l<0)l=b>>15&31,n-=5;while(n>=7&(i<d.size()-1|n>20-l))cout.put(b>>(n-=7)&127);++i;}}int main(int t,char**a){L::global(L("en_US.utf8"));**++a<'d'?c():d();}

Char liczy się i zdobywa punkty

Kod: 593 znaków (znak nowej linii jest usuwany)

Skompresowane teksty (znaki Unicode) : 654 + 145 + 82 + 38 + 51 + 104 + 73 + 423 + 506 = 2148 (liczone wc -mjako liczba znaków Unicode zamiast bajtów, liczba bajtów jest, tak jak w przypadku odpowiedzi @ gxtaillon , wyższa niż oryginały, łącznie 8413 bajtów, zgodnie z policzeniemwc -c ).

Współczynnik kompresji (ASCII do Unicode) : 35,01% (przy użyciu 6135 bajtów z pytania (tak samo jakwc -c ))

Strzec się:

Wiele powłok nie jest w stanie obsłużyć znaków Unicode generowanych przez ten program. Zatem dekompresja może prowadzić do obcięcia tekstu gdzieś, gdy powłoka nie może obsłużyć znaku, ponieważ dane wejściowe są pobierane przezstdin z powłoki.

Kompilacja

Powinien się skompilować z clang++ig++ -std=c++11 , ale to pokaże jakieś ostrzeżenia o pierwszeństwa operatora, jako wyraz jak b<<20-n&0xFFFFFnie będą traktowane jak ((b << 20) - n) & 0xFFFFF, jak można by się spodziewać, ale raczej jako (b << (20 - n)) & 0xFFFFF.

Stosowanie

  • Skompiluj program w plik wykonywalny, np ./compress .
  • Uruchom program w ./compress ccelu kompresji lub ./compress ddekompresji. (Ostrożnie, pominięcie opcji daje SEGFAULT (sprawdzanie błędów jest tak drogie, że znak jest drogi ...), a inne opcje (takie jak używanie Dzamiast d) mogą dawać nieoczekiwane wyniki
  • Dane wejściowe są odczytywane, stdina dane wyjściowe zapisywanestdout

Jak to działa

Nie golfił

#include <cstdio>
#include <iostream>
#include <locale>
#include <string>

using namespace std;

long b, n, i, l;

// Compress
void c() {
    string d;
    char x;
    // Read from STDIN until EOF
    while((x = cin.get()) != EOF)
        d += x;
    // Calculate the number of bits used from the last unicode character
    // (maximum 19) and store it into the first 5 bits
    b = (d.size() * 7) % 20;
    n = 5;
    // Set the output locale to allow unicode
    wcout.imbue(locale());
    // For each character in the input...
    for (char y : d) {
        // Add its bit representation (7 bits) to the right of the buffer
        // by shifting the buffer left and ORing with the character code
        b = (b << 7) | (y & 127);
        // Add 7 to the bit counter
        n += 7;
        // If enough data is present (20 bits per output character),
        // calculate the output character by shifting the buffer right,
        // so that the last 20 bits are the left 20 bits of the buffer.
        // Also decrement the bit counter by 20, as 20 bits are removed.
        if (n >= 20)
            wcout.put((b >> (n -= 20)) & 0xFFFFF);
    }
    // If there are still bits in the buffer, write them to the front of
    // another unicode character
    if (n)
        wcout.put((b << (20 - n)) & 0xFFFFF);
}

// Decompress
void d() {
    wstring d;
    wchar_t w;
    // Set STDIN to UNICODE
    wcin.imbue(locale());
    // Read wide characters from STDIN (std::wcin) until EOF
    while ((w = wcin.get()) != EOF)
        d += w;
    // `l' represents the number of bits used in the last unicode character.
    // It will be set later
    l = -1;
    // For each input character...
    for (wchar_t y : d) {
        // Add it to the buffer and add 20 to the bit counter
        b = (b << 20) | y;
        n += 20;
        // If the number of bits in the last unicode character has not been
        // set yet, read the first 5 buffer bits into `l'. This is
        // necessary because the last character may contain more than 7
        // (one entire uncompressed character) unused bits which may
        // otherwise be interpreted as garbage.
        if (l < 0) {
            l = (b >> 15) & 31;
            n -= 5;
        }
        // As long as there is data to turn into output characters
        // (at least 7 bits in the buffer and either not the last
        // unicode character or before the unused bits)
        while (n >= 7 && ((i < d.size() - 1) || (n > (20 - l)))
            cout.put((b >> (n -= 7)) & 127); // Output the left 7 bits in the buffer as an ASCII character
        ++i; // Increment the character index, so that we know when we reach the last input character
    }
}
int main(int t, char**a) {
    // Set the default locale to en_US.utf8 (with unicode)
    locale::global(locale("en_US.utf8"));
    // Decide whether to compress or decompress.
    // This is just fancy pointer stuff for a[1][0] < 'd' ? c() : d()
    (**(++a) < 'd') ? c() : d();
}

Wyjaśnienie

Ponieważ wszystkie znaki Unicode od U+0000do U+10FFFFsą dozwolone, możemy użyć 20 bitów na znak Unicode:U+FFFFF używa 20 bitów i nadal znajduje się w dozwolonym zakresie. Dlatego staramy się po prostu wcisnąć wszystkie pojedyncze bity znaków ASCII w znaki Unicode, aby przechowywać wiele znaków ASCII w jednym znaku Unicode. Musimy jednak również przechowywać liczbę bitów użytych w ostatnim znaku Unicode, ponieważ nieużywane bity śmieci mogą być inaczej interpretowane jako właściwe skompresowane znaki ASCII. Ponieważ maksymalna liczba używanych bitów w ostatnim znaku Unicode wynosi 20, potrzebujemy do tego 5 bitów, które są umieszczone na początku skompresowanych danych.

Przykładowe dane wyjściowe

To jest wynik na przykład # 4 (podany przez less):

<U+4E1C5><U+8F265><U+CD9F4><U+69D5A><U+F66DD><U+DBF87><U+1E5CF><U+A75ED>
<U+DFC79><U+F42B8><U+F7CBC><U+BA79E><U+BA77F>쏏𦛏<U+A356B><U+D9EBC><U+63ED8>
<U+B76D1><U+5C3CE><U+6CF8F><U+96CC3><U+BF2F5><U+D3934><U+74DDC><U+F8EAD>
<U+7E316><U+DEFDB><U+D0AF5><U+E7C77><U+EDD7A><U+73E5C><U+786FD><U+DB766>
<U+BD5A7><U+467CD><U+97263><U+C5840>

( 쏏𦛏podaj <U+C3CF><U+266CF>jako kody znaków, ale mogłem się pomylić)

hlt
źródło
2

Python 3, 289 + 818 = 1107 punktów

Tylko lekko golfa.

import zlib as Z
def p(s):
 z=int.from_bytes(Z.compress(s),'big');o=''
 while z:
  z,d=divmod(z,1<<20)
  if d>0xd000:d+=1<<16
  o+=chr(d)
 return o[::-1]
def u(s):
 i=0
 for c in s:
  d=ord(c)
  if d>0xe000:d-=1<<16
  i=(i<<20)+d
 return Z.decompress(i.to_bytes(i.bit_length()//8+1,'big'))

Całkowity rozmiar kodu wynosi 289 bajtów i koduje podane 6135 bajtów na 818 znaków Unicode - całkowita liczba bajtów wyjściowych wynosi 3201 bajtów, znacznie mniej niż pierwotne dane wejściowe.

Koduje za pomocą zlib, a następnie za pomocą kodowania Unicode. Potrzebna była dodatkowa logika, aby uniknąć surogatów (których Python naprawdę nienawidzi).

Przykładowe dane wyjściowe z # 4, jak widać less(37 znaków Unicode):

x<U+AC0DC><U+BB701><U+D0200><U+D00B0><U+AD2F4><U+EEFC5>𤆺<U+F4F34>멍<U+3C63A><U+2F62C><U+BA5B6><U+4E70A><U+F7D88><U+FF138><U+40CAE>
<U+CB43E><U+C30F5><U+6FFEF>𥠝<U+698BE><U+9D73A><U+95199><U+BD941><U+10B55E><U+88889><U+75A1F><U+4C4BB><U+5C67A><U+1089A3><U+C75A7>
<U+38AC1><U+4B6BB><U+592F0>ᚋ<U+F2C9B>

Program sterownika do testowania:

if __name__ == '__main__':
    import os
    total = 0
    for i in range(1,10+1):
        out = p(open('data/%d.txt'%i,'rb').read())
        total += len(out)
        open('out/%d.bin'%i,'w',encoding='utf8').write(out)
    print(total)
    for i in range(1,10+1):
        out = u(open('out/%d.bin'%i,'r',encoding='utf8').read())
        open('data2/%d.txt'%i,'wb').write(out)

Liczniki bajtów wyjściowych:

 607 out/1.bin
 128 out/2.bin
 101 out/3.bin
 143 out/4.bin
 177 out/5.bin
 145 out/6.bin
 186 out/7.bin
 222 out/8.bin
 389 out/9.bin
1103 out/10.bin
3201 total
nneonneo
źródło
1
Czy to nie fakt, że używa to oszukiwania w bibliotece kompresji?
Rozpad beta
@BetaDecay: Nie ogranicza tego pytanie, więc pomyślałem, że to uczciwa gra.
nneonneo
Musisz także dołączyć dekompresor.
Beta Decay
@BetaDecay: pjest pakującym, ujest rozpakowującym.
nneonneo
1

Python 2 - 1141 punktów

from zlib import *;v=256
def e(b):
 x=0
 for c in compress(b,9):x=(x*v)+ord(c)
 b=bin(x)[2:]
 return "".join(unichr(int("1"+b[a:a+19],2))for a in range(0,len(b),19))
def d(s):
 y=int("".join(bin(ord(a))[3:]for a in s),2);x=""
 while y:y,d=(y/v,chr(y%v));x=d+x
 return decompress(x)

Rozmiar kodu to 281bajty i koduje 6135bajty na 860znaki Unicode.

Jak to działa:

Aby zakodować:

  1. Skompresuj ciąg do zakodowania.
  2. Interpretuj skompresowany ciąg jako podstawową liczbę 256.
  3. Konwertuj liczbę na binarną.
  4. Podziel plik binarny na grupy 19 bitów, dodaj 1trochę na początku każdego z nich, a następnie przekonwertuj na znaki Unicode.

Dekodowanie jest odwrotne.

Zauważ, że niektóre wersje Pythona mogą obsługiwać tylko znaki Unicode do 0xFFFF, dlatego ten kod podniesie wartość ValueError.

pppery
źródło