Oto plik tekstowy ASCII o wielkości 1,2 MB zawierający tekst Moby-Dicka Hermana Melville'a ; lub Wieloryb . Twoim zadaniem jest napisanie programu lub funkcji (lub klasy itp. - patrz poniżej), które otrzymają ten plik po jednym znaku na raz, i na każdym kroku należy odgadnąć następny znak.
To jest wyzwanie kodowe . Twój wynik będzie
2*L + E
gdzie L
jest rozmiar twojego przesłania w bajtach, i E
liczba znaków, które odgaduje niepoprawnie. Najniższy wynik wygrywa.
Dalsze szczegóły
Twoje zgłoszenie będzie programem lub funkcją (itp.), Które będą wielokrotnie wywoływane, wywoływane lub wysyłane. (1215235 razy, aby być dokładne). Kiedy to się nazywa po n th czasie będzie mieć n th charakteru whale.txt
lub whale2.txt
i to musi zgadywać jego wyjście dla ( n + 1 ) th charakteru. E
Składnikiem jego wynik będzie łączna liczba znaków, że domyśla się nieprawidłowo.
Większość zgłoszeń będzie musiała przechowywać pewien stan pomiędzy wywołaniami, aby mogli śledzić, ile razy zostały wywołane i jakie były poprzednie dane wejściowe. Możesz to zrobić, pisząc do zewnętrznego pliku, używając static
zmiennych globalnych, podając klasę zamiast funkcji, używając monady stanu lub cokolwiek innego, co działa w twoim języku. Twoje zgłoszenie musi zawierać dowolny kod wymagany do zainicjowania jego stanu przed pierwszym wywołaniem.
Twój program powinien działać deterministycznie, aby zawsze tworzył te same domysły przy tych samych danych wejściowych (a zatem zawsze otrzymuje ten sam wynik).
Twoja odpowiedź musi zawierać nie tylko przesłanie, ale także kod użyty do obliczenia E
części wyniku. Nie musi to być napisane w tym samym języku co przesłanie i nie będzie wliczane do liczby bajtów. Zachęcamy do uczynienia go czytelnym.
Jeśli chodzi o interfejs między twoim zgłoszeniem a programem do obliczania wyników, wszystko jest w porządku, o ile twój program zawsze podaje jeden bajt danych wyjściowych przed otrzymaniem kolejnego bajtu danych wejściowych. (Na przykład nie można po prostu przekazać ciągu zawierającego wszystkie dane wejściowe i uzyskać ciąg zawierający wszystkie dane wyjściowe).
Musisz faktycznie uruchomić swój program testowy i obliczyć / zweryfikować swój wynik przed przesłaniem zgłoszenia. Jeśli twoje zgłoszenie przebiega zbyt wolno, abyś mógł zweryfikować swój wynik, nie kwalifikuje się do konkurowania, nawet jeśli wiesz, jaki byłby jego wynik.
L
Składnikiem swój wynik zostanie obliczona zgodnie ze zwykłymi zasadami wyzwań kod golfowych. Jeśli Twoje zgłoszenie będzie zawierać wiele plików, zwróć uwagę na zasady dotyczące oceniania i struktury katalogów w tym przypadku. Wszelkie dane używane przez kod muszą być uwzględnione w L
wyniku.
Możesz importować istniejące biblioteki, ale nie możesz ładować żadnych innych plików zewnętrznych, a twój kod może nie mieć dostępu do whale.txt
lubwhale2.txt
plik w jakikolwiek inny sposób niż opisano powyżej. Nie możesz ładować żadnych przeszkolonych sieci neuronowych ani innych źródeł danych statystycznych. (Korzystanie z sieci neuronowych jest w porządku, ale musisz dołączyć dane o wadze do swojego zgłoszenia i policzyć je do liczby bajtów.) Jeśli z jakiegoś powodu twój język lub biblioteki zawierają funkcję, która udostępnia część lub całość tekstu Moby Dick , nie możesz korzystać z tej funkcji. Oprócz tego możesz używać dowolnych innych wbudowanych lub bibliotekowych funkcji, w tym związanych z przetwarzaniem tekstu, prognozowaniem lub kompresją, o ile są one częścią twojego języka lub jego standardowych bibliotek. W przypadku bardziej egzotycznych, specjalistycznych procedur, które zawierają źródła danych statystycznych, należy je zaimplementować samodzielnie i uwzględnić w liczbie bajtów.
Prawdopodobnie niektóre zgłoszenia będą zawierać komponenty, które same są generowane przez kod. W takim przypadku prosimy o dołączenie do odpowiedzi kodu użytego do ich wytworzenia i wyjaśnienie, jak to działa . (Tak długo, jak ten kod nie jest potrzebny do uruchomienia przesyłania, nie będzie uwzględniony w liczbie bajtów).
Ze względów historycznych istnieją dwie wersje pliku i możesz użyć jednej z nich w odpowiedzi. W whale2.txt
(połączony powyżej) tekst nie jest zawijany, więc znaki nowej linii pojawiają się tylko na końcu akapitów. W oryginale whale.txt
tekst jest zawinięty do szerokości 74 znaków, więc musisz przewidzieć koniec każdej linii, a także przewidzieć tekst. To sprawia, że wyzwanie jest bardziej skomplikowane, dlatego whale2.txt
jest zalecane w przypadku nowych odpowiedzi. Oba pliki mają ten sam rozmiar, 1215236 bajtów.
Podsumowując, wszystkie odpowiedzi powinny zawierać następujące rzeczy:
- Twoje samo zgłoszenie. (Kod oraz wszelkie używane przez niego pliki danych - mogą być linkami, jeśli są duże).
- Wyjaśnienie, jak działa Twój kod. Proszę wyjaśnić metodę we / wy, a także sposób przewidywania następnego znaku. Wyjaśnienie twojego algorytmu jest ważne, a dobre wyjaśnienia przyniosą ode mnie nagrody.
- Kod użyty do oceny wyniku. (Jeśli jest to identyczna z poprzednią odpowiedzią, możesz po prostu link do niej.)
- Każdy kod użyty do wygenerowania zgłoszenia wraz z objaśnieniem tego kodu. Obejmuje to kod użyty do optymalizacji parametrów, generowania plików danych itp. (Nie wlicza się to do liczby bajtów, ale powinno zostać uwzględnione w odpowiedzi).
Tabela liderów
Nagrody
Od czasu do czasu oferuję nagrody, aby zachęcić do różnych podejść.
Pierwszy, 50 punktów, został przyznany A. Rexowi za najlepszą wówczas odpowiedź.
Druga, 100 punktów, została również przyznana A. Rexowi za tę samą odpowiedź, ponieważ dodali bardzo dobre wyjaśnienie do swojej istniejącej odpowiedzi.
Następna nagroda, 200 punktów , zostanie przyznana jednemu z nich
- Konkurencyjna odpowiedź wykorzystująca nową technikę. (Będzie to oparte na moim subiektywnym osądzie, ponieważ to mój przedstawiciel wchodzi na nagrodę, ale możesz mi zaufać, że jestem uczciwy. Pamiętaj, że twoja odpowiedź musi zawierać wystarczające wyjaśnienie, aby zrozumieć, jak to działa!) Takiej odpowiedzi potrzebowałem nie ma najwyższego wyniku, musi po prostu dobrze sobie poradzić w porównaniu z istniejącymi odpowiedziami. Szczególnie zależy mi na rozwiązaniach opartych na cyklicznych sieciach neuronowych, ale nagrodę przyznam za wszystko, co wydaje się wystarczająco inne niż modele Markowa, które dominują w obecnych najlepszych wynikach.
Lub:
- Każdy inny, kto bije najlepszy wynik A. Rexa (obecnie 444444), przy użyciu dowolnej metody.
Po odebraniu nagrody za 200 punktów najprawdopodobniej zaoferuję 400 punktów, odpowiednio aktualizując wymagania.
źródło
Odpowiedzi:
/// , 2 * 1 + 1020874 = 1020876
Drukuje spację.
źródło
Node.js, 2 * 224 + 524279 = 524727
Informacje na temat aktualizacji wyników można znaleźć w dzienniku zmian na końcu tego postu.
Funkcja pobierająca i zwracająca bajt.
Składa się z prostego modelu PPM, który patrzy na ostatnie 8 znaków, aby przewidzieć następny.
Ufamy wzorowi długości L, gdy napotkaliśmy go co najmniej T [L] razy, gdzie T jest tablicą dowolnych progów: [1,1,2,1,2,3,5,2] . Ponadto zawsze ufamy wzorowi, którego pierwsza postać pasuje
[A-Z '"(]
.Wybieramy najdłuższy zaufany wzór i zwracamy prognozę z najwyższym wynikiem związanym z tym wzorem w momencie połączenia.
Notatki
To oczywiście nie jest zoptymalizowane pod kątem prędkości, ale działa na około 15 sekundach na moim laptopie.
Gdybyśmy mogli powtórzyć proces kilka razy z rzędu bez resetowania modelu, liczba błędów zbiegnie się do ~ 268000 po 5 iteracjach.
Aktualny wskaźnik skuteczności funkcji prognozowania wynosi ~ 56,8%. Jak zauważył @immibis w komentarzach, jeśli błędne i prawidłowe domysły zostaną zmieszane razem, wynik nie będzie nawet ledwo czytelny.
Na przykład ten fragment kodu na końcu książki:
staje się:
Zastępując błędne domysły znakami podkreślenia, mamy lepsze pojęcie o tym, co funkcja zadziałała:
Uwaga : powyższy przykład został utworzony przy użyciu poprzedniej wersji kodu, działającej na pierwszej wersji pliku wejściowego.
Kod testowy
Zmień dziennik
źródło
sidg tlanses,oeth to, shuld hottut tild aoersors Ch, th! Sa, yr! Sheu arinning whales aut ihe e sl he traaty of rrsf tg homn Bho dla tiasot a shab sor ty, af etoors tnd hocket sh bts ait mtubb tiddin tis aeewnrs, dnhost maundy cnd sner aiwt d boelh cheugh -aaieiyns aasiyns taaeiins! th, tla
. Czasami potrafi uzyskać kilka pełnych słów. Jakwhales
.Perl, 2 · 70525 + 326508 = 467558
Urządzenie prognozujące
Aby uruchomić ten program, potrzebujesz tego pliku tutaj , który musi mieć nazwę
B
. (Możesz zmienić tę nazwę pliku w drugim wystąpieniuB
powyższego znaku .) Zobacz poniżej, jak wygenerować ten plik.Program wykorzystuje kombinację modeli Markowa zasadniczo jak w tej odpowiedzi użytkownika 2699 , ale z kilkoma niewielkimi modyfikacjami. To tworzy rozkład dla następnej postaci. Korzystamy z teorii informacji, aby zdecydować, czy zaakceptować błąd, czy wydać fragmenty pamięci na
B
kodowanie wskazówek (a jeśli tak, to w jaki sposób). Używamy kodowania arytmetycznego do optymalnego przechowywania ułamkowych bitów z modelu.Program ma długość 582 bajtów (w tym niepotrzebną końcową
B
nową linię ), a plik binarny ma długość 69942 bajtów, więc zgodnie z regułami oceniania wielu plików oceniamyL
jako 582 + 69942 + 1 = 70525.Program prawie na pewno wymaga architektury 64-bitowej (little-endian?). Uruchomienie
m5.large
instancji na Amazon EC2 zajmuje około 2,5 minuty .Kod testowy
Wiązka testowa zakłada, że przesłanie znajduje się w pliku
submission.pl
, ale można to łatwo zmienić w drugim wierszu.Porównanie tekstu
Ta próbka (wybrana w innej odpowiedzi ) pojawia się dość późno w tekście, więc model jest dość rozwinięty do tego momentu. Pamiętaj, że model jest powiększony o 70 kilobajtów „podpowiedzi”, które bezpośrednio pomagają odgadnąć postacie; nie jest napędzany po prostu krótkim fragmentem kodu powyżej.
Generowanie wskazówek
Poniższy program akceptuje dokładny kod przesłania powyżej (na standardowym wejściu) i generuje dokładny
B
plik powyżej (na standardowym wyjściu):Uruchomienie zajmuje około tyle samo czasu co przesłanie, ponieważ wykonuje podobne obliczenia.
Wyjaśnienie
W tej sekcji postaramy się opisać wystarczająco szczegółowo to rozwiązanie, abyś mógł sam „wypróbować go w domu”. Główną techniką, która odróżnia tę odpowiedź od innych, jest kilka sekcji w dół jako mechanizm „przewijania”, ale zanim do tego dojdziemy, musimy skonfigurować podstawy.
Model
Podstawowym składnikiem rozwiązania jest model językowy. Dla naszych celów model jest czymś, co wymaga pewnej ilości tekstu w języku angielskim i zwraca rozkład prawdopodobieństwa dla następnego znaku. Gdy użyjemy modelu, tekst w języku angielskim będzie jakimś (poprawnym) prefiksem Moby Dicka. Należy pamiętać, że pożądanym wyjściem jest rozkład , a nie tylko jedno odgadnięcie najbardziej prawdopodobnej postaci.
W naszym przypadku zasadniczo używamy modelu w tej odpowiedzi użytkownika 2699 . Nie wykorzystaliśmy modelu z odpowiedzi o najwyższym wyniku (innej niż nasza) Andersa Kaseorga właśnie dlatego, że nie byliśmy w stanie wyodrębnić dystrybucji, a nie pojedynczego najlepszego przypuszczenia. Teoretycznie ta odpowiedź oblicza ważoną średnią geometryczną, ale otrzymaliśmy nieco słabe wyniki, gdy interpretowaliśmy to zbyt dosłownie. „Ukradliśmy” model z innej odpowiedzi, ponieważ nasz „sekretny sos” nie jest modelem, ale raczej ogólnym podejściem. Jeśli ktoś ma „lepszy” model, powinien być w stanie uzyskać lepsze wyniki przy użyciu pozostałych naszych technik.
Jako uwaga, większość metod kompresji, takich jak Lempel-Ziv, może być postrzegana jako „model językowy” w ten sposób, choć może trzeba trochę zmrużyć oczy. (Jest to szczególnie trudne w przypadku czegoś, co powoduje transformację Burrowsa-Wheelera!) Ponadto zauważ, że model użytkownika 2699 jest modyfikacją modelu Markowa; zasadniczo nic innego nie jest konkurencyjne w stosunku do tego wyzwania, a może nawet do modelowania tekstu w ogóle.
Ogólna architektura
Dla zrozumienia dobrze jest podzielić ogólną architekturę na kilka części. Z perspektywy najwyższego poziomu musi być trochę kodu zarządzania stanem. Nie jest to szczególnie interesujące, ale dla kompletności chcemy podkreślić, że w każdym momencie program jest proszony o kolejne przypuszczenia, ma dostępny poprawny prefiks Moby Dick. W żaden sposób nie wykorzystujemy naszych niepoprawnych domysłów. Ze względu na wydajność, model językowy może prawdopodobnie ponownie użyć swojego stanu z pierwszych N znaków do obliczenia swojego stanu dla pierwszych (N + 1) znaków, ale w zasadzie może za każdym razem przywoływać obliczenia od zera.
Odłóżmy ten podstawowy „sterownik” programu na bok i zajrzyjmy do części odgadującej następny znak. Pomaga koncepcyjnie oddzielić trzy części: model języka (omówiony powyżej), plik „podpowiedzi” i „tłumacza”. Na każdym kroku tłumacz zapyta model językowy o rozkład dla następnego znaku i może odczytać pewne informacje z pliku wskazówek. Następnie połączy te części w zgadywanie. Dokładnie, jakie informacje znajdują się w pliku wskazówek, a także jak są wykorzystywane zostaną wyjaśnione później, ale na razie pomaga zachować te części osobno. Zauważ, że pod względem implementacji plik wskazówek jest dosłownie osobnym (binarnym) plikiem, ale mógł być łańcuchem lub czymś przechowywanym w programie. W przybliżeniu
Jeśli ktoś używa standardowej metody kompresji, takiej jak bzip2, jak w tej odpowiedzi , plik „podpowiedzi” odpowiada skompresowanemu plikowi. „Tłumacz” odpowiada dekompresorowi, podczas gdy „model językowy” jest nieco niejawny (jak wspomniano powyżej).
Dlaczego warto korzystać z pliku podpowiedzi?
Wybierzmy prosty przykład do dalszej analizy. Załóżmy, że tekst to
N
znaki długie i dobrze przybliżone przez model, w którym każdy znak jest (niezależnie) literąE
z prawdopodobieństwem nieco mniejszym niż połowa,T
podobnie z prawdopodobieństwem nieco mniejszym niż połowa, iA
z prawdopodobieństwem 1/1000 = 0,1%. Załóżmy, że żadne inne postacie nie są możliwe; w każdym razieA
jest bardzo podobny do przypadku wcześniej niewidzianej postaci nieoczekiwanej.Jeśli działaliśmy w trybie L 0 (jak większość, ale nie wszystkie, inne odpowiedzi na to pytanie), nie ma lepszej strategii dla tłumacza niż wybranie jednego z
E
iT
. Średnio uzyska około połowy poprawnych znaków. Więc E ≈ N / 2 i wynik ≈ N / 2 również. Jeśli jednak zastosujemy strategię kompresji, możemy skompresować ją do nieco więcej niż jednego bitu na znak. Ponieważ L jest liczone w bajtach, otrzymujemy L ≈ N / 8 i tym samym osiągamy ≈ N / 4, dwa razy więcej niż poprzednia strategia.Osiągnięcie tej szybkości nieco więcej niż jednego bitu na znak dla tego modelu jest nieco nietrywialne, ale jedną z metod jest kodowanie arytmetyczne.
Kodowanie arytmetyczne
Jak powszechnie wiadomo, kodowanie jest sposobem reprezentowania niektórych danych przy użyciu bitów / bajtów. Na przykład ASCII to 7-bitowe kodowanie znaków w tekście angielskim i powiązanych znakach, a także kodowanie oryginalnego rozważanego pliku Moby Dick. Jeśli niektóre litery są bardziej powszechne niż inne, wówczas kodowanie o stałej szerokości, takie jak ASCII, nie jest optymalne. W takiej sytuacji wiele osób sięga po kodowanie Huffmana . Jest to optymalne, jeśli chcesz mieć stały (bez prefiksów) kod z liczbą całkowitą bitów na znak.
Jednak kodowanie arytmetyczne jest jeszcze lepsze. Z grubsza mówiąc, jest w stanie używać „ułamkowych” bitów do kodowania informacji. Istnieje wiele przewodników po kodowaniu arytmetycznym dostępnych online. Pominiemy tutaj szczegóły (szczególnie praktyczne wdrożenie, które może być nieco trudne z punktu widzenia programowania) ze względu na inne zasoby dostępne online, ale jeśli ktoś narzeka, może ten rozdział można rozwinąć bardziej.
Jeśli tekst jest generowany przez znany model językowy, wówczas kodowanie arytmetyczne zapewnia zasadniczo optymalne kodowanie tekstu z tego modelu. W pewnym sensie „rozwiązuje” to problem kompresji tego modelu. (Tak więc w praktyce głównym problemem jest to, że model nie jest znany, a niektóre modele są lepsze niż inne w modelowaniu tekstu ludzkiego.) Jeśli nie dopuszczono się błędów w tym konkursie, to w języku poprzedniej sekcji , jednym ze sposobów na rozwiązanie tego problemu byłoby użycie kodera arytmetycznego do wygenerowania pliku „wskazówek” z modelu językowego, a następnie użycie dekodera arytmetycznego jako „interpretera”.
W tym zasadniczo optymalnym kodowaniu ostatecznie wydajemy -log_2 (p) bitów na znak o prawdopodobieństwie p, a ogólną przepływnością kodowania jest entropia Shannona . Oznacza to, że znak o prawdopodobieństwie bliskim 1/2 zajmuje około jednego bitu do zakodowania, a znak o prawdopodobieństwie 1/1000 zajmuje około 10 bitów (ponieważ 2 ^ 10 to w przybliżeniu 1000).
Ale wskaźnik punktacji dla tego wyzwania został dobrze wybrany, aby uniknąć kompresji jako optymalnej strategii. Będziemy musieli wymyślić jakiś sposób na popełnienie błędów jako kompromis w uzyskaniu krótszego pliku wskazówek. Na przykład jedna strategia, którą można wypróbować, jest prostą strategią rozgałęziania: na ogół staramy się stosować kodowanie arytmetyczne, kiedy tylko możemy, ale jeśli rozkład prawdopodobieństwa z modelu jest „zły”, to po prostu odgadujemy najbardziej prawdopodobną postać i nie „ spróbuj go zakodować.
Po co popełniać błędy?
Przeanalizujmy wcześniejszy przykład, aby uzasadnić, dlaczego możemy chcieć popełniać błędy „umyślnie”. Jeśli użyjemy kodowania arytmetycznego do zakodowania poprawnego znaku, wydamy około jednego bitu w przypadku
E
lubT
, ale około dziesięciu bitów w przypadkuA
.Ogólnie rzecz biorąc, jest to całkiem dobre kodowanie, wydające nieco ponad nieco na postać, mimo że istnieją trzy możliwości; w zasadzie
A
jest to raczej mało prawdopodobne i nie spędzamy zbyt często odpowiadających mu dziesięciu bitów. Czy jednak nie byłoby miło, gdybyśmy mogli zamiast tego popełnić błąd w przypadkuA
? W końcu metryka problemu uważa, że 1 bajt = 8 bitów długości jest równoważny 2 błędom; dlatego wydaje się, że należy preferować błąd zamiast wydawać więcej niż 8/2 = 4 bity na znak. Wydanie więcej niż bajtu, aby zapisać jeden błąd, zdecydowanie brzmi nieoptymalnie!Mechanizm „przewijania do tyłu”
W tej sekcji opisano główny sprytny aspekt tego rozwiązania, który jest sposobem obsługi niepoprawnych domysłów bez żadnych kosztów.
W tym prostym przykładzie, który analizowaliśmy, mechanizm przewijania jest szczególnie prosty. Tłumacz interpretuje jeden bit z pliku wskazówek. Jeśli jest to 0, zgaduje
E
. Jeśli jest to 1, zgadujeT
. Przy następnym wywołaniu zobaczy, jaki jest prawidłowy znak. Jeśli plik podpowiedzi jest dobrze skonfigurowany, możemy zapewnić, że w przypadkuE
lubT
interpreter zgadnie poprawnie. Ale co z tymA
? Pomysł z mechanizmem zwijania jest po prostu nie kodowaćA
w ogóle . Mówiąc ściślej, jeśli interpreter dowie się później, że poprawny znak byłA
, metaforycznie „ przewija taśmę”: zwraca fragment, który wcześniej przeczytał. Bit, który odczytuje, ma zamiar kodowaćE
lubT
, ale nie teraz; zostanie wykorzystany później. W tym prostym przykładzie, w zasadzie oznacza to, że utrzymuje zgadywania ten sam znak (E
lubT
), aż robi się to dobrze; potem odczytuje kolejny kawałek i kontynuuje.Kodowanie tego pliku wskazówek jest bardzo proste: zamień wszystkie
E
s na 0 bitów isT
na 1 bit, jednocześnieA
całkowicie ignorując s. Według analizy pod koniec poprzedniej sekcji, ten schemat popełnił pewne błędy, ale zmniejsza ogólny wynik, nie kodując żadnego z nichA
. Jako mniejszy efekt, faktycznie oszczędza również na długości pliku wskazówek, ponieważ ostatecznie używamy dokładnie jednego bitu na każdyE
iT
zamiast nieco więcej niż nieco.Trochę twierdzenia
Jak decydujemy, kiedy popełnić błąd? Załóżmy, że nasz model podaje rozkład prawdopodobieństwa P dla następnego znaku. Rozdzielimy możliwe znaki na dwie klasy: zakodowane i niekodowane . Jeśli prawidłowy znak nie zostanie zakodowany, wówczas skorzystamy z mechanizmu „przewijania”, aby zaakceptować błąd bez żadnych kosztów. Jeśli prawidłowy znak jest zakodowany, użyjemy innej dystrybucji Q do zakodowania go za pomocą kodowania arytmetycznego.
Ale jaki rozkład Q wybrać? Nietrudno dostrzec, że wszystkie zakodowane znaki powinny mieć większe prawdopodobieństwo (w P) niż znaki niekodowane. Także rozkład Q powinien zawierać tylko zakodowane znaki; w końcu nie kodujemy pozostałych, więc nie powinniśmy „wydawać” na nie entropii. Trochę trudniej jest dostrzec, że rozkład prawdopodobieństwa Q powinien być proporcjonalny do P na zakodowanych znakach. Zebranie tych obserwacji razem oznacza, że powinniśmy kodować najbardziej prawdopodobne znaki, ale być może nie mniej prawdopodobne, i że Q jest po prostu przeskalowane P na znakach kodowanych.
Okazuje się ponadto, że istnieje fajne twierdzenie dotyczące tego, który „punkt odcięcia” należy wybrać dla znaków kodujących: należy zakodować znak, o ile jest on co najmniej 1 / 5.393 tak prawdopodobny, jak inne zakodowane znaki łącznie. To „wyjaśnia” pojawienie się pozornie losowej stałej
5.393
bliżej końca powyższego programu. Liczba 1 / 5,393 × 0,18542 jest rozwiązaniem równania -p log (16) - p log p + (1 + p) log (1 + p) = 0 .Być może rozsądnym pomysłem jest zapisanie tej procedury w kodzie. Ten fragment jest w C ++:
Kładąc wszystko razem
Poprzednia sekcja jest niestety trochę techniczna, ale jeśli połączymy wszystkie pozostałe elementy razem, struktura będzie następująca. Ilekroć program jest proszony o przewidzenie następnego znaku po danym poprawnym znaku:
Kodowanie pliku wskazówek działa podobnie. W takim przypadku program wie, jaki jest poprawny następny znak. Jeśli jest to znak, który należy zakodować, to oczywiście należy użyć kodera arytmetycznego na nim; ale jeśli jest to niekodowany znak, po prostu nie aktualizuje stanu enkodera arytmetycznego.
Jeśli rozumiesz tło teoretyczne informacji, takie jak rozkłady prawdopodobieństwa, entropia, kompresja i kodowanie arytmetyczne, ale próbowałeś i nie zrozumiał tego postu (z wyjątkiem tego, dlaczego twierdzenie jest prawdziwe), daj nam znać, a my możemy spróbować wyjaśnić sytuację. Dziękuje za przeczytanie!
źródło
B
pliku potrzebny jest dodatkowy kod ? Jeśli tak, czy możesz to uwzględnić w swojej odpowiedzi?Python 3, 2 · 267 + 510193 = 510727
Urządzenie prognozujące
Wykorzystuje ważoną kombinację bayesowską modeli 0,…, 16 modeli Markowa, z wagami [1, 6, 12, 30, 65, 99, 87, 117, 118, 89, 95, 118, 96, 184, 126, 219, 126].
Wynik nie jest bardzo wrażliwy na dobór tych wag, ale zoptymalizowałem je, ponieważ mogłem, stosując ten sam algorytm wspinaczki późnej akceptacji , który zastosowałem w odpowiedzi na „Ustaw większość senacką” , gdzie każda mutacja kandydująca jest zaledwie przyrost o ± 1 do pojedynczej masy.
Kod testowy
źródło
b"\0\3\6\r\34'&-20'\22!P\n[\26"
to ascii reprezentacja wag, w której małe wartości niedrukowalne są unikane w postaci ósemkowej.Python 3 ,
2 * 279 + 592920 = 5934782 * 250 + 592467 = 5929672 * 271 + 592084 = 5926262 * 278 + 592059 = 5926152 * 285 + 586660 = 5872302 * 320 + 585161 = 5858012 * 339 + 585050 = 585728Wypróbuj online!
Funkcja wykorzystująca zmienne globalne. Uczy się, jak to się dzieje, budując model na poziomie słowa: biorąc pod uwagę to, co do tej pory widać w tym słowie , jaka jest najczęstsza następna postać? W miarę, jak pojawia się w nim więcej informacji, dość dobrze uczy się popularnych słów z tekstu, a także uczy najczęstszych znaków rozpoczynających następne słowo.
Na przykład:
Na początku nie radzi sobie zbyt dobrze, ale pod koniec pojawiają się duże części prawdziwych słów. Opcją zastępczą jest spacja, a po pojedynczym spacji jest to „a”, chyba że poprzednia litera była jedną z „nedtfo”, cyfry, łącznika lub apostrofu. Agresywnie przewiduje także łamanie wiersza po 71 znakach lub spację po 66. Oba zostały właśnie dostrojone do danych („t” jest znacznie bardziej powszechne po spacji, ale częściej jest już przewidywane, więc „ „lepiej zgadnąć poza tymi sześcioma specjalnymi przypadkami”.
Uczenie się, jakie pary słów idą w parze i uprzedzanie mapowania, okazało się nie opłacalne.
Kończy się na nim taki tekst:
co odpowiada tej części danych wejściowych:
Widać, gdzie w szczególności właściwe rzeczowniki wychodzą całkiem nieźle, ale końcówki słów są również w większości właściwe. Kiedy zobaczysz „dou”, oczekuje „wątpliwości”, ale gdy pojawi się „l”, staje się „dublonem”.
Jeśli uruchomisz go po raz drugi z tym samym modelem, który właśnie zbudował, natychmiast uzyska kolejne 92k poprawności (51,7% -> 59,3%), ale zawsze jest nieco poniżej 60% od drugiej iteracji.
Kod pomiarowy znajduje się w łączu TIO lub oto nieco lepsza wersja:
guess.txt
ma zgadnięte wyjście na końcu.źródło
C ++, wynik: 2 * 132 + 865821 = 866085
Dzięki @Quentin za oszczędność 217 bajtów!
Bardzo proste rozwiązanie, które po podaniu znaku wypisuje znak, który najczęściej pojawia się po znaku wejściowym.
Sprawdź wynik za pomocą:
Edycja: Używanie
whale2.txt
daje lepszy wynik.źródło
L
aby zapisać kilka znaków :)Python, 2 * 516 + 521122 = 522154
Algorytm:
Jeszcze inne przesłanie pytona, ten algorytm oblicza najbardziej prawdopodobną następną literę, patrząc na sekwencje o długości 1, ..., l. Wykorzystywana jest suma prawdopodobieństw i istnieje kilka sztuczek, aby uzyskać lepsze wyniki.
Wyniki:
Przeważnie bełkot, choć można go zauważyć, od czasu do czasu pojawia się w zwrotach, takich jak „Ojciec Mapple”.
Kod testowy:
Całkiem proste, wyświetla kilka przykładów tekstu w różnych punktach. Używa pliku whale2.txt, ponieważ pozwala to uniknąć dodatkowej logiki obliczania nowych linii.
źródło
C (gcc) ,
6797876528928476 bajtów,679619652740 błędne domysłyWypróbuj online!
Aktualizacja: ~ 27000 punktów zniżki przy zaktualizowanym pliku, 16 punktów (8 bajtów) z funkcją lepszej gry w golfa.
Wyjaśnienie
Działa to w ten sposób, że gdy kod przechodzi przez tekst, zapamiętuje ostatni znak, który zakończył dowolną 4-znakową sekwencję, i zwraca tę wartość. Nieco podobny do powyższego podejścia Arnaulda, ale opiera się na nieodłącznym prawdopodobieństwie dwóch danych sekwencji 4-znakowych kończących się w ten sam sposób.
Gra w golfa:
źródło
sh + bzip2, 2 * 364106 = 728212
2 * 381249 + 0 = 762498a następnie skompresowany przez bzip2 plik whale2.txt z brakującym pierwszym bajtem
Ignoruje swój wkład; wypisuje poprawną odpowiedź. Zapewnia to linię bazową na jednym końcu; daniero zapewnia linię bazową na drugim końcu.
Skrypt konstruktora:
Wiązka testowa we / wy (tcc; odetnij pierwszą linię dla gcc). Ta uprząż testowa może być używana przez każdego na odpowiedniej platformie, która przesyła pełny program, który oczekuje odczytu / zapisu We / Wy. Używa bajtów we / wy, aby uniknąć oszustwa. Program potomny musi opróżniać wyjście po każdym bajcie, aby uniknąć blokowania.
źródło
but may not load any other external files, and your code may not access the whale.txt file in any way other than described above.
klauzuli?nth
czas, otrzyma n-ty znakwhale.txt
lubwhale2.txt
i musi wypisać przypuszczenie(n+1)th
postać." - Jak spełniony jest ten wymóg? Kod wyświetla cały tekst zawhale.txt
każdym razem, gdy jest wykonywany.Python 3 , 879766
Wypróbuj online!
... The
///
Odpowiedź, która wypisuje spację, otrzymuje 10 upvotes, podczas gdy mój kod może dostać tylko 3 ...Wyjaśnienie:
Dla każdej postaci program:
frequency[prev][char]
frequency[char]
które mają łączny wynik
Ponieważ nie ma możliwości przesłania dużego pliku do TIO (poza pytaniem Dennisa), przykładowe uruchomienie w łączu TIO uruchamia program tylko dla niewielkiej części tekstu.
W porównaniu ze starszą odpowiedzią ta zawiera 362 więcej niepoprawnych znaków, ale kod jest krótszy o 255 bajtów. Mnożnik sprawia, że moje zgłoszenie ma niższy wynik.
źródło
C #, 378 * 2 + 569279 = 570035
Podejście to wykorzystuje tabelę przeglądową, aby poznać najczęstszy znak występujący po danym ciągu. Klawisze tabeli przeglądowej mają maksymalnie 4 znaki, więc funkcja najpierw aktualizuje tabelę przeglądową bieżącym znakiem, a następnie sprawdza, która postać jest najbardziej prawdopodobna po 4 poprzednich, w tym bieżącej . Jeśli te 4 znaki nie zostaną znalezione w tabeli przeglądowej, drukuje spację.
Ta wersja używa
whale2.txt
pliku, ponieważ znacznie poprawia liczbę udanych zgadnięć.Oto kod użyty do przetestowania klasy:
Kod działa w zaledwie 2 sekundy. Dla przypomnienia, to właśnie otrzymuję, gdy modyfikuję rozmiar klawiszy tabeli przeglądowej (obejmuje wyniki drugiego uruchomienia bez resetowania modelu):
Interesujące byłoby wiedzieć, dlaczego kluczowy rozmiar 4 znaków jest najlepszym wyborem w tym algorytmie.
Porównanie tekstu
Oryginał:
Odtworzono:
Domysły:
Zmień dziennik
whale2.txt
i tym samym usunięto optymalizację.źródło
Java 7, 1995 znaków, (1995 * 2 + 525158) 529148
Java jest do bani dla małych rozmiarów programów. W każdym razie wypróbowałem kilka niezwykle złożonych i podchwytliwych podejść, które przyniosły zaskakująco badziewne rezultaty. Potem wróciłem i po prostu zastosowałem proste podejście, które spowodowało mniejszy rozmiar programu i lepsze wyniki.
To podejście jest w rzeczywistości niezwykle proste. Ślepo podaje poprzednie znaki x (oprócz wszystkich podciągów tych znaków) do tablicy mieszającej, odwzorowanej na bieżący znak. Następnie śledzi, które wzory najdokładniej przewidują obecną postać. Jeśli wzorce poprzedzające określone znaki występują wielokrotnie, udaje im się przewidzieć postać. Daje pierwszeństwo dłuższym ciągom i daje pierwszeństwo każdemu znakowi, który najczęściej występuje po danym ciągu. Ten algorytm nie wie nic o rodzaju dokumentu ani języku angielskim.
Postanowiłem użyć 9 znaków i próbować dopasować całe słowa w tych poprzednich 9 znakach, jeśli to możliwe. Gdy nie próbujesz dopasowywać słów w łańcuchach, optymalna długość to 6 znaków, co powoduje kilka tysięcy nieporozumień.
Ciekawym spostrzeżeniem było to, że użycie 20 znaków spowodowało złe przewidywania za pierwszym razem, ale 99,9 procent dokładności przy kolejnych podaniach. Algorytm w zasadzie był w stanie zapamiętać książkę w nakładających się na siebie 20 bajtowych fragmentach, a było to na tyle wyraźne, że mogło przywołać całą książkę na raz.
źródło
Python 3,
2 × 497 + 619608 = 6206022 × 496 + 619608 = 620600Próbowałem tego niezależnie, ale skończyło się na tym, że faktycznie jest to gorsza wersja odpowiedzi Michaela Homera. Mam nadzieję, że to nie czyni mojej odpowiedzi całkowicie nieaktualną.
Z czasem buduje się słownik słów (zdefiniowany jako ciągi znaków zakończone przez
lub
\n
, z rozróżnianiem wielkości liter i włączając interpunkcję). Następnie przeszukuje ten słownik pod kątem słów zaczynających się od tego, co do tej pory wie o bieżącym słowie, sortuje wynikową listę według częstotliwości występowania (powoli) i zgaduje, że następny znak jest następnym znakiem w najczęściej pasującym słowie. Jeśli mamy już najczęściej pasujące słowo lub nie istnieje już pasujące słowo, zwraca ono.
Tworzy również obrzydliwie nieefektywny słownik par słów. Po osiągnięciu granicy słowa zgaduje, że następny znak jest pierwszą literą drugiego słowa w najczęściej pasującej parze słów lub
t
jeśli nie ma dopasowania. Nie jest to jednak zbyt mądre. NastępnieMoby
program poprawnie zgaduje, że jest następny znakD
, ale potem zapomina o kontekście i zwykle nazywa wieloryba „Kaczką Moby” (ponieważ słowo „holenderski” wydaje się częściej w pierwszej połowie tekstu ). Łatwo byłoby to naprawić, ustawiając pary słów na pojedyncze słowa, ale spodziewam się, że zysk byłby marginalny (ponieważ zwykle jest poprawny od trzeciego znaku, a pary słów nie są tak pomocne).Mógłbym dostroić to, aby lepiej pasowało do dostarczonego tekstu, ale nie sądzę, aby ręczne dostrojenie algorytmu na podstawie wcześniejszej wiedzy na temat danych wejściowych było naprawdę w duchu gry, więc poza wybraniem t jako zastępczego znaku po spacji ( i prawdopodobnie też nie powinienem był tego robić), unikałem tego. Zignorowałem znaną długość linii pliku wejściowego i zamiast tego wstawiłem
\n
co każde 13 spacji - jest to prawie na pewno bardzo słabe dopasowanie, głównym celem było utrzymanie rozsądnej długości linii, a nie dopasowanie do danych wejściowych.Kod nie jest dokładnie szybki (~ 2 godziny na mojej maszynie), ale ogólnie dostaje około połowy prawidłowych znaków (49%). Spodziewam się, że wynik byłby marginalnie lepszy, gdyby został uruchomiony
whale2.txt
, ale tego nie zrobiłem.Początek danych wyjściowych wygląda następująco:
ale pod koniec wygląda trochę bardziej jak ... coś. Mój ulubiony fragment z końca książki
wychodzi jako
To sprawiłoby, że Gniew Khana byłby znacznie bardziej zagmatwany. A „samotny” → „mrowienie” jest szczególnie satysfakcjonującym zamiennikiem.
Edycja: Zapisano jeden bajt, usuwając niepotrzebne miejsce
Punktacja
Spowoduje to uruchomienie programu dla tekstu Moby Dicka i wyprowadzenie „przewidywanego” tekstu na standardowe wyjście, i nadużywa stderr do napisania partytury. Polecam przekierowanie wyjścia do pliku.
źródło
lambda i:i[1]
byłoby taniej niż radzenie sobieoperator
?C ++, 2 62829 + 318786 = 444444
Aby uruchomić ten program, potrzebujesz tego pliku tutaj , który musi mieć nazwę
C
.Program wykorzystuje tę samą kombinację modeli Markowa, co w naszej wcześniejszej odpowiedzi . Tak jak poprzednio, ta kombinacja jest zasadniczo modelem z tej odpowiedzi użytkownika 2699 , ale z kilkoma małymi modyfikacjami.
Zważywszy na to, że ta odpowiedź wykorzystuje dokładnie ten sam model, co poprzednio, ulepszenie jest lepszym mechanizmem teoretycznym niż opisany wcześniej mechanizm „przewijania”. Pozwala to popełnić mniej błędów, a jednocześnie ma mniejszą łączną długość. Sam program nie jest bardzo golfowy, ponieważ nie jest głównym czynnikiem wpływającym na wynik.
Program ma długość 2167 bajtów (w tym wszystkie zakładki wcięcia i wiele innych niepotrzebnych znaków, ale przed kodem testowym), a plik binarny
C
ma długość 60661 bajtów, więc zgodnie z zasadami oceniania wielu plików oceniamyL
jako 2167 + 60661 + 1 = 62829.Uruchomienie programu na
m5.4xlarge
instancji w Amazon EC2 zajmuje około 8 minut i zużywa nieco ponad 16 GB pamięci. (To nadmierne zużycie pamięci nie jest konieczne - po prostu też tego nie zoptymalizowaliśmy).źródło
Python 3, 526640
274 bajty, 526092 błędy (za pomocą
whale2.txt
). To z pewnością jest w stanie poprawić, ale osiągnął etap „wystarczająco dobry, aby opublikować”.Chodzi o to, aby zapisać częstotliwości wszystkich przebiegów 2, 3, 4, ..., 10 znaków. Dla każdej z tych długości L sprawdzamy, czy najnowsze znaki L-1 pasują do zapisanego wzoru; jeśli tak, to zgadujemy, że L jest najczęstszą kolejną postacią stosowaną według tego wzoru. W ten sposób zbieramy do dziewięciu domysłów. Aby zdecydować, której zgadnąć użyć, ważymy częstotliwość każdego wzoru według jego długości do 8. potęgi. Wybrano przypuszczenie o największej sumie ważonych częstotliwości. Jeśli nie ma pasujących wzorów, domyślamy się miejsca.
(Maksymalna długość wzoru i wykładnik ważenia zostały wybrane metodą prób i błędów, aby dać najmniej błędnych domysłów.)
Oto moja niezakończona wersja w toku:
I uprząż testowa:
Oto przykładowe dane wyjściowe z początku tekstu. Już zaczynamy dostrzegać możliwości, aby zakończyć wspólne słowa po obejrzeniu swój pierwszy list (
in
,to
,and
,by
, również, jak widać,school
).Pod koniec wciąż jest mnóstwo błędów, ale także mnóstwo bardzo dobrych sekwencji (
shmage seashawks
na przykład).Interesujące jest spojrzenie na niektóre błędy i odgadnięcie, jakiego słowa algorytm „oczekiwał”. Na przykład, gdy
sail
program oba razy przewidujeo
--forsailor
, jak sądzę. Lub ponownie, gdy, a
się spodziewa -n
prawdopodobnie z powodu powszechnego występowania, and
.Dziennik zmian:
źródło
Python 2, wynik: 2 * (407 + 56574) + 562262 = 676224
Wyszukuje słowa pasujące do poprzednich znaków z listy
wszystkichwiększości słów użytych w tekście, posortowanych według liczby ich wystąpienia.Kod:
Dane: https://www.dropbox.com/s/etmzi6i26lso8xj/d?dl=0
Zestaw testowy:
Edycja: Używanie
whale2.txt
daje lepszy wynik.źródło
C ++ (GCC), 725 × 2 + 527076 = 528526
Jeszcze jedno przedłożenie częstotliwości. Uruchom
whale2.txt
i uzyskaj podobny (nieco gorszy) wynik niż inni.Ten łapczywie odnajduje najdłuższy ciąg, który zaczyna się od sufiksu historii, a jeśli jest wielu kandydatów, rozstaje się z krótszymi ciągami.
Na przykład: jeśli ostatnie 7 znaków to
abcdefgh
, a ciągabcdefghi
iabcdefghj
pojawia się z największą częstotliwością we wszystkich ciągach formularzaabcdefgh*
, wynikiem będzie albo,i
alboj
tiebreak z krótszymi przyrostkami (bcdefgh
,cdefgh
, ...).Z nieznanych przyczyn cokolwiek więcej niż 7, a mój komputer nie ma wystarczającej ilości pamięci RAM, aby go uruchomić. Nawet w przypadku wersji 7 muszę zamknąć wszystkie przeglądarki internetowe, aby go uruchomić.
Kod testowy:
Nie golfowany:
Przykładowe dane wyjściowe:
Ten jest pod koniec tekstu. Większość długie słowa są dość dokładnie przewidzieć (
intervals
,pivot-hole
,distance
)Wielkie litery nie wydają się dobre.
źródło
Python 2, 756837
Używa czegoś, co może być łańcuchem Markowa?
źródło
zlib.decompress('...')
Ocenia{'G?':' ', 'G;':' ','G"':' ',.......}
ia
jest słownikiem, który odwzorowuje od 2 znaków do 1 znaku. Zasadniczo wariant 2-charakter Steadybox za odpowiedź .xxd
,hexdump
,uuencode
, Lub podobnyHaskell, (1904 + 1621 + 208548 + 25646) * 2 + 371705 = 847143
Przykład:
Wykorzystuje trzy wstępnie obliczone pliki pomocnicze:
seqwords
zawiera 236 najczęstszych słów.wordsseq
zawiera skompresowany przez LZMA ciąg tych słów i dla wszystkich słów nie należących do 236 najczęściej, długość.dicttries
zawiera, dla każdej długości słowa, drzewo decyzyjne, które zawiera wszystkie pozostałe słowa. Z tych prób wpisy są wybierane na bieżąco.W ten sposób osiągamy znacznie niższy poziom błędu niż wszystkie inne schematy stratne; Niestety
wordsseq
plik jest wciąż zbyt duży, aby był konkurencyjny.Oto ukończona wersja, która tworzy pliki i wykonuje analizę:
źródło
C ++ (WIP), 1923 * 2 + 1017344 = 1021190
Na obecnym etapie rozwiązaniem jest WIP i dlatego nie jest golfem. Biorąc również pod uwagę fakt, że rzeczywisty rozmiar kodu nie ma prawie żadnego wpływu na wynik, pomyślałem, że najpierw opublikuję swoją odpowiedź, zanim zacznę ją mikrooptymalizować.
(Pełny kod dostępny tutaj: https://github.com/BrainStone/MobyDickRNG - Obejmuje pełne wyszukiwanie programu i nasion)
To rozwiązanie jest oparte na RNG. Najpierw analizuję tekst. Tworzę mapę, która liczy wystąpienia dwóch następujących po sobie postaci. Następnie tworzę mapę dystrybucji. Wszystko to odbywa się statycznie, więc powinno być zgodne z zasadami.
Następnie, próbując wydrukować tekst, sprawdzam i wyciągam losowy znak z możliwych. Chociaż zwykle daje to gorsze wyniki niż tylko wypisywanie najpopularniejszej następującej litery, mogą istnieć boga ziarna, które przyniosą lepsze wyniki. Dlatego ziarno jest mocno zakodowane. Obecnie szukam najlepszego ziarna. Zaktualizuję tę odpowiedź, gdy znajdę lepsze nasiona. Bądź na bieżąco!
Jeśli ktoś chce samodzielnie szukać nasion lub korzystać z różnych RNG, nie wahaj się rozwiązywać repozytorium.
Metoda zastosowana do obliczenia wyniku: https://github.com/BrainStone/MobyDickRNG/blob/master/src/search.cpp#L15
Zauważ, że chociaż całkowity wynik jest w tej chwili najgorszy, to przewyższa liczbę błędów po prostu wypisując spacje. Są duże szanse, że wynik spadnie, sprawdzając więcej nasion.
Dziennik zmian
Nasiona w kratkę: 0-50000. Wynik: 2305 * 2 + 1017754 = 1022364
Nasiona w kratkę: 0–80000. Wynik: 1920 * 2 + 1017754 = 1021594 (-770)
Sprawdzone nasiona: 0-32000000. Wynik: 1923 * 2 + 1017344 = 1021190 (-404)
źródło
Ruby, 1164418 (ouch)
Chciałem tylko zobaczyć, jak sobie radzę bez sprawdzania innych odpowiedzi.
Nie jestem pewien, czy jest to dozwolone, ponieważ zawiera literał, który wygenerowałem analizując plik, ale nawet jeśli tak nie było, groziło to komukolwiek.
Jak wygenerowałem
x
Najpierw wygenerowałem
a.txt
z następującymi:Następnie wygenerowałem
a.csv
:Następnie przeanalizowałem go
x
za pomocą następującego skryptu Ruby:Jak zdobyłem
źródło
Python 3 , (146 * 2 + 879757) 880049 bajtów
Wypróbuj online!
Dość prosta tabela częstotliwości. Każda pozycja w ciągu odpowiada kodowi ascii bieżącego znaku (minus 10 = 0x0a = „\ n”, najniższy znak w pliku), a znak przy każdym indeksie jest najczęstszym następnym znakiem. Zakładając, że poprawnie wyliczyłem częstotliwości…
Przetestowano za pomocą kodu z testu user202729
źródło
def f(c):return(" ">c)*c or"t ... e"[ord(c)-32]
?[Python 3] (644449 * 2 + 0) 1288898 punktów
Doskonała dokładność w zaledwie 644449 bajtów
Pełny kod nie mieści się w odpowiedzi, więc umieściłem go tutaj i zastąpiłem literał dużego ciągu binarnego literą b '###' w tekście odpowiedzi.
Jest on generowany za pomocą następującego kodu, w którym „zmodyfikowany.py” to wygenerowany plik, a „cheatsheet.txt” to plik whale2.txt rozpoczynający się od drugiego znaku.
Kod można wykonać, dodając następujący tekst na końcu „Modified.py”. „whale2.txt” musi znajdować się w tym samym katalogu co „zmodyfikowany.py”, a dane wyjściowe zostaną zapisane w „out.txt”.
Ta odpowiedź nie zapewnia bezpośredniego dostępu do pliku whale.txt lub whale2.txt. Korzysta z istniejących standardowych bibliotek kompresji, co jest wyraźnie dozwolone w regułach.
źródło