Jelly , 309 bytes na codificação de 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”@;Ṫ
Experimente online!
Decidi que estava na hora de tentar meu próprio desafio. O uso do Jelly (e sua página de código de 8 bits) me oferece uma vantagem de 12,5% em relação aos idiomas somente ASCII, e o Jelly é conveniente para esse desafio devido a ter operadores de conversão de base incorporados com nomes abreviados, mas a maior parte da economia são devido a um algoritmo de compactação melhor (esse programa calcula a média de menos de um byte por tipo de monstro).
Algoritmo e explicação
Classificação baseada em palavras
Decidi que, para obter uma boa pontuação, era necessário aproveitar mais a estrutura da entrada do que outras entradas. Uma coisa que é muito perceptível é que muitos monstros têm nomes da forma " espécies adjetivas "; a e a são os dois tipos de dragão e, portanto, aparecem como . Alguns outros monstros têm nomes na forma " trabalho de espécies ", como o ; sendo um tipo de orc, isso aparece como . Assuntos complicadores são os mortos-vivos; a é um kobold e um zumbi, e o último estado tem precedência na nomeação de monstros do NetHack, portanto, queremos classificá-lo como .red dragon
blue dragon
D
orc shaman
o
kobold zombie
Z
Como tal, classifiquei as palavras que aparecem nos nomes dos monstros da seguinte forma: um indicador é uma palavra que sugere fortemente a classe de monstro apropriada (por exemplo sphere
, sugere fortemente que o monstro está na classe e
); uma palavra ambígua é uma palavra que sugere muito menos ( lord
não lhe diz muito) e todas as outras palavras são não-palavras com as quais não nos importamos. A idéia básica é que observemos as palavras no nome do monstro do início ao fim e escolhemos o primeiro indicador que vemos. Como tal, era necessário garantir que cada nome de monstro contivesse pelo menos um indicador, que fosse seguido inteiramente por palavras ambíguas. Como exceção, palavras que aparecem nos nomes de monstros que parecem um@
(o maior grupo) são todos classificados como ambíguos. Tudo pode aparecer antes de um indicador; por exemplo, nomes de cores (como red
) sempre aparecem mais cedo em um nome do que um indicador e, portanto, são considerados não-palavras (como nunca são examinados ao determinar a identidade de um monstro).
No final, esse programa se resume a uma tabela de hash, como os outros programas. No entanto, a tabela não contém entradas para todos os nomes de monstros ou para todas as palavras que aparecem nos nomes de monstros; pelo contrário, contém apenas os indicadores. Os hashes de palavras ambíguas não aparecem na tabela, mas devem ser atribuídos a slots vazios (a tentativa de procurar uma palavra ambígua sempre aparecerá vazia). Para não-palavras, não importa se a palavra aparece na tabela ou não, ou se o hash colide ou não, porque nunca usamos o valor de procurar uma não-palavra. (A tabela é bastante esparsa, portanto, a maioria das não-palavras não aparece na tabela, mas algumas, como flesh
, são encontradas na tabela como consequência de colisões de hash.)
Aqui estão alguns exemplos de como essa parte do programa funciona:
woodchuck
é uma única palavra longa (portanto, um indicador), e a consulta da tabela woodchuck
fornece a resposta pretendida r
.
abbot
também tem uma única palavra, mas se parece com um @
. Como tal, abbot
é considerada uma palavra ambígua; a pesquisa da tabela fica vazia e retornamos uma resposta @
por padrão.
vampire lord
consiste em um indicador ( vampire
correspondente a V
) e uma palavra ambígua ( lord
, que não está na tabela). Isso significa que verificamos as duas palavras (na ordem inversa) e fornecemos a resposta correta de V
.
gelatinous cube
consiste em uma não palavra ( gelatinous
correspondente a H
devido a uma colisão de hash) e um indicador ( cube
correspondente a b
). Como pegamos apenas a última palavra encontrada na tabela, isso retorna b
como esperado.
gnome mummy
consiste em dois indicadores, gnome
correspondentes a G
e mummy
correspondentes a M
. Tomamos o último indicador e obtemos M
, que é o que queremos.
O código para lidar com a classificação baseada em palavras é a última linha do programa Jelly. Veja como funciona:
ḲÇ€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
Existem dois casos reais; se a entrada consistir inteiramente de palavras ambíguas, t0
excluirá toda a saída das pesquisas da tabela e obteremos um @
resultado por padrão; se houver indicadores na entrada, t0
excluirá qualquer coisa à direita do indicador mais à direita e Ṫ
nos fornecerá o resultado correspondente para esse indicador.
Compressão de mesa
Obviamente, dividir a entrada em palavras não resolve o problema por si só; ainda precisamos codificar a correspondência entre indicadores e as classes de monstros correspondentes (e a falta de correspondência de palavras ambíguas). Para fazer isso, construí uma tabela esparsa com 181 entradas usadas (correspondentes aos 181 indicadores; isso é uma grande melhoria em relação aos 378 monstros!) E 966 entradas totais (correspondentes aos 966 valores de saída da função hash). A tabela é codificada no programa através do uso de duas cadeias: a primeira cadeia especifica os tamanhos das "lacunas" na tabela (que não contêm entradas); e a segunda sequência especifica a classe de monstro que corresponde a cada entrada. Ambos são representados de maneira concisa via conversão de base.
No programa Jelly, o código para a pesquisa da tabela, junto com o próprio programa, é representado na segunda linha, a partir da primeira em µ
diante. Veja como esta parte do programa funciona:
“…’ḃ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
A base bijetiva 21 é como a base 21, exceto que 21 é um dígito legal e 0 não. Essa é uma codificação mais conveniente para nós, porque contamos duas entradas adjacentes como tendo um intervalo de 1, para que possamos encontrar os índices válidos por soma cumulativa. Quando se trata da parte da tabela que contém os valores, temos 58 valores exclusivos; portanto, primeiro decodificamos em 58 números inteiros consecutivos e depois decodificamos novamente usando uma tabela de pesquisa que os mapeia nos caracteres reais que estão sendo usados. (A maioria dessas letras são letras, portanto, iniciamos esta tabela de pesquisa secundária com entradas que não são da letra &;:'
e, em seguida, apenas anexamos uma constante Jelly que começa com os alfabetos maiúsculos e minúsculos; também possui alguns outros itens indesejados, mas não nos importamos sobre isso.)
O valor sentinela "índice não encontrado" do Jelly, se você o usar para indexar em uma lista, retorna o último elemento da lista; portanto, acrescentei um zero (um número inteiro zero, mesmo que a tabela seja composta principalmente de caracteres) na tabela de pesquisa para fornecer um sentinela mais apropriado para indicar uma entrada ausente.
Função hash
A parte restante do programa é a função hash. Isso começa com bastante simplicidade, comOḌ
; isso converte a string de entrada em seus códigos ASCII e calcula o último código, mais 10 vezes o penúltimo código, mais 100 vezes o código anterior e assim por diante (isso tem uma representação muito curta no Jelly, porque é mais comumente usado como um string → função de conversão de número inteiro). No entanto, se simplesmente reduzíssemos esse hash diretamente por meio de uma operação de módulo, acabaríamos precisando de uma tabela bastante grande. Então, em vez disso, começo com uma cadeia de operações para reduzir a tabela. Cada um deles trabalha assim: tomamos a quinta potência do valor atual do hash; então reduzimos o valor do módulo uma constante (essa constante depende de qual operação estamos usando). Essa cadeia oferece mais economia (em termos de redução do tamanho da tabela resultante) do que custa (em termos de necessidade de codificar a própria cadeia de operações), de duas maneiras: pode fazer a tabelamuito menor (966 em vez de 3529 entradas), e o uso de vários estágios oferece mais oportunidades para introduzir colisões benéficas (isso não aconteceu muito, mas existe uma dessas colisões: ambas Death
e Yeenoghu
hash a 806, permitindo assim remover uma entrada da tabela, pois ambos vão para&
) Os módulos usados aqui são [3529, 2163, 1999, 1739, 1523, 1378, 1246, 1223, 1145, 966]. Aliás, a razão para aumentar para a quinta potência é que, se você pegar o valor diretamente, as lacunas tendem a permanecer do mesmo tamanho, enquanto a exponenciação move as lacunas e pode permitir que a tabela seja distribuída de maneira mais uniforme após o cadeia em vez de ficar preso no mínimo local (intervalos mais uniformemente distribuídos permitem uma codificação por terser dos tamanhos dos intervalos). Isso deve ter um poder ímpar para evitar que x ² = (- x ) ² introduza colisões, e 5 funcione melhor que 3.
A primeira linha do programa codifica a sequência de módulos usando a codificação delta:
“…’;“…‘_\
“…’ Compressed integer list encoding, arbitrary sized integers
; Append
“…‘ Compressed integer list encoding, small integers (≤ 249)
_\ Take cumulative differences
O restante do programa, o início da segunda linha, implementa a função 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ção
Este é o script Perl que usei para verificar se o programa funciona corretamente:
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
> _ <