Aiutami a riconoscere il mio mostro


35

sfondo

Il gioco per computer NetHack risale al 1987, prima che l'uso della grafica nei giochi per computer fosse ampiamente affermato. Ci sono molti mostri nel gioco, e potenzialmente molti hanno bisogno di adattarsi allo schermo contemporaneamente, quindi i mostri sono disegnati in un modo molto minimale: un mostro è semplicemente disegnato come un personaggio ASCII sullo schermo.

Oltre al fatto che ci sono molti mostri, ci sono molti tipi di mostri. Può essere importante sapere quale è quale; dovresti reagire diversamente vedendo un gattino e vedendo un drago. Come tale, la maggior parte di ASCII è usata per rappresentare mostri; per esempio, un gattino lo è fe un drago rosso lo è D. Ciò significa che può essere molto utile sapere come apparirà un dato mostro, poiché ti aiuterà a riconoscerlo se lo incontrerai più avanti nel gioco. (Nota che ci sono più tipi di mostri di quanti siano i caratteri ASCII, quindi alcuni condividono; un drago rosso e un drago blu sono entrambi D.)

Compito

Il tuo programma deve prendere il nome di un mostro NetHack come input e produrre il carattere ASCII che lo rappresenta all'interno del gioco come output. Il programma può supporre che l'ingresso sia in effetti il ​​nome di un mostro NetHack; può, se desidera, arrestarsi, produrre risultati insignificanti, ecc. se l'input non è valido.

Il seguente frammento di stack è un oggetto JSON che fornisce il mapping completo dei possibili input ai relativi output:

Quindi, in sostanza, l'attività qui è "data una chiave nel dizionario rappresentato dall'oggetto JSON sopra, restituisce il valore corrispondente".

Si noti che questa sfida è, in un certo senso, una inversa ; invece di passare da un input piccolo / vuoto a un output di grandi dimensioni, si passa da un input di grandi dimensioni a un output di piccole dimensioni. (Ci sono quindi molte informazioni ridondanti nell'input, che puoi ignorare o usare come desideri). È anche abbastanza simile al regex golf, tranne per il fatto che a) è consentita qualsiasi lingua (non solo regex) eb) ci sono più di due possibili output. (Abbiamo avuto alcune attività come questa prima, come queste due , ma questa attività è leggermente diversa perché il comportamento di input / output specificato ha schemi più forti).

chiarimenti

È possibile utilizzare qualsiasi formato di input e output ragionevole (ad esempio, è possibile produrre l'output come carattere o come codice ASCII o come stringa lunga un carattere). Se preferisci, puoi inviare una funzione anziché un programma completo.

Questo è già menzionato nelle scappatoie standard, ma solo per ribadirlo: non è possibile memorizzare la corrispondenza tra input e output in qualsiasi luogo diverso dal codice sorgente del programma. Questa sfida consiste essenzialmente nel rappresentare il comportamento di input / output nello spazio più piccolo possibile, quindi non devi fare cose come scaricare un elenco da Internet, archiviare la corrispondenza in un file esterno, avviare NetHack in modalità debug e generare il mostro in questione per vedere come appare, ecc. (Inoltre, non voglio combattere i mostri per testare i tuoi invii.)

Condizione di vittoria

Questo è , quindi la voce vincente sarà la più corta, conteggiata in byte. In bocca al lupo!


6
mail daemon> _ <
briantist

1
Suggerimento: forse puoi anche organizzare la lista dei mostri in ordine secondo il simbolo ASCII che rappresentano
Kritixi Lithos

2
Sospiro - è stato un gran bel gioco, quelli erano i giorni ...
GreenAsJade

2
@GreenAsJade è ancora un bel gioco! In effetti una nuova versione è stata rilasciata pochi mesi fa dopo alcuni anni di inattività
nmjcman101,

1
È apparso un selvaggio BROWN PUDDING !!
Magic Octopus Urn

Risposte:


11

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 dragonblue dragonD orc shamanokobold zombieZ

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, spheresuggerisce fortemente che il mostro è in classe e); una parola ambigua è una parola che rende molto meno un suggerimento ( lordnon 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 woodchuckci 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 ( vampirecorrispondente 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 Hcausa 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, gnomecorrispondenti Ge mummycorrispondenti 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, t0elimina l'intero output delle ricerche della tabella e otteniamo un @risultato per impostazione predefinita; se ci sono indicatori nell'input, t0cancellerà 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 Deathe Yeenoghuhash 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";
}

10

JavaScript (ES6), 915 ... 902 890 byte

w=>[..."aZM?o@;LWu&P?D@zF@W: @aT&@nCEfvQ&R&Tb'b@&p@:Srn @ahlrdpdT'TRv:HUYG@&fSfYdG&SGHL@Mh@G@gs';@CS@km@OsirA@q@njOZS@O@';HYqHE&DJavq&&aYaBmZMf;bv@EqHg@Z@;dm@M@?@rs@d@@oDAosDT@d@ZeBVrq@jFooD@VV&&BvMEDKiuiPC@&@DYrD&eD@D@@:AwccKZiF:DKLXAwdL@w&@@u'Hc@@q&;D:::WjdN@N@xD&eFh@gh@&Md?&Ye@@&h@hNN'Z&qtKEd@@HtH&@'@&@xd&dZsv@oo@FDyd@@&&@&@HS'Hw?DF@@@MPfDfi'AH&@@pkZkuMyZhFNN'P?d@u@NN&B@uo'fdi@?ke&"].find((_,i)=>!(s-=`GD4~#_@'R<1*~7C7RbZ6F'"Sa&!*1),#''3'.+B6(K$.l%9&!#0@51""~/+!gaW!/.(5'-@0'';!%C.&""!-.$16.2>(#&g!!O,#8A50O!)*(9b|Z4@7V).;*A*HWO(g1$/*-4&SL1I#K$#"3"#=e/'V~4'B(*,.3),$@D3)*76-"\\&kL7(-4#=7!!#+(B/B!-%!"_+!")+)0$1:E84!L191&)(255)!3O<,90NN6&;Q2'"bO=*h7.%1![<o!%M'G5/R.0$-J*%\\~6T?>)16""L&!X94T4"3$!2'^070Y2a"##)#"&n&(+1*&!-M""73R5%'y0~$-6<".MV?+1*ED>!B6b!)%&)8.+$&X0~Q'E%8&#%S/H.1<#>~!sU`.charCodeAt(i)-32),w=w.replace(/\W/g,1),s=parseInt((w+=w+w)[0]+w[2]+w[3]+w[6]+[...w].pop(),36)%8713)

formattato

Di seguito è riportata una versione formattata del codice con dati di payload troncati.

w => [..."aZM(…)"].find(
  (_, i) =>
    !(s -= `GD4(…)`.charCodeAt(i) - 32),
    w = w.replace(/\W/g, 1),
    s = parseInt((w += w + w)[0] + w[2] + w[3] + w[6] + [...w].pop(), 36) % 8713
)

Come funziona

Passo 1

Per prima cosa riduciamo il nome del mostro di:

  1. Sostituzione di caratteri non alfanumerici (spazi e trattini) con 1's.
  2. Ripetendo questa stringa 3 volte per assicurarsi che abbiamo abbastanza caratteri con cui lavorare per il passaggio successivo.
  3. Mantenere solo il 1o, 3o, 4o, 7o e ultimo personaggio.

Esempi:

1.34..7..L
Demogorgon -> Dmorn
^ ^^  ^  ^

             1.34..7.L
orc mummy -> orc1mummy -> oc1my
             ^ ^^  ^ ^

        1.34..7....L
xorn -> xornxornxorn -> xrnrn
        ^ ^^  ^    ^

Questo porta ad alcune collisioni. Ad esempio, "Master Assassin"e "Master Kaen"sono entrambi ridotti a "Mst1n". Fortunatamente, tutti i nomi di mostri in collisione condividono lo stesso simbolo ( @in questo caso).

Passo 2

Quindi, interpretiamo questa stringa di 5 caratteri come una quantità di base 36 per convertirla in decimale (questa operazione non fa distinzione tra maiuscole e minuscole) e applichiamo un modulo 8713, che è stato scelto empiricamente per produrre un elenco di chiavi privo di collisioni.

Esempi:

Dmorn --[from base 36]--> 22893539 --[MOD 8713]--> 4488
oc1my --[from base 36]--> 40872778 --[MOD 8713]--> 95
xrnrn --[from base 36]--> 56717843 --[MOD 8713]--> 4926

Passaggio n. 3

Tutte le chiavi sono ordinate in ordine crescente:

[ 39, 75, 95, 192, 255, 287, 294, 344, 372, 389, 399, 516, 551, 574, 624, ..., 8635, 8688 ]

Convertito in valori delta:

[ 39, 36, 20, 97, 63, 32, 7, 50, 28, 17, 10, 117, 35, 23, 50, ..., 83, 53 ]

E codificato come caratteri ASCII nell'intervallo [ 32, 126 ]. Alcuni valori fittizi intermedi vengono inseriti quando la differenza tra due tasti consecutivi supera la grandezza massima codificabile.

Infine, l'elenco delle chiavi viene mappato su un elenco di simboli disposti nello stesso ordine.

Test


Secondo la propria suite di test, questo classifica erroneamente 5 voci. Non ho studiato per vedere cosa li sta causando, ma probabilmente è necessario risolverlo.

@ ais523 Questo è quello che ottengo per averlo testato solo in Firefox. Proverò a risolverlo per (almeno) Chrome.
Arnauld,

2
@ ais523 Ora dovrebbe funzionare bene su FF, Chrome & Edge. Mi dispiace per quello.
Arnauld

Ho avuto l'idea di estrarre bit dai nomi convertiti in numeri, ma ovviamente stavo pensando troppo in piccolo. Congratulazioni!
Jonas Schäfer,

8

Java, 1130 byte

import java.util.*;class G{public static void main(String[]u){BitSet s=BitSet.valueOf(Base64.getDecoder().decode("D94FuoCWYEIhCTEgLWwRNU/CMB1cE7XBhxBsBCusihaASRg14IJpQMOIDJdFx3BOdDcmThdhILVkCgGsEmhII8UE+SB4kDYEEJzw7Tw54oUEQZe0AUHCACH6nAdqgiZgJhASCIPAEAzJBmuMIrBCHE8IiFjgKQwrN4/90B4QFaLBQBEwTArRBMLCLHQOUQs7ZXZ8B8uGC1EbeAMJBdihUDgCIwGUEKgEAu4W2SJkIAhzB1IQSHgNiEAwABQECV5BvAB7eizABXxFLEg5iMA3whhAFXOKHXEURB7UA7PQjgUK7sji8CmIC0FJsTB4tAMFgiARB3hOJATDsBkgGKnGmWIiIWBRwkMgToQJ49G8gTR4IqcB4vJwDBHSVBLQhpwHsUFipqBcWWaEsCBoGBF0AlNAE305HAfdU1AEbELBO0EERAfkmMkgZcEXDIa4MAp4HcENmYAMBB7UBbTwBqQPSMS9kVkEBMhCudAqBAKaR1CzZggDRw8WMAh0FQPEyKAsRAxzBwn0grwDMQMyQMdADRtFUBAsBQetRRBwcUgrlsQ1IkosBc9B6iBcjAkSDDKgEAQ1wgLIMEEwMkYB42ERBCdiEJMAt1wYSIAQkdIEI0UPNhALsDnRQ1AT/HQi1AyCEwiICOICpiAPlB8MwxnBPIk6JYaIgDy8NJHDsiAqzK0JAXpQPXgPLwJuEEbMTAGBYlQbDESvAXJAAQ=="));int i,j,k,c,d,h=u[0].hashCode(),a=(h&4092)>>2|(h>>5)&1024|(h>>7)&2048|(h>>9)&4096;char r='@';for(i=k=0;i<4297;i+=14){for(c=0,j=7;j>=0;j--)c+=c+(s.get(i+j)?1:0);if((k+=c)==a){for(d=0,j=13;j>=8;j--)d+=d+(s.get(i+j)?1:0);r=d<5?" &':;".charAt(d):(char)((d<31?60:66)+d);}}System.out.println(r);}}

Ungolfed:

import java.util.*;

class G {
    public static void main(String[] u) {
        BitSet s = BitSet.valueOf(Base64.getDecoder().decode(
                "D94FuoCWYEIhCTEgLWwRNU/CMB1cE7XBhxBsBCusihaASRg14IJpQMOIDJdFx3BOdDcmThdhILVkCgGsEmhII8UE+SB4kDYEEJzw7Tw54oUEQZe0AUHCACH6nAdqgiZgJhASCIPAEAzJBmuMIrBCHE8IiFjgKQwrN4/90B4QFaLBQBEwTArRBMLCLHQOUQs7ZXZ8B8uGC1EbeAMJBdihUDgCIwGUEKgEAu4W2SJkIAhzB1IQSHgNiEAwABQECV5BvAB7eizABXxFLEg5iMA3whhAFXOKHXEURB7UA7PQjgUK7sji8CmIC0FJsTB4tAMFgiARB3hOJATDsBkgGKnGmWIiIWBRwkMgToQJ49G8gTR4IqcB4vJwDBHSVBLQhpwHsUFipqBcWWaEsCBoGBF0AlNAE305HAfdU1AEbELBO0EERAfkmMkgZcEXDIa4MAp4HcENmYAMBB7UBbTwBqQPSMS9kVkEBMhCudAqBAKaR1CzZggDRw8WMAh0FQPEyKAsRAxzBwn0grwDMQMyQMdADRtFUBAsBQetRRBwcUgrlsQ1IkosBc9B6iBcjAkSDDKgEAQ1wgLIMEEwMkYB42ERBCdiEJMAt1wYSIAQkdIEI0UPNhALsDnRQ1AT/HQi1AyCEwiICOICpiAPlB8MwxnBPIk6JYaIgDy8NJHDsiAqzK0JAXpQPXgPLwJuEEbMTAGBYlQbDESvAXJAAQ=="));

        int i, j, k, c, d, h = u[0].hashCode(), 
            a = (h & 4092) >> 2 | (h >> 5) & 1024 | (h >> 7) & 2048 | (h >> 9) & 4096;
        char r = '@';
        for (i = 0, k = 0; i < 4297; i += 14) {
            for (c = 0, j = 7; j >= 0; j--)
                c += c + (s.get(i + j) ? 1 : 0);
            if ((k += c) == a) {
                for (d = 0, j = 13; j >= 8; j--)
                    d += d + (s.get(i + j) ? 1 : 0);
                r = d < 5 ? " &':;".charAt(d) : (char) ((d < 31 ? 60 : 66) + d);
            }
        }
        System.out.println(r);
    }
}

I nomi dei mostri sono:

  • hash usando il hashcodemetodo Java => 32 bit
  • ANDed con maschera 1001001000111111111100 => 13 bit
  • ordinati dal più piccolo al più grande
  • utilizziamo quindi i valori delta dell'elenco ordinato => 8 bit

Il carattere di visualizzazione è codificato su 6 bit.

Quindi ogni tupla (nome del mostro, personaggio) usa 14 bit. Tutte le tuple vengono salvate in un BitSet e codificate in base 64.

Perdo molti byte con la codifica Base64 e le operazioni BitSet :-)


È possibile ridurre le dimensioni, rendendo lambda espressione: ()->{...}. La domanda lo dice nella sua sezione "chiarimenti".
Olivier Grégoire,

5

Mathematica, 1067 byte (codifica caratteri romani per Mac OS)

FromCharacterCode[Mod[Tr/@{c=ToCharacterCode@#,c^2},216,32],"MacintoshRoman"]/.Inner[Rule,";¤7«´3πœ(ú-UU=6z±'¥ÿ†tƒ|\¢KÛd§≤jSóKWÊ8‰Ñwiøì¡ÛhÓ\‡¨:–*~‚¬æº¢»‘¤Á^∫„·nLÒ¤b|$|ÇòCóÌÈS_Ñä.Ëí 5y«KΔË\Ãò™_E`J’ëΔñTV–N„'„Ÿà¥xpîH#-PP)ÈÊVQ©LrBt}∑WÉ∏dÿå„•Tz∑Âao¿rÃ^bbP¨}ëÖ◇1èÇ&d¢¤ái√,B}±BˆÍdA´íBtæÅ/m√yQ6,uãÊ≤/Î!ïøuΩÒÉ)ë“∕C$RY•ÍÍu£oÉÓå‚Ïl.·1‚40ÃÚ¨ÇÆÅccflÓ8Ï Gáç3EÑ¥fXñ¨Àìz~j÷–ñÓz0~ôWtñ}μÎ◇f||Dd\ ÙH﷿É∑Ì´|¿Ö_»RT8Ûª|Äqü‘&6Ãác›Yˆ¿ô5≈ënÚqΩåVä>∫æ∂p ¨jtöåoÌfløÏÏò§¤flÈ;À∑Ѥ·›9né∕<·ì∕ÿmŸ«Ì»j√üà‰÷“5ïä^Ûe◇kd‡“(Ïö71›iΟÁm„ÈïÒß„kÕπ°ÊÓÒçÓfˆ¨flÁ9k|¶ä∕l~Òød‹jZÏ2[kÎ√3ÛâìÓΔE]ıIÚ>{#ÁÖ‚Üâ;·?l^vàß‹‘jîÙÇÅÉú¥äärÆæ™∏Üi≈mØÂ’-%USÌâ’ı Ê›·Ëÿb‡ıÖ31nh™Δ$~%À0n-À´sflk∑p.o5vz}mè]ÎÅç©lt;Îu„ŸW„›ˆˆÍ﷿Ä*7m8‰πór,„Õш/”Ë∕ªß9±‡¶çÁ•âg˜fló)ÖÔ¡'wúæ0ñ„Kûr"~(a=StringPartition)~2,"AAA&&DH&&&&&D&KKKKH&o&WT&&soV&bEYLDD:DDwDwDDDD&q&WBDyNNPuDj&FPhYss:c'ScAd:LdR&dvhhMZhE;MZv&MZHaEHve'evCdeHgSe:b ZaBa;mMrsZH'pGGMZGGo'NNDPuDFPgxNNdd&Hohoi&ufMZ&Tv:i&'pJdf;AafkMkZk;fdkm'iqlLFd:wtf&i&LhqhHYCnq&:jOODMoZooYf';&SCuwcSQiabrBDFNNrpT'qR:&Ysr eFDZmSajEvH'H'&ifHqtTUBVVF&du&ESnTdrdDugddd'nrWqXDyFYz"~a~1,List]/.x_/;StringLength@x>1->"@"&

Funzione senza nome che accetta una stringa come input e restituisce un carattere. La funzione ha la forma seguente:

1  FromCharacterCode[
2    Mod[Tr/@{c=ToCharacterCode@#,c^2},216,32]
3    ,"MacintoshRoman"] /.
4  Inner[Rule,
5    GIANT_STRING_1 ~(a=StringPartition)~2,
6    GIANT_STRING_2 ~a~1,
7    List]
8  /. x_/;StringLength@x>1 -> "@" &

Qui GIANT_STRING_1 è una stringa contenente 608 caratteri a un byte nella codifica dei caratteri romani di Mac OS (nessuno dei quali è compreso nell'intervallo 00-1F), mentre GIANT_STRING_2 è una stringa contenente 304 caratteri ASCII.

La riga 2 avvia la funzione hash: converte la stringa di input in un elenco di codici di caratteri (codifica irrilevante poiché sono tutti ASCII stampabili), quindi calcola la somma di quei codici di caratteri e la somma dei loro quadrati, sia modulo 216 che forzatura la risposta è compresa tra 32 e 255. Quindi le righe 1 e 3 convertono quelle coppie ordinate di numeri interi in stringhe di due caratteri, che è il valore di hash che alla fine usiamo.

La riga 5 trasforma GIANT_STRING_1 in un elenco di 304 stringhe di due caratteri; la riga 6 trasforma GIANT_STRING_2 in un elenco di 304 stringhe di un carattere. Quindi le righe 4 e 5 convertono quei due elenchi in un set di 304 regole di sostituzione: se vedi una stringa di due caratteri del genere e quella, trasformala in una stringa di quel carattere. Infine, la riga 8 trasforma qualsiasi stringa rimanente di due caratteri in "@".

Ci sono 71 mostri nella lista il cui simbolo è "@"e quelli sono gestiti senza hashing (ho rubato questa idea da un commento di Ais523 su un'altra risposta). Accade così che gli altri 304 valori di hash siano tutti unici! e quindi non sono necessarie altre modifiche all'algoritmo. (È una pausa fortunata che "human"deve essere mappata "@", poiché le somme dei codici dei caratteri delle lettere "human"e delle lettere "shark"sono identiche, così come le somme dei quadrati di quei codici - come numeri interi, nemmeno modulo 216!)


3

Python, 2055 byte

def f(s):import re;m=re.search(s[-3:-1]+s[:2]+str(len(s))+"(.)","omgn5Gteen13vligr7glero10'akga12Sanso11aragi9rgoDe10&tiet5HleVl16Vanst11Hmpmo14nubge15brsho5uingo21Nbuin7&gobl11Dgobl12Duaja6faule10lkest7Eolic9Ttawa15EanKo14KanKo12Kviba12&gore10Dimim3iutzr5zingn10Ganhi10Herfr15emmel9Mlele13'guNa6Wkaja6dotco6docvr5&petr7tmaor10oarli6:nhpi7;moma11&icli4Linbl20Nolwa11Titwr6Wtlgi12ateru12Rbign12Zozgr9Plepa11'oufo9vingu23Norhi8onena10&tati5Hiosc8sinre18Nligo6obeki10aeaow7Yeyfl12elewo10'adsh5 anfr11Hapap3Ygrog4Obequ9ahopy6Steki6fgogr11Dgogr12Dpepi9Sngdi5dindw10hlegl11'imgr11Pbava11Bcero12phaOl8Tdoli10dcuwi15dargn14GotIx5Dinbl13Parwa4dpuhe14dtisa9&ilba14:liho9onyer6&euAs8&aupl14Cttle9qmmdw11Molbr10Fmism11mncPe10&mpwa11noror3oispy8caumo16Clest11'haUr8okekr6;bigi12ZbuBa9&gowh12Dbiko13Zbiet12Zmmgn11Molwe8dmowa11&icde8Lbiho6hdola9dleJu7&otMi18&ulum10Uenpi9&luho10ighye12ymamu5qorwh13ughbl11yylga8gKoKe12Knndj6&mmet11Magbl10Narsh5;osgh5 orxo4Xoltr5Tdoma8qopCy7Hceir12pgoba18Dorlo9wgoba16Dbidw12ZinFa6&goor13DeaAl5Aiuba14qloac9bkemo6Yniqu16QteDi8&aufo14Ckesh8Fetye4Yolro9ryema18hersh15eaggo11Nrase9ranig6:ghba12Winbr13Polwi11dgeti5fzoNa6&orga9emmko12Manfi8aorgn10Gatco6Alecl10'goye13Deabu7hinog9Oheli6Feoch9:ynly4fngte5ieeel12;rawe7ricch11caior11ocala9fguvi13Fangi9aangi5Hhepa7fdesa10:cuOr5&rswa8ubrco5Sorva12Vxaxa3xovlu12tbaba3Bilcr9:geAn5Aolwo4dviic9&tafi14Ecegl13pbugr8xorpu11wgoCh16Dicar9Laggu13Ndegi12shoAr6Aolla12kedce9sitma8&erti11qicma11Lbior10Zviho12&test12vbusu8&fofo3ddeca11srara9rolko6kmpwo10ntaea15Ellbl10jgosi13Daksn5Svibo10&tosk8Zicco10cvera5Bgoba15DatDe5&goba17Dpuwu6qkawe10dmmhu11Mdodo3dunhe10dtcsa9Yckge5:tefi11vsiqu6iloqu14bewne4:yoGe6&caho8fucwo9rorMo10oisje9;taai13Eardw5holye11Fordw10hlloc11jough5Zerfl14emila11mtedu11vthro5qteic10vtuLo11Hmmor9Mirva7Vbagi9Bolro10Tmako13kleir10'biel10Zmmgi11Mnema5ilego10'olre8Forbl13usiwa14Sroba6&agre8Nrohe6&orgr12ulefl11'ocja10JghYe8&aumi8HiuSc8sbihu12Zriki6Ayemi11horko11kolgr10Furle6ianfi10Hmigi11monpo4ullsp13jaiKo11Ktedi12Rapca15Yorog9Oylwi15geegi9;orba14worba16w");return m.group(1)if m else'@'

Ecco la mia imbracatura di prova, nel caso in cui aiuti qualcun altro.

MAPPING = {
    # as given in the original question
}
def validate_solution(f):
    for k, v in MAPPING.iteritems():
        vv = f(k)
        if vv != v:
            print 'FAIL: f(%s) = %s instead of %s' % (k, vv, v)
    print 'SUCCESS!'

Ho scritto un piccolo programma per elencare tutti i vari modi di estrarre 4 caratteri più la lunghezza della stringa. Il mio piano originale era stato quello di allora ord()questi personaggi, fare un po 'di matematica su di loro e ridurlo a una funzione hash perfetta che produceva indici in una tabella di output. Così ho scritto un altro piccolo programma per elencare tutti i vari modi di sommare / moltiplicare / modulo questi quattro caratteri insieme; ma le funzioni di hash risultante tenuti avendo modo troppe collisioni. Quindi alla fine ho rinunciato e ho appena fatto quello che vedi qui, che è solo una mappa dalla rappresentazione a piccola stringa del nome di ciascun mostro al simbolo appropriato.

Cioè: quello che volevo ottenere era

def f(monster_name):
    small_string = f1(monster_name)
    integer_index = f2(small_string)
    symbol = "relatively_short_table"[integer_index]
    return symbol

ma sono riuscito solo ad arrivare fino a

def f(monster_name):
    small_string = f1(monster_name)
    symbol = {relatively_large_dict}[small_string]
    return symbol

dove la mia ricerca di dict {relatively_large_dict}[small_string]è espressa re.match(small_string+"(.)", "relatively_large_string")per quanto riguarda il golfiness.


2

JavaScript (ES6), 1178

n=>'@0uy1c8@@@@@@2cb7sj0sb5rhcm626435y6js6u651b1nj5jg85g2xj02l4wh31u2py2xl96h5fz6ys46tc7821p2e9c1o1td1cy834@2sq2c055iabn91f82vahc6ytagh5d363i@@@@@@@@@@@@@@@@@@@7hh2wf2nd1bu2d93cm0gu862@144819a6v2h44o41d4@@@@@@0c404806f3fa0z8@04c82o1vfac3@c10a3g08g@82e0lr7bf26p2dibcb11t9y19q6bbh4db7tr3592u2bof4913edawy84p1cr@bap1qzb1o033bt6@8d93v230t4240w9ahh8cy@09u0a60sd1qd@1n23ak1bt614bax0ro7sd57xagg22s1gj@@be0@74l01c28qcdi@1so83t0c068s@2jh7as7ddalq0vxag68pn6b9@0gabu71zp54m6997imb2047h@10s0zo0mv@aww6ixbqgag7@944@bza76b@1a053c2yn6101eh8en@4je6fq97t1py9f0@6co@b3k5my44p@4edb737t9@0tl@00rau75y369z5hk0ot@23d2wicb90uwb54a9l3gw9lv3z51nv@@@@@@@amy81e3kh9yc90e59d@6528z42ic@7uv6bm58t@3av0w004t05aavs3oq3040irawj0ov1n90213h89yn0vs@0mcc284fv6uyaxp@3242ok39h0jd06905v1ia@7zc9659bk@ax30ua0um0652sa65daqd@00z03d2ra1f95751xu@9x10676yz@72w33r24b63d@2d7@ats6f678u@bcg9uf6h6@1b60us2d17ygbxn72106t02g@adublf05q@8xu5wobqb1tc1c73cs7pj@87k3cj2xq6258l379y@0q42qy3vs3y70r9@06v2a9@ast4su12w0ko4y77dn@7oubr07ju1ct5qe81v@0d52kb66t4zj@93508c@af30kj@299'.replace(/@\w*/g,v=>~-v.search((100+h.toString(36)).slice(-3))%3?++i:r=String.fromCharCode(i),i=32,r='@',n.replace(/\w/g,c=>h=parseInt(c,36)^(h*3)&16383,h=0))&&r

Meno golf

n=>(
'@0uy1c8@@@@@@2cb7sj0sb5rhcm626435y6js6u651b1nj5jg85g2xj02l4wh31u2py2xl96h5fz6ys46tc7821p2e9c1o1td1cy834@2sq2c055iabn91f82vahc6ytagh5d363i@@@@@@@@@@@@@@@@@@@7hh2wf2nd1bu2d93cm0gu862@144819a6v2h44o41d4@@@@@@0c404806f3fa0z8@04c82o1vfac3@c10a3g08g@82e0lr7bf26p2dibcb11t9y19q6bbh4db7tr3592u2bof4913edawy84p1cr@bap1qzb1o033bt6@8d93v230t4240w9ahh8cy@09u0a60sd1qd@1n23ak1bt614bax0ro7sd57xagg22s1gj@@be0@74l01c28qcdi@1so83t0c068s@2jh7as7ddalq0vxag68pn6b9@0gabu71zp54m6997imb2047h@10s0zo0mv@aww6ixbqgag7@944@bza76b@1a053c2yn6101eh8en@4je6fq97t1py9f0@6co@b3k5my44p@4edb737t9@0tl@00rau75y369z5hk0ot@23d2wicb90uwb54a9l3gw9lv3z51nv@@@@@@@amy81e3kh9yc90e59d@6528z42ic@7uv6bm58t@3av0w004t05aavs3oq3040irawj0ov1n90213h89yn0vs@0mcc284fv6uyaxp@3242ok39h0jd06905v1ia@7zc9659bk@ax30ua0um0652sa65daqd@00z03d2ra1f95751xu@9x10676yz@72w33r24b63d@2d7@ats6f678u@bcg9uf6h6@1b60us2d17ygbxn72106t02g@adublf05q@8xu5wobqb1tc1c73cs7pj@87k3cj2xq6258l379y@0q42qy3vs3y70r9@06v2a9@ast4su12w0ko4y77dn@7oubr07ju1ct5qe81v@0d52kb66t4zj@93508c@af30kj@299'
.replace(/@\w*/g, v= > 
   (v.search((100 + h.toString(36)).slice(-3))-1) % 3  
     ? ++i : r = String.fromCharCode(i),
   i=32,
   r='@',
   n.replace(/\w/g,c => h=parseInt(c,36) ^ (h*3) & 16383,h=0)
)
&& r

Test

F=
n=>'@0uy1c8@@@@@@2cb7sj0sb5rhcm626435y6js6u651b1nj5jg85g2xj02l4wh31u2py2xl96h5fz6ys46tc7821p2e9c1o1td1cy834@2sq2c055iabn91f82vahc6ytagh5d363i@@@@@@@@@@@@@@@@@@@7hh2wf2nd1bu2d93cm0gu862@144819a6v2h44o41d4@@@@@@0c404806f3fa0z8@04c82o1vfac3@c10a3g08g@82e0lr7bf26p2dibcb11t9y19q6bbh4db7tr3592u2bof4913edawy84p1cr@bap1qzb1o033bt6@8d93v230t4240w9ahh8cy@09u0a60sd1qd@1n23ak1bt614bax0ro7sd57xagg22s1gj@@be0@74l01c28qcdi@1so83t0c068s@2jh7as7ddalq0vxag68pn6b9@0gabu71zp54m6997imb2047h@10s0zo0mv@aww6ixbqgag7@944@bza76b@1a053c2yn6101eh8en@4je6fq97t1py9f0@6co@b3k5my44p@4edb737t9@0tl@00rau75y369z5hk0ot@23d2wicb90uwb54a9l3gw9lv3z51nv@@@@@@@amy81e3kh9yc90e59d@6528z42ic@7uv6bm58t@3av0w004t05aavs3oq3040irawj0ov1n90213h89yn0vs@0mcc284fv6uyaxp@3242ok39h0jd06905v1ia@7zc9659bk@ax30ua0um0652sa65daqd@00z03d2ra1f95751xu@9x10676yz@72w33r24b63d@2d7@ats6f678u@bcg9uf6h6@1b60us2d17ygbxn72106t02g@adublf05q@8xu5wobqb1tc1c73cs7pj@87k3cj2xq6258l379y@0q42qy3vs3y70r9@06v2a9@ast4su12w0ko4y77dn@7oubr07ju1ct5qe81v@0d52kb66t4zj@93508c@af30kj@299'.replace(/@\w*/g,v=>~-v.search((100+h.toString(36)).slice(-3))%3?++i:r=String.fromCharCode(i),i=32,r='@',n.replace(/\w/g,c=>h=parseInt(c,36)^(h*3)&16383,h=0))&&r


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"
}
err = ok = 0

for(name in monsters) {
  code = monsters[name]
  result = F(name)
  if (result != code)
    console.log('ERROR',++err, name, result, code)
  else
    ++ok
}
console.log('Errors',err,'OK', ok)


2

Javascript, 1185 byte

s=>{h=0;for(i of s)h=(h<<5)-h+i.charCodeAt()|0;for(v of "Aqgh201etxitsxy0_&ctpzfekt09j36uafamqw46mz1qcxvnnoego4212nxfivutt09qyac4td1ayiotfh3dvub5fggzjqa58h37bnva3dzy_D9wlywkgkifldlp6t46v97basg905f8wadwt0w49q0gk9c8edz9e33uj6esjl_Hkkt54kr0qdlxs6hxdxxyegrdzcmz8ymvg_Ki0enu0ct1shv_o193ve2y3tpa71xu3pud405o7_We09jfsayx_Tw2gk0spoqab5c9k_s7timco3yh674rp1_Vppq2k9t1q_b3mo3tac13_E0r50a7vi5a0kgim_Y2omnjbkq59mw5njf5t_Lu9z2bj6w2128_:n0gngsocqeuh5czhyiedwd3a_w9lf1hv1rra7r_qmckg7rbhlldbvros4f44h_B32t12yzdci83_yjkb3va_Nt2cbaqd46toc29anic1qq3es_P3mkmtv2l4j8r_ukjb44lwm5vkaz5hwkh_j3oo7uj9ip_Fzuk8mh1rpfw7obl6s9fsq_hzmwz3f7kdhiaj4enlxha1_c0q0yu8tnf_'nf7c1sks8rzgxhw83vjq0s76xhrvppbgn_Slr90h5su3zokncwi2m_doi5t2p4vw6dryycyhtl6eujb1ta26752ta7hr19d9vceq_Rqk8tsy_vuxwglitt4u25zfhj5q_M4j7tjk9cryvqn8101u5h646p_Ztzwr09t8ckxx3hbsl6r7dqv7qxmnwt_;u7r3e9trqqkmdj5tlx_apoj0ngpcqy6r7t8gw9_e2wtyw9oyve8uxlf_C8tpo3hlb3_gxji2n2nl4_ kwft9p_maxcdzat5e_rcy28c360mmndp8ksxh_pegqkkuur3_Gh6f8pheo0nn2_xu6yjdx_iz538jwkbwuh4ge7ymj_f3eytt6khltgxj13itedbz_Jlgk_knskybpe8n69a_llnv_tuxgkxc_nod5ob3cft_Oij0a222q3_Q6af_Uc5x_Xzjn_z6iq".split`_`)if(~v.slice(1).match(/.../g).indexOf(h.toString(36).slice(-3)))return v[0];return"@"}

Utilizza una versione golf dell'hash della stringa Javascript presente qui. L'hash effettivo memorizzato nella tabella (quella stringa lunga) assume il valore assoluto dell'hash prodotto da quel metodo, lo converte in base-36 e rilascia tutte tranne tre le cifre meno significative.


1
Per quanto ne so, stai eseguendo l'hashing per {primo carattere, ultimo carattere, lunghezza}, quindi stai utilizzando una tabella di ricerca di grandi dimensioni? Ciò ha senso, ma penso che ci siano un paio di miglioramenti che potresti apportare: ci sono alcune voci duplicate nella tabella e potresti risparmiare un sacco di spazio rimuovendo le voci @dalla tabella e semplicemente impostando @se l'input non trovato.

2
cavewomane chameleonavere lo stesso primo carattere, ultimo carattere e lunghezza, che potrebbe essere un problema?
Arnaud,

1
split("_")può diventare split backtick _ backtick
user2428118

@SuperChafouin Fixed.
SuperJedi224,

1
@ SuperJedi224 Ce ne sono altri: Cyclopse Croesus, baluchitheriume baby long worm, crocodilee centipede, e altri 24
Arnaud,

1

Python 3, 1915 1900 byte

changelog:

  • Lavora con e genera codice ASCII invece del carattere stesso (salvato 15 byte)

Passa il nome del mostro come primo argomento della riga di comando, ricevi il personaggio su stdout.

import sys
D=b'`"\x08\x04\x02&@Yx\xf6\x90a\x00Z\x00\x00c\x00X\x00\x00f\x00z\x00\x00hS\x12\x06\t@PSTft?z\x0fnK\nH\x87\xa2ig\t\t\x12 &;@FZkoq\x05\xfc~?\x1b\x80\xc2,z\r\xf3Y\x141\x9cS\x10\x80jU\x06\x08\t&;@BKpqr\x9f\xbe\xbb\xf9O\xcde\x03!kK\x11\x07\x07&:@WYsu\x1boDv\xc9i\x90lS$\x06\r@Sdirw\x1f\x1d\x198\xb3\xb2\x91\x0fm\xa5\x03A@mB#\x07\x07@GPWdiv\x7f;n\xb3Bk\xa5ng\x07\x0c\x16&@EHSVcdfqru\x01\xfen\x83q\xd8\xf3\x1c.\xe5\xac^\x87\t\xaaT\xd4D\x9c\xe1*Io;\x03\x05\x06@desu\x01\xf7\x95R0\x88pc \x08\n:@KMNknq\xfd\xfe\ru\xb2z\xea\\\x9b\x05qC\x08\x07\x06&@AGOfhy\xe2\xbbA\xf2ArS\x1e\x08\x08&@JVYdfi_\x1c\xd8/k\x89\xa8\xe0sw\x08\x0b\x1c&;@Kdfhijou\t\xe0[# \\\x9a\xd3F(L\xfapM\tp\xa8t\xccp\x8d\x11e+\x05\x0c\x8a\x08t+)\x04\x02@PQT\xf2\x94uG\x1c\x06\t&@Uilq\x0f\ryl\xc4`\xa5\x10\x90v\x85\r\x0e$&:@FKLNORSWYry\x9f\x97\xf8\xae\xb8\xdf\xdd\xc1\xcdl\xb2\xc9L|\xbb;\x92\xb8j\xb0\xa99\xdd\x9c\xb8\xd0\x8bh\x95\x88T\xb3;1\xb6\x0bwb\x06\x0c\x11&:;@DGHOVhkm\x02\xfe\x8fO{\xd9u\xac&\xd7\x90\x9fe\xc0\xf44GxW\x07\x07\x0bADHScdv?>\xdd<:\xb7s.\x8cI\x07yR\x07\x07\t&:@bcht;Zx\x16sO\x8d\xab\xc3ze\x0b\x08\x14&@ABCaqs\x01}\xbe=\x15\xc6\xcdL\xa1\xc8\x9e.\xf7\x02\xc1Xq4\x99\t{G\x16\x06\t@Faefg\x1f\x9bU$2P`\xa8\x80|G\x15\x06\x07&\';@Go\x1c1\\\xa7*\x0bS}s\x06\n" &@AHLYZdh\xf6\x1e\t\xb93N2\xc27\xd6\xd8\xd8*\xe5L\xa3\xa4f\x860A\xfa:7.\xdd\x9b)\xb80\x85\xc4\xb4\x83~W\x0e\x07\r&:@ERbd>\x1b\xda\x15\xd4\x92\x0eM\xacJH\x04c\x7fG\x00\x06\x08:@dghx\x1f\xbc\xf4Z\xa1%\xd3C'
R=range
N=sys.argv[1].lower()
B=0
for c in N:B|=ord(c)&0x7f;B<<=7
B%=2**62-1
P=N.split()
F=ord(P[-1][0])^(ord(P[-1][1])>>2)
while D:
 f=D[0];ik,j,h,n=D[1:5];i=ik>>4;k=ik&15;D=D[5:];c=D[:h];m=D[h:h+n];m=int.from_bytes(m,"big");s=1;C=j;b=(h-1).bit_length()
 for x in R(i-1):s<<=k;s|=1
 s<<=j;z=(B&s)>>j;x=0;u=1
 for y in R(i):x<<=1;x|=bool(z&u);u<<=k
 if f!=F:D=D[h+n:];continue
 while m:
  if m&(2**i-1)==x:m>>=i;C=c[m&(2**b-1)];break
  m>>=b+i
 break
print(C)

Quando ho letto la domanda, ho pensato "Ho bisogno di comprimerlo". Il primo passo è stato quello di mettere in minuscolo tutti i nomi.

Guardando i dati, ho sentito che in qualche modo usare la prima lettera dell'ultima parola dovrebbe fare il trucco come una prima ipotesi approssimativa su quali lettere potrebbe avere il mostro. A quanto pare, questa è stata una potente ipotesi iniziale. La tabella seguente è "primo carattere dell'ultima parola", "numero di risultati", "caratteri mostro":

 'd' (37) => & @ D L R d h
 'g' (31) =>   & ' : @ G H Z g o
 's' (30) =>   & : ; @ E F H K P S Y Z e k o s
 'm' (28) => & @ F H M Q R S Y i m q r
 'c' (24) => : @ A C H S b c d f s v
 'p' (20) => & ; @ P S c d f p u
 'w' (20) => @ G W d q r u w
 'a' (19) => & @ A L Y a t
 'h' (17) => & @ N U d f h i o u
 'l' (17) => : @ F G K L O V f h i k l q y
 'n' (15) => & : @ N W n
 't' (14) => @ H T f i q t
 'b' (14) => & @ B a b h q x
 'k' (13) => ; @ A G K O f h k
 'e' (12) => & ; @ E H e
 'o' (12) => & @ O P T Y o
 'z' ( 9) => Z z
 'v' ( 9) => & @ S V v
 'r' ( 8) => @ B q r
 'j' ( 8) => & ; J d f j
 'f' ( 6) => & F d h
 'i' ( 5) => & : D V i
 'u' ( 4) => o u
 'y' ( 3) => & @ Y
 'x' ( 2) => X x
 'q' ( 1) => i

Per migliorare ulteriormente lo spreadout, ho modificato leggermente la chiave, XOR-ing il secondo carattere dell'ultima parola, spostato in bit a destra, nel primo carattere (chiamiamo questo costrutto first_key):

 '}' (29) =>   & @ A H L Y Z d h
 'v' (25) => & : @ F K L N O R S W Y r y
 'x' (25) => A D H S c d v
 's' (21) => & ; @ K d f h i j o u
 'p' (21) => : @ K M N k n q
 'z' (19) => & @ A B C a q s
 'n' (19) => & @ E H S V c d f q r u
 '|' (18) => & ' ; @ G o
 'l' (17) => @ S d i r w
 '~' (16) => & : @ E R b d
 '{' (15) => @ F a e f g
 'w' (14) => & : ; @ D G H O V h k m
 'i' (14) =>   & ; @ F Z k o q
 'j' (13) => & ; @ B K p q r
 'u' (12) => & @ U i l q
 'm' (12) => @ G P W d i v
 '\x7f' (11) => : @ d g h x
 'o' (11) => @ d e s u
 'h' (11) => @ P S T f t
 'y' (10) => & : @ b c h t
 'r' ( 9) => & @ J V Y d f i
 'k' ( 9) => & : @ W Y s u
 'a' ( 8) => Z
 'q' ( 7) => & @ A G O f h
 't' ( 6) => @ P Q T
 '`' ( 4) => & @ Y x
 'c' ( 1) => X
 'f' ( 1) => z

Come puoi vedere, questo ci dà nove nomi che possono essere mappati in modo univoco solo con quelle informazioni. Bello!

Ora dovevo trovare la mappatura rimanente. Per questo ho iniziato convertendo il nome completo (maiuscolo) in un numero intero:

def name_to_int(name):
    bits = 0
    for c in name:
        bits |= ord(c) & 0x7f
        bits <<= 7
    return bits

Questo sta semplicemente concatenando insieme i valori ASCII a 7 bit dei nomi in un intero numero enorme. Prendiamo questo modulo 4611686018427387903(2⁶²-1) per i prossimi passi.

Ora provo a trovare una maschera di bit che produce un numero intero che a sua volta distingue bene i diversi personaggi dei mostri. Le maschere di bit sono costituite da unità a diffusione uniforme (come 101010101o 1000100010001) e sono parametrizzate dal numero di bit ( i>=1) e spread ( k>=1). Inoltre, le maschere vengono spostate a sinistra per un massimo di 32*ibit. Quelli sono AND-ed con il nome intero e il numero intero risultante viene usato come chiave in una mappatura. i*number_of_mapping_entriesViene utilizzata la migliore mappatura (contrassegnata da ) senza conflitti.

Gli interi ottenuti da E-ing la maschera e il nome integerised sono spostate indietro di jbit e spogliati dei loro zeri (archiviamo i, ke jinsieme con la mappatura di essere in grado di ricostruire quello), risparmiando un sacco di spazio.

Quindi ora abbiamo una mappatura a due livelli: da first_keya hashmap e hashmap associa il nome completo al carattere mostro in modo univoco. Dobbiamo memorizzarlo in qualche modo. Ogni voce della mappatura di livello superiore è simile alla seguente:

Row = struct.Struct(
    ">"
    "B"  # first word char
    "B"  # number of bits (i) and bit spacing (k)
    "B"  # shift (j) or character to map to if i = 0
    "B"  # number of monster characters
    "B"  # map entry bytes
)

seguito dai personaggi mostruosi e dalla mappatura di secondo livello.

Il mapping di secondo livello viene serializzato impacchettandolo in un intero di grandi dimensioni e convertendolo in byte. Ogni valore e chiave viene spostato consecutivamente nell'intero, il che rende ricostruibile la mappatura (il numero di bit per chiave / valore è inferibile dal numero di caratteri e i, entrambi memorizzati nella voce di riga).

Se una voce ha solo un singolo carattere mostro possibile da mappare, iè zero, anche il numero di caratteri e la mappatura sono zero byte. Il personaggio viene memorizzato dove jverrebbe normalmente memorizzato.

I dati completi hanno una dimensione di 651 byte, serializzati come stringa di byte python i suoi 1426 byte.

Il programma di decodifica essenzialmente fa il contrario: prima estrae first_keye cerca nei dati la voce corrispondente. Quindi calcola l'hash del nome e cerca nella hashmap la voce corrispondente.

Decodificatore non compromesso

#!/usr/bin/python3
import sys
import math

data = b'`"\x08\x04\x02&@Yx\xf6\x90a\x00Z\x00\x00c\x00X\x00\x00f\x00z\x00\x00hS\x12\x06\t@PSTft?z\x0fnK\nH\x87\xa2ig\t\t\x12 &;@FZkoq\x05\xfc~?\x1b\x80\xc2,z\r\xf3Y\x141\x9cS\x10\x80jU\x06\x08\t&;@BKpqr\x9f\xbe\xbb\xf9O\xcde\x03!kK\x11\x07\x07&:@WYsu\x1boDv\xc9i\x90lS$\x06\r@Sdirw\x1f\x1d\x198\xb3\xb2\x91\x0fm\xa5\x03A@mB#\x07\x07@GPWdiv\x7f;n\xb3Bk\xa5ng\x07\x0c\x16&@EHSVcdfqru\x01\xfen\x83q\xd8\xf3\x1c.\xe5\xac^\x87\t\xaaT\xd4D\x9c\xe1*Io;\x03\x05\x06@desu\x01\xf7\x95R0\x88pc \x08\n:@KMNknq\xfd\xfe\ru\xb2z\xea\\\x9b\x05qC\x08\x07\x06&@AGOfhy\xe2\xbbA\xf2ArS\x1e\x08\x08&@JVYdfi_\x1c\xd8/k\x89\xa8\xe0sw\x08\x0b\x1c&;@Kdfhijou\t\xe0[# \\\x9a\xd3F(L\xfapM\tp\xa8t\xccp\x8d\x11e+\x05\x0c\x8a\x08t+)\x04\x02@PQT\xf2\x94uG\x1c\x06\t&@Uilq\x0f\ryl\xc4`\xa5\x10\x90v\x85\r\x0e$&:@FKLNORSWYry\x9f\x97\xf8\xae\xb8\xdf\xdd\xc1\xcdl\xb2\xc9L|\xbb;\x92\xb8j\xb0\xa99\xdd\x9c\xb8\xd0\x8bh\x95\x88T\xb3;1\xb6\x0bwb\x06\x0c\x11&:;@DGHOVhkm\x02\xfe\x8fO{\xd9u\xac&\xd7\x90\x9fe\xc0\xf44GxW\x07\x07\x0bADHScdv?>\xdd<:\xb7s.\x8cI\x07yR\x07\x07\t&:@bcht;Zx\x16sO\x8d\xab\xc3ze\x0b\x08\x14&@ABCaqs\x01}\xbe=\x15\xc6\xcdL\xa1\xc8\x9e.\xf7\x02\xc1Xq4\x99\t{G\x16\x06\t@Faefg\x1f\x9bU$2P`\xa8\x80|G\x15\x06\x07&\';@Go\x1c1\\\xa7*\x0bS}s\x06\n" &@AHLYZdh\xf6\x1e\t\xb93N2\xc27\xd6\xd8\xd8*\xe5L\xa3\xa4f\x860A\xfa:7.\xdd\x9b)\xb80\x85\xc4\xb4\x83~W\x0e\x07\r&:@ERbd>\x1b\xda\x15\xd4\x92\x0eM\xacJH\x04c\x7fG\x00\x06\x08:@dghx\x1f\xbc\xf4Z\xa1%\xd3C'


def name_to_int(name):
    bits = 0
    for c in name:
        bits |= ord(c) & 0x7f
        bits <<= 7
    return bits


def make_mask(nbits, k):
    mask = 1
    for i in range(nbits-1):
        mask <<= k
        mask |= 1
    return mask


def collapse_mask(value, nbits, k):
    bits = 0
    shift = 0
    for i in range(nbits):
        bits <<= 1
        bits |= bool(value & (1<<shift))
        shift += k
    return bits


name = sys.argv[1].casefold()
last_word = name.split()[-1]
last_word_char = chr(ord(last_word[0]) ^ (ord(last_word[1]) >> 2))
while data:
    first_char = chr(data[0])
    ik, j, nchars, nbytes = data[1:5]

    i = ik >> 4
    k = ik & 15

    data = data[5:]
    if first_char != last_word_char:
        # skip this entry
        data = data[nchars+nbytes:]
        continue

    chars, mapping = data[:nchars], data[nchars:nchars+nbytes]
    result = j
    if i == 0:
        break

    mapping = int.from_bytes(mapping, "big")

    name_bits = name_to_int(name) % (2**62-1)
    mask = make_mask(i, k) << j
    key = collapse_mask((name_bits & mask) >> j, i, k)
    bits_per_key = i
    key_mask = 2**(bits_per_key)-1
    bits_per_value = math.ceil(math.log(len(chars), 2))
    value_mask = 2**(bits_per_value)-1
    while mapping:
        if mapping & key_mask == key:
            mapping >>= bits_per_key
            result = chars[mapping & value_mask]
            break
        mapping >>= bits_per_value+bits_per_key

    break
print(chr(result))

Strumento di analisi

Questo è lo strumento che ho creato e usato per generare i dati - leggi a tuo rischio e pericolo:

#!/usr/bin/python3
import base64
import collections
import math
import json
import struct
import zlib

data = json.load(open("data.json"))

reverse_pseudomap = {}
forward_pseudomap = {}
forward_info = {}
reverse_fullmap = {}
hits = collections.Counter()
monster_char_hitmap = collections.Counter()

for name, char in data.items():
    name = name.casefold()
    parts = name.split()
    monster_char_hitmap[char] += 1

    # if len(parts) > 1:
    #     key = first_char + parts[0][0]
    # else:
    #     key = first_char + last_part[1]

    key = chr(ord(parts[-1][0]) ^ (ord(parts[-1][1]) >> 2))
    # key = parts[-1][0]

    hits[key] += 1
    reverse_pseudomap.setdefault(char, set()).add(key)
    forward_pseudomap.setdefault(key, set()).add(char)
    forward_info.setdefault(key, {})[name] = char
    reverse_fullmap.setdefault(char, set()).add(name)


for char, hit_count in sorted(hits.items(), key=lambda x: x[1], reverse=True):
    monsters = forward_pseudomap[char]
    print(" {!r} ({:2d}) => {}".format(
        char,
        hit_count,
        " ".join(sorted(monsters))
    ))


def make_mask(nbits, k):
    mask = 1
    for i in range(nbits-1):
        mask <<= k
        mask |= 1
    return mask


def collapse_mask(value, nbits, k):
    bits = 0
    shift = 0
    for i in range(nbits):
        bits <<= 1
        bits |= bool(value & (1<<shift))
        shift += k
    return bits


def expand_mask(value, nbits, k):
    bits = 0
    for i in range(nbits):
        bits <<= k
        bits |= value & 1
        value >>= 1
    return bits


assert collapse_mask(expand_mask(0b110110, 6, 3), 6, 3)
assert expand_mask(collapse_mask(0b1010101, 7, 3), 7, 3)


def name_to_int(name):
    # mapped_name = "".join({"-": "3", " ": "4"}.get(c, c) for c in name)
    # if len(mapped_name) % 8 != 0:
    #     if len(mapped_name) % 2 == 0:
    #         mapped_name += "7"
    #     mapped_name = mapped_name + "="*(8 - (len(mapped_name) % 8))
    # print(mapped_name)
    # return base64.b32decode(
    #     mapped_name,
    #     casefold=True,
    # )

    bits = 0
    for c in name:
        bits |= ord(c) & 0x7f
        bits <<= 7
    return bits


compressed_maps = {}
max_bit_size = 0
nmapentries = 0


for first_char, monsters in sorted(forward_info.items()):
    monster_chars = forward_pseudomap[first_char]
    print("trying to find classifier for {!r}".format(first_char))
    print("  {} monsters with {} symbols".format(
        len(monsters),
        len(monster_chars))
    )
    bits = math.log(len(monster_chars), 2)
    print("  {:.2f} bits of clever entropy needed".format(
        bits
    ))

    bits = math.ceil(bits)

    int_monsters = {
        name_to_int(name): char
        for name, char in monsters.items()
    }

    reverse_map = {}
    for name, char in int_monsters.items():
        reverse_map.setdefault(char, set()).add(name)

    solution = None
    solution_score = float("inf")

    if bits == 0:
        char = ord(list(int_monsters.values())[0][0])
        solution = 0, 0, char, {}

    for i in range(bits, 3*bits+1):
        print("  trying to find solution with {} bits".format(i))
        for k in [2, 3, 5, 7, 11]:
            mask = make_mask(i, k)
            for j in range(0, 32*bits):
                bucketed = {}
                for int_name, char in int_monsters.items():
                    bucket = (int_name % (2**62-1)) & mask
                    try:
                        if bucketed[bucket] != char:
                            break
                    except KeyError:
                        bucketed[bucket] = char
                else:
                    new_solution_score = i*len(bucketed)
                    if new_solution_score < solution_score:
                        print("   found mapping: i={}, k={}, j={}, mapping={}".format(
                            i, k, j, bucketed
                        ))
                        solution = i, k, j, bucketed
                        solution_score = new_solution_score
                mask <<= 1

    if solution is not None:
        print("  solution found!")

    chars = "".join(sorted(set(int_monsters.values())))
    i, k, j, mapping = solution

    # sanity check 1
    if i > 0:
        mask = make_mask(i, k) << j
        for int_name, char in int_monsters.items():
            key = (int_name % (2**62-1)) & mask
            assert mapping[key] == char

    compressed_mapping = {}
    for hash_key, char in mapping.items():
        hash_key = collapse_mask(hash_key >> j, i, k)
        max_bit_size = max(hash_key.bit_length(), max_bit_size)
        compressed_mapping[hash_key] = chars.index(char)

    nmapentries += len(compressed_mapping)
    compressed_maps[first_char] = i, k, j, chars, compressed_mapping

    print(" ", compressed_maps[first_char])

    print()

print("max_bit_size =", max_bit_size)
print("nmapentries =", nmapentries)

print("approx size =", (1+math.ceil(max_bit_size/8))*nmapentries)


# first we need to map first word chars to compressed mappings
Row = struct.Struct(
    ">"
    "B"  # first word char
    "B"  # number of bits (i) and bit spacing (k)
    "B"  # shift (j) or character to map to if i = 0
    "B"  # number of characters
    "B"  # map entry bytes
)


def map_to_bytes(i, nchars, mapping):
    bits_per_value = math.ceil(math.log(nchars, 2))
    bits_per_key = i

    bits = 0
    # ensure that the smallest value is encoded last
    for key, value in sorted(mapping.items(), reverse=True):
        assert key.bit_length() <= bits_per_key
        assert value.bit_length() <= bits_per_value

        bits <<= bits_per_value
        bits |= value
        bits <<= bits_per_key
        bits |= key

    return bits.to_bytes(math.ceil(bits.bit_length() / 8), "big")


def bytes_to_map(i, nchars, data):
    data = int.from_bytes(data, "big")

    bits_per_value = math.ceil(math.log(nchars, 2))
    bits_per_key = i
    key_mask = 2**(bits_per_key)-1
    value_mask = 2**(bits_per_value)-1

    mapping = {}
    while data:
        key = data & key_mask
        data >>= bits_per_key
        value = data & value_mask
        data >>= bits_per_value
        assert key not in mapping
        mapping[key] = value

    return mapping


parts = bytearray()
for first_char, (i, k, j, chars, mapping) in sorted(compressed_maps.items()):
    raw_data = map_to_bytes(i, len(chars), mapping)
    recovered_mapping = bytes_to_map(i, len(chars), raw_data)
    assert recovered_mapping == mapping, "{}\n{}\n{}\n{} {}".format(
        mapping,
        recovered_mapping,
        raw_data,
        i, len(chars),
    )
    assert len(raw_data) <= 255

    print(" {!r} => {} {} {} {} {}".format(
        first_char,
        i, k, j,
        len(chars),
        raw_data
    ))

    assert k <= 15
    assert i <= 15

    if i == 0:
        chars = ""

    row = Row.pack(
        ord(first_char),
        (i << 4) | k, j,
        len(chars),
        len(raw_data),
    )
    row += chars.encode("ascii")
    row += raw_data
    parts.extend(row)

parts = bytes(parts)
print(parts)
print(len(parts))
print(len(str(parts)))
print(len(str(zlib.compress(parts, 9))))

Autista di prova

#!/usr/bin/python3
import json
import subprocess
import sys

with open("data.json") as f:
    data = json.load(f)

for name, char in data.items():
    stdout = subprocess.check_output(["python3", sys.argv[1], name])
    stdout = stdout.decode().rstrip("\n")
    if char != stdout:
        print("mismatch for {!r}: {!r} != {!r}".format(
            name, char, stdout
        ))

0

awk 73 + 2060 byte

s{while(!(i=index(s,$0)))sub(/.$/,"");print substr(s,i+length(),1)}{s=$0}

I dati sono stati preparati a questo:

  "Aleax": "A",            Al A     # first of alphabet truncate to len=1
  "Angel": "A",            An A
  "Arch Priest": "@",      Arch @   # this needs to come
  "Archon": "A",           Archo A  # before this
  "Ashikaga Takauji": "@", Ash @
  "Asmodeus": "&",         Asm &    

(2060 caratteri). alla stringa unica più corta con il carattere mostro aggiunto al nome e infine a questo modulo:

AlAAnAArch@ArchoAAsh@Asm&

(per contrassegnare una non corrispondenza è necessario che sia presente un carattere di fallback all'inizio della stringa) Durante la ricerca di una corrispondenza, il nome viene accorciato di carattere con carattere dalla fine fino a quando non esiste una corrispondenza e viene restituito il carattere successivo dopo la corrispondenza :

$ cat monster
Aleax
$ awk -f program.awk monsters_string monster
A

Posso ancora radere qualche byte dalla stringa dei mostri con qualche organizzazione:

AAnArch@ArchoAsh@Asm&

Considerare la dimensione dei dati con i nomi dei mostri che iniziano con Aridurrebbe da 38 byte a 22 significa ridurre la dimensione dei dati dal 2060 al 1193 in media.

questo è ancora in corso di elaborazione e la stringa di mostri verrà pubblicata un po 'più tardi.

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.