Jelly , 309 byte nella codifica di Jelly
“Æ÷“¥s“ɲ“¡µ’;“ịƊ⁴çNṂ‘_\
OḌ;¢*5$%¥/µ“+⁷ż!¤ña¡jIȧƁfvḶg/Ọ=^ƝĠ0Ẇƭ³½N~=.Ɗ°ɗẇ⁵\ɦ*ɠPf⁾?ṾHḣ 2=⁹ƒ!©ƊĠṣƥ®Ƙ0Yƙ>!ȧtƊN0w,$ɠẎ46fẋ⁷(ṣẆm⁾ŻƓṫµsçwṣḂḲd0Ruṛ’ḃ21+\iµØW“&;:' ”;“¡3ȧ%⁾xƑ?{Ñṃ;Ċ70|#%ṭdṃḃ÷ƑĠẏþḢ÷ݳȦṖcẇọqƁe ʠ°oḲVḲ²ụċmvP[ỴẊẋ€kṢ ȯḂ;jɓỴẏeṾ⁴ḳḢ7Ẓ9ġƤṙb€xÇ4ɗ⁻>Ẉm!Ƈ)%Ḃẇ$ġ£7ȧ`ỵẈƘɗ¡Ṃ&|ƙƥ³ẏrṛbḋƙċ⁻ṁƲRṀẹṾ<ñ⁻Ṅ7j^ɓĊ’b58¤ị;0ị@
ḲÇ€t0”@;Ṫ
Provalo online!
Ho deciso che era ora di provare la mia sfida. L'uso di Jelly (e la sua tabella codici a 8 bit) mi dà un vantaggio del 12,5% rispetto alle lingue solo ASCII, e Jelly è conveniente per questa sfida a causa della presenza di operatori di conversione di base integrati con nomi brevi, ma la maggior parte dei risparmi sono dovuti a un migliore algoritmo di compressione (questo programma media in media meno di un byte per tipo di mostro).
Algoritmo e spiegazione
Classificazione basata su parole
Ho deciso che per ottenere un buon punteggio era necessario sfruttare maggiormente la struttura dell'input rispetto ad altre voci. Una cosa molto evidente è che molti mostri hanno nomi della forma " specie aggettivale "; a e a sono entrambi i tipi di drago e quindi appaiono come . Alcuni altri mostri hanno nomi della forma " specie di lavoro ", come ad esempio ; essendo un tipo di orco, questo appare come . Le questioni complicate sono i non morti; a è sia un coboldo che uno zombi, e quest'ultimo stato ha la precedenza nella denominazione dei mostri di NetHack, quindi vorremmo classificarlo come .red dragon
blue dragon
D
orc shaman
o
kobold zombie
Z
Come tale, ho classificato le parole che compaiono nei nomi dei mostri come segue: un indicatore è una parola che suggerisce fortemente la classe di mostri appropriata (ad esempio, sphere
suggerisce fortemente che il mostro è in classe e
); una parola ambigua è una parola che rende molto meno un suggerimento ( lord
non ti dice molto), e tutte le altre parole sono non parole a cui non ci interessa. L'idea di base è che guardiamo le parole nel nome del mostro dalla fine all'indietro all'inizio e scegliamo il primo indicatore che vediamo. Pertanto, era necessario assicurarsi che ciascun nome di mostro contenesse almeno un indicatore, seguito interamente da parole ambigue. Come eccezione, le parole che compaiono nei nomi dei mostri che sembrano un@
(il gruppo più numeroso) sono tutti classificati come ambigui. Tutto può apparire davanti a un indicatore; per esempio, i nomi dei colori (come red
) appaiono sempre prima in un nome rispetto a un indicatore, e quindi sono considerati non parole (poiché non vengono mai esaminati durante la determinazione dell'identità di un mostro).
Alla fine, questo programma si riduce a una tabella hash, come fanno gli altri programmi. Tuttavia, la tabella non contiene voci per tutti i nomi dei mostri o per tutte le parole che compaiono nei nomi dei mostri; piuttosto, contiene solo gli indicatori. Gli hash di parole ambigue non compaiono nella tabella, ma devono essere assegnati a slot vuoti (il tentativo di cercare una parola ambigua risulterà sempre vuoto). Per le non-parole, non importa se la parola appare nella tabella o meno, o se l'hash si scontra o meno, perché non usiamo mai il valore di cercare una non-parola. (La tabella è piuttosto scarsa, quindi la maggior parte delle non-parole non appare nella tabella, ma alcuni, come ad esempio flesh
, si trovano nella tabella come conseguenza delle collisioni di hash.)
Ecco alcuni esempi di come funziona questa parte del programma:
woodchuck
è una singola parola lunga (quindi un indicatore) e la ricerca della tabella su woodchuck
ci dà la risposta desiderata r
.
abbot
è anche una sola parola, ma sembra un @
. Come tale, abbot
è considerata una parola ambigua; la ricerca della tabella appare vuota e restituiamo una risposta @
per impostazione predefinita.
vampire lord
è costituito da un indicatore ( vampire
corrispondente a V
) e una parola ambigua ( lord
, che non è presente nella tabella). Ciò significa che controlliamo entrambe le parole (in ordine inverso), quindi forniamo la risposta corretta di V
.
gelatinous cube
è costituito da una non parola ( gelatinous
, corrispondente a H
causa di una collisione dell'hash) e da un indicatore ( cube
, corrispondente a b
). Dato che prendiamo solo l'ultima parola trovata nella tabella, questo ritorna b
, come previsto.
gnome mummy
è costituito da due indicatori, gnome
corrispondenti G
e mummy
corrispondenti a M
. Prendiamo l'ultimo indicatore e otteniamo M
, che è quello che vogliamo.
Il codice per gestire la classificazione basata su parole è l'ultima riga del programma Jelly. Ecco come funziona:
ḲÇ€t0”@;Ṫ
Ḳ Split on spaces
Ç€ Call function 2 (table lookup) on each entry
t0 Remove trailing zeroes (function 2 returns 0 to mean "not found")
”@; Prepend an @ character
Ṫ Take the last result
Esistono due casi reali; se l'input è costituito interamente da parole ambigue, t0
elimina l'intero output delle ricerche della tabella e otteniamo un @
risultato per impostazione predefinita; se ci sono indicatori nell'input, t0
cancellerà qualcosa a destra dell'indicatore più a destra e Ṫ
ci darà il risultato corrispondente per quell'indicatore.
Compressione della tabella
Naturalmente, rompere l'input in parole non risolve il problema da solo; dobbiamo ancora codificare la corrispondenza tra gli indicatori e le corrispondenti classi di mostri (e la mancanza di corrispondenza da parole ambigue). Per fare questo, ho costruito una tabella sparsa con 181 voci utilizzate (corrispondenti ai 181 indicatori; questo è un grande miglioramento rispetto ai 378 mostri!) E 966 voci totali (corrispondenti ai 966 valori di output della funzione hash). La tabella è codificata nel suo programma tramite l'uso di due stringhe: la prima stringa specifica le dimensioni degli "spazi" nella tabella (che non contengono voci); e la seconda stringa specifica la classe di mostri che corrisponde a ciascuna voce. Entrambi sono rappresentati in modo conciso tramite la conversione di base.
Nel programma Jelly, il codice per la ricerca della tabella, insieme al programma stesso, è rappresentato nella seconda riga, dalla prima in µ
poi. Ecco come funziona questa parte del programma:
“…’ḃ21+\iµØW“&;:' ”;“…’b58¤ị;0ị@
“…’ Base 250 representation of the gap sizes
ḃ21 Convert to bijective base 21
+\ Cumulative sum (converts gaps to indexes)
i Find the input in this list
µ Set as the new default for missing arguments
ØW Uppercase + lowercase alphabets (+ junk we ignore)
“&;:' ”; Prepend "&;:' "
“…’ Base 250 representation of the table entries
b58 Convert to base 58
¤ Parse the preceding two lines as a unit
i Use the table to index into the alphabets
;0 Append a zero
i@ Use {the value as of µ} to index into the table
La base biiettiva 21 è come la base 21, tranne per il fatto che 21 è una cifra legale e 0 non lo è. Questa è una codifica più conveniente per noi perché contiamo due voci adiacenti con uno spazio di 1, in modo da poter trovare gli indici validi tramite somma cumulativa. Quando si tratta della parte della tabella che contiene i valori, abbiamo 58 valori univoci, quindi prima decodifichiamo in 58 numeri interi consecutivi, quindi decodifichiamo nuovamente usando una tabella di ricerca che li mappa nei caratteri effettivi utilizzati. (La maggior parte di queste sono lettere, quindi iniziamo questa tabella di ricerca secondaria con le voci senza lettere, &;:'
quindi aggiungiamo semplicemente una costante Jelly che inizia con gli alfabeti maiuscoli e minuscoli; ha anche qualche altra spazzatura ma non ci interessa riguardo a questo.)
Il valore sentinella "indice non trovato" di Jelly, se lo si utilizza per indicizzare in un elenco, restituisce l'ultimo elemento dell'elenco; così, ho aggiunto uno zero (uno zero intero, anche se la tabella è per lo più composta da caratteri) alla tabella di ricerca per dare una sentinella più appropriata per indicare una voce mancante.
Funzione hash
La parte rimanente del programma è la funzione hash. Questo inizia abbastanza semplicemente, conOḌ
; questo converte la stringa di input nei suoi codici ASCII, quindi calcola l'ultimo codice, più 10 volte il penultimo codice, più 100 volte il codice precedente e così via (questo ha una rappresentazione molto breve in Jelly perché è più comunemente usato come un stringa → funzione di conversione intera). Tuttavia, se semplicemente riducessimo direttamente questo hash tramite un'operazione di modulo, finiremmo per avere bisogno di una tabella piuttosto grande. Quindi, invece, inizio con una catena di operazioni per ridurre la tabella. Ciascuno di essi funziona così: prendiamo la quinta potenza dell'attuale valore di hash; quindi riduciamo il valore modulo una costante (quale costante dipende dall'operazione che stiamo usando). Questa catena offre più risparmi (in termini di riduzione delle dimensioni della tabella risultante) rispetto a quanto costa (in termini di necessità di codificare la catena di operazioni stessa), in due modi: può rendere la tabellamolto più piccolo (966 anziché 3529 voci) e l'uso di più stadi offre maggiori opportunità di introdurre collisioni benefiche (ciò non è accaduto molto, ma esiste una di queste collisioni: entrambe Death
e Yeenoghu
hash a 806, permettendoci così di rimuoverne una voce dal tavolo, mentre entrambi vanno a&
). I moduli qui utilizzati sono [3529, 2163, 1999, 1739, 1523, 1378, 1246, 1223, 1145, 966]. Per inciso, la ragione per elevare alla quinta potenza è che se si prende semplicemente direttamente il valore, gli spazi tendono a rimanere delle stesse dimensioni, mentre l'esponenziazione sposta gli spazi intorno e può consentire alla tabella di essere distribuita più uniformemente dopo catena piuttosto che rimanere bloccati in un minimo locale (lacune distribuite in modo più uniforme consentono una codifica più terser delle dimensioni del gap). Questo deve essere un potere dispari al fine di prevenire il fatto che x ² = (- x ) ² introducendo collisioni e 5 funzionino meglio di 3.
La prima riga del programma codifica la sequenza dei moduli utilizzando la codifica delta:
“…’;“…‘_\
“…’ Compressed integer list encoding, arbitrary sized integers
; Append
“…‘ Compressed integer list encoding, small integers (≤ 249)
_\ Take cumulative differences
Il resto del programma, l'inizio della seconda riga, implementa la funzione hash:
OḌ;¢*5$%¥/
O Take ASCII codepoints
Ḍ "Convert from decimal", generalized to values outside the range 0-9
;¢ Append the table of moduli from the previous line
/ Then reduce by:
*5$ raising to the power 5 (parsing this as a group)
%¥ and modulusing by the right argument (parsing this as a group, too).
Verifica
Questo è lo script Perl che ho usato per verificare che il programma funzioni correttamente:
use warnings;
use strict;
use utf8;
use IPC::Run qw/run/;
my %monsters = ("Aleax", "A", "Angel", "A", "Arch Priest", "@", "Archon", "A",
"Ashikaga Takauji", "@", "Asmodeus", "&", "Baalzebub", "&", "Chromatic Dragon",
"D", "Croesus", "@", "Cyclops", "H", "Dark One", "@", "Death", "&", "Demogorgon",
"&", "Dispater", "&", "Elvenking", "@", "Famine", "&", "Geryon", "&",
"Grand Master", "@", "Green-elf", "@", "Grey-elf", "@", "Hippocrates", "@",
"Ixoth", "D", "Juiblex", "&", "Keystone Kop", "K", "King Arthur", "@",
"Kop Kaptain", "K", "Kop Lieutenant", "K", "Kop Sergeant", "K", "Lord Carnarvon",
"@", "Lord Sato", "@", "Lord Surtur", "H", "Master Assassin", "@", "Master Kaen",
"@", "Master of Thieves", "@", "Medusa", "@", "Minion of Huhetotl", "&",
"Mordor orc", "o", "Nalzok", "&", "Nazgul", "W", "Neferet the Green", "@", "Norn",
"@", "Olog-hai", "T", "Oracle", "@", "Orcus", "&", "Orion", "@", "Pelias", "@",
"Pestilence", "&", "Scorpius", "s", "Shaman Karnov", "@", "Thoth Amon", "@",
"Twoflower", "@", "Uruk-hai", "o", "Vlad the Impaler", "V", "Wizard of Yendor",
"@", "Woodland-elf", "@", "Yeenoghu", "&", "abbot", "@", "acid blob", "b",
"acolyte", "@", "air elemental", "E", "aligned priest", "@", "ape", "Y",
"apprentice", "@", "arch-lich", "L", "archeologist", "@", "attendant", "@",
"baby black dragon", "D", "baby blue dragon", "D", "baby crocodile", ":",
"baby gray dragon", "D", "baby green dragon", "D", "baby long worm", "w",
"baby orange dragon", "D", "baby purple worm", "w", "baby red dragon", "D",
"baby silver dragon", "D", "baby white dragon", "D", "baby yellow dragon", "D",
"balrog", "&", "baluchitherium", "q", "barbarian", "@", "barbed devil", "&",
"barrow wight", "W", "bat", "B", "black dragon", "D", "black light", "y",
"black naga hatchling", "N", "black naga", "N", "black pudding", "P",
"black unicorn", "u", "blue dragon", "D", "blue jelly", "j", "bone devil", "&",
"brown mold", "F", "brown pudding", "P", "bugbear", "h", "captain", "@",
"carnivorous ape", "Y", "cave spider", "s", "caveman", "@", "cavewoman", "@",
"centipede", "s", "chameleon", ":", "chickatrice", "c", "chieftain", "@",
"clay golem", "'", "cobra", "S", "cockatrice", "c", "couatl", "A", "coyote", "d",
"crocodile", ":", "demilich", "L", "dingo", "d", "disenchanter", "R", "djinni",
"&", "dog", "d", "doppelganger", "@", "dust vortex", "v", "dwarf king", "h",
"dwarf lord", "h", "dwarf mummy", "M", "dwarf zombie", "Z", "dwarf", "h",
"earth elemental", "E", "electric eel", ";", "elf mummy", "M", "elf zombie", "Z",
"elf", "@", "elf-lord", "@", "energy vortex", "v", "erinys", "&", "ettin mummy",
"M", "ettin zombie", "Z", "ettin", "H", "fire ant", "a", "fire elemental", "E",
"fire giant", "H", "fire vortex", "v", "flaming sphere", "e", "flesh golem", "'",
"floating eye", "e", "fog cloud", "v", "forest centaur", "C", "fox", "d",
"freezing sphere", "e", "frost giant", "H", "gargoyle", "g", "garter snake", "S",
"gas spore", "e", "gecko", ":", "gelatinous cube", "b", "ghost", " ", "ghoul",
"Z", "giant ant", "a", "giant bat", "B", "giant beetle", "a", "giant eel", ";",
"giant mimic", "m", "giant mummy", "M", "giant rat", "r", "giant spider", "s",
"giant zombie", "Z", "giant", "H", "glass golem", "'", "glass piercer", "p",
"gnome king", "G", "gnome lord", "G", "gnome mummy", "M", "gnome zombie", "Z",
"gnome", "G", "gnomish wizard", "G", "goblin", "o", "gold golem", "'",
"golden naga hatchling", "N", "golden naga", "N", "gray dragon", "D", "gray ooze",
"P", "gray unicorn", "u", "green dragon", "D", "green mold", "F", "green slime",
"P", "gremlin", "g", "grid bug", "x", "guard", "@", "guardian naga hatchling",
"N", "guardian naga", "N", "guide", "@", "healer", "@", "hell hound pup", "d",
"hell hound", "d", "hezrou", "&", "high priest", "@", "hill giant", "H",
"hill orc", "o", "hobbit", "h", "hobgoblin", "o", "homunculus", "i",
"horned devil", "&", "horse", "u", "housecat", "f", "human mummy", "M",
"human zombie", "Z", "human", "@", "hunter", "@", "ice devil", "&", "ice troll",
"T", "ice vortex", "v", "iguana", ":", "imp", "i", "incubus", "&", "iron golem",
"'", "iron piercer", "p", "jabberwock", "J", "jackal", "d", "jaguar", "f",
"jellyfish", ";", "ki-rin", "A", "killer bee", "a", "kitten", "f", "knight", "@",
"kobold lord", "k", "kobold mummy", "M", "kobold shaman", "k", "kobold zombie",
"Z", "kobold", "k", "kraken", ";", "large cat", "f", "large dog", "d",
"large kobold", "k", "large mimic", "m", "leather golem", "'", "lemure", "i",
"leocrotta", "q", "leprechaun", "l", "lich", "L", "lichen", "F", "lieutenant",
"@", "little dog", "d", "lizard", ":", "long worm", "w", "lurker above", "t",
"lynx", "f", "mail daemon", "&", "manes", "i", "marilith", "&", "master lich",
"L", "master mind flayer", "h", "mastodon", "q", "mind flayer", "h", "minotaur",
"H", "monk", "@", "monkey", "Y", "mountain centaur", "C", "mountain nymph", "n",
"mumak", "q", "nalfeshnee", "&", "neanderthal", "@", "newt", ":", "ninja", "@",
"nurse", "@", "ochre jelly", "j", "ogre king", "O", "ogre lord", "O", "ogre", "O",
"orange dragon", "D", "orc mummy", "M", "orc shaman", "o", "orc zombie", "Z",
"orc", "o", "orc-captain", "o", "owlbear", "Y", "page", "@", "panther", "f",
"paper golem", "'", "piranha", ";", "pit fiend", "&", "pit viper", "S",
"plains centaur", "C", "pony", "u", "priest", "@", "priestess", "@", "prisoner",
"@", "purple worm", "w", "pyrolisk", "c", "python", "S", "quantum mechanic", "Q",
"quasit", "i", "queen bee", "a", "quivering blob", "b", "rabid rat", "r",
"ranger", "@", "raven", "B", "red dragon", "D", "red mold", "F",
"red naga hatchling", "N", "red naga", "N", "rock mole", "r", "rock piercer", "p",
"rock troll", "T", "rogue", "@", "rope golem", "'", "roshi", "@", "rothe", "q",
"rust monster", "R", "salamander", ":", "samurai", "@", "sandestin", "&",
"sasquatch", "Y", "scorpion", "s", "sergeant", "@", "sewer rat", "r", "shade", " ",
"shark", ";", "shocking sphere", "e", "shopkeeper", "@", "shrieker", "F",
"silver dragon", "D", "skeleton", "Z", "small mimic", "m", "snake", "S",
"soldier ant", "a", "soldier", "@", "spotted jelly", "j", "stalker", "E",
"steam vortex", "v", "stone giant", "H", "stone golem", "'", "storm giant", "H",
"straw golem", "'", "student", "@", "succubus", "&", "tengu", "i", "thug", "@",
"tiger", "f", "titan", "H", "titanothere", "q", "tourist", "@", "trapper", "t",
"troll", "T", "umber hulk", "U", "valkyrie", "@", "vampire bat", "B",
"vampire lord", "V", "vampire", "V", "violet fungus", "F", "vrock", "&", "warg",
"d", "warhorse", "u", "warrior", "@", "watch captain", "@", "watchman", "@",
"water demon", "&", "water elemental", "E", "water moccasin", "S", "water nymph",
"n", "water troll", "T", "werejackal", "d", "wererat", "r", "werewolf", "d",
"white dragon", "D", "white unicorn", "u", "winged gargoyle", "g",
"winter wolf cub", "d", "winter wolf", "d", "wizard", "@", "wolf", "d",
"wood golem", "'", "wood nymph", "n", "woodchuck", "r", "wraith", "W", "wumpus",
"q", "xan", "x", "xorn", "X", "yellow dragon", "D", "yellow light", "y",
"yellow mold", "F", "yeti", "Y", "zruty", "z");
for my $monster (sort keys %monsters) {
run ["./jelly", "fu", "monsters.j", $monster], \ "", \my $out;
print "$monster -> \"$out\" (",
($out ne $monsters{$monster} ? "in" : ""), "correct)\n";
}
mail daemon
> _ <