Contexte
Le jeu informatique NetHack date de 1987, avant l’utilisation généralisée des graphiques dans les jeux informatiques. Il y a beaucoup de monstres dans le jeu, et potentiellement beaucoup de choses doivent être placées à l'écran en même temps, donc les monstres sont dessinés de manière très minimale: un monstre est simplement dessiné comme un personnage ASCII à l'écran.
En plus de la présence de nombreux monstres, il existe de nombreux types de monstres. Il peut être important de savoir qui est lequel; il faudrait réagir différemment en voyant un chaton et en voyant un dragon. En tant que tel, la majeure partie de l'ASCII est utilisée pour représenter des monstres; par exemple, un chaton est f
, et un dragon rouge est D
. Cela signifie qu'il peut être très utile de savoir à quoi ressemblera un monstre donné, car cela vous aidera à le reconnaître si vous le rencontrez plus tard dans le jeu. (Notez qu’il existe plus de types de monstres que de caractères ASCII. Certains partagent donc un dragon rouge et un dragon bleu D
.)
Tâche
Votre programme doit prendre le nom d'un monstre NetHack en entrée et générer le caractère ASCII qui le représente dans le jeu en sortie. Le programme est autorisé à supposer que l'entrée est en fait le nom d'un monstre NetHack; il peut le faire s’il souhaite bloquer, produire des résultats dénués de sens, etc. si la saisie est invalide.
Le fragment de pile suivant est un objet JSON donnant le mappage complet des entrées possibles à leurs sorties correspondantes:
Donc, fondamentalement, la tâche ici "est donnée à une clé dans le dictionnaire représenté par l'objet JSON ci-dessus, renvoyer la valeur correspondante".
Notez que ce défi est en quelque sorte une complexité inverse de kolmogorov ; au lieu de passer d'une petite entrée / vide à une grande sortie, vous passez d'une grande entrée à une petite sortie. (Il y a donc beaucoup d'informations redondantes dans l'entrée, que vous pouvez ignorer ou utiliser à votre guise). C'est aussi assez similaire au golf regex, sauf que a) n'importe quelle langue est autorisée (pas seulement le regex), et b) il y a plus de deux sorties possibles. (Nous avons déjà eu quelques tâches comme celle-ci, telles que ces deux-là , mais cette tâche est quelque peu différente car le comportement d'entrée / sortie spécifié a des motifs plus forts).
Des clarifications
Vous pouvez utiliser n’importe quel format d’entrée et de sortie raisonnable (par exemple, vous pouvez produire la sortie sous forme de caractère, sous forme de code ASCII ou sous forme de chaîne comportant un seul caractère). Vous pouvez soumettre une fonction au lieu d'un programme complet, si vous préférez.
Ceci est déjà mentionné dans les lacunes standard, mais rappelons-le simplement: vous ne pouvez pas stocker la correspondance entre entrée et sortie ailleurs que dans le code source de votre programme. Ce défi consiste essentiellement à représenter le comportement des entrées / sorties dans le plus petit espace possible. Par conséquent, vous ne devez pas effectuer de tâches telles que télécharger une liste à partir d'Internet, stocker la correspondance dans un fichier externe, démarrer NetHack en mode débogage et créer le monstre en question. pour voir à quoi ça ressemble, etc. (En outre, je ne veux pas avoir à combattre des monstres pour tester vos soumissions.)
Condition de victoire
Il s'agit d'un code-golf , de sorte que l'entrée gagnante sera l'entrée la plus courte, comptée en octets. Bonne chance!
la source
mail daemon
> _ <Réponses:
Jelly , 309 octets dans l'encodage de Jelly
Essayez-le en ligne!
J'ai décidé qu'il était temps que j'essaye de relever mon propre défi. L'utilisation de Jelly (et de sa page de codes 8 bits) me donne un avantage de 12,5% sur les langues exclusivement ASCII. Jelly est pratique pour ce défi en raison de ses opérateurs de conversion de base intégrés avec des noms abrégés, mais la plupart des économies sont dus à un meilleur algorithme de compression (ce programme a en moyenne moins d'un octet par type de monstre).
Algorithme et explication
Classification basée sur les mots
J'ai décidé que pour obtenir un bon score, il était nécessaire de tirer davantage parti de la structure de l'entrée que d'autres entrées. Une chose qui est très remarquable est que beaucoup de monstres ont des noms de la forme " espèces adjectives "; a et a sont les deux types de dragon, et apparaissent donc comme . Certains autres monstres ont des noms de la forme " travail d' espèce ", tels que le ; étant un type d'orc, cela apparaît comme . Les choses qui compliquent sont les morts-vivants; a est à la fois un kobold et un zombie, et le dernier état est prioritaire dans la dénomination de monstre NetHack, nous voudrions donc classer cela comme .
red dragon
blue dragon
D
orc shaman
o
kobold zombie
Z
En tant que tel, j'ai classé les mots qui apparaissent dans les noms de monstres de la manière suivante: un indicateur est un mot qui suggère fortement la classe de monstre appropriée (par exemple, il
sphere
suggère fortement que le monstre est en classee
); un mot ambigu est un mot qui fait beaucoup moins de suggestion (lord
ne vous en dit pas beaucoup), et tous les autres mots sont des non - mots qui ne nous intéressent pas. L'idée de base est que nous examinons les mots dans le nom du monstre de la fin au début, puis choisissons le premier indicateur que nous voyons. En tant que tel, il était nécessaire de veiller à ce que chaque nom de monstre contienne au moins un indicateur, suivi entièrement par des mots ambigus. Exceptionnellement, les mots qui apparaissent dans les noms de monstres et qui ressemblent à des@
(le groupe le plus important) sont tous classés comme ambigus. Tout peut apparaître devant un indicateur; Par exemple, les noms de couleurs (tels quered
) apparaissent toujours plus tôt dans un nom que l'indicateur et sont donc considérés comme des non-mots (car ils ne sont jamais examinés lors de la détermination de l'identité d'un monstre).En fin de compte, ce programme se résume à une table de hachage, comme le font les autres programmes. Cependant, le tableau ne contient pas d'entrées pour tous les noms de monstres, ni pour tous les mots apparaissant dans les noms de monstres; au contraire, il ne contient que les indicateurs. Les hachages de mots ambigus n'apparaissent pas dans le tableau, mais doivent être attribués à des emplacements vides (toute recherche d'un mot ambigu sera toujours vide). Pour les non-mots, peu importe que le mot apparaisse dans la table ou non, que le hachage soit en collision ou non, car nous n'utilisons jamais la valeur de la recherche d'un non-mot. (Le tableau est relativement rare, la plupart des non-mots n'apparaissent pas dans le tableau, mais quelques-uns, tels que
flesh
, figurent dans le tableau à la suite de collisions de hachage.)Voici quelques exemples du fonctionnement de cette partie du programme:
woodchuck
est un seul mot long (donc un indicateur), et la recherche de table surwoodchuck
nous donne la réponse vouluer
.abbot
est aussi un seul mot long, mais ressemble à un@
. En tant que tel,abbot
est considéré comme un mot ambigu; la recherche dans la table est vide et nous renvoyons une réponse@
par défaut.vampire lord
consiste en un indicateur (vampire
correspondant àV
) et un mot ambigu (lord
, qui ne figure pas dans le tableau). Cela signifie que nous vérifions les deux mots (dans l'ordre inverse), puis donnons la réponse correcte deV
.gelatinous cube
se compose d'un non-mot (gelatinous
correspondant àH
une collision de hachage) et d'un indicateur (cube
correspondant àb
). Comme nous ne prenons que le dernier mot trouvé dans la table, cela retourneb
, comme prévu.gnome mummy
se compose de deux indicateurs,gnome
correspondant àG
etmummy
correspondant àM
. Nous prenons le dernier indicateur et obtenonsM
ce qui est ce que nous voulons.Le code de traitement de la classification basée sur les mots est la dernière ligne du programme Jelly. Voici comment cela fonctionne:
Il y a deux cas réels; si l'entrée consiste entièrement en mots ambigus,
t0
supprime la sortie complète des recherches dans la table et nous obtenons un@
résultat par défaut; s'il y a des indicateurs dans l'entrée,t0
supprimera tout ce qui se trouve à droite de l'indicateur le plus à droite etṪ
nous donnera le résultat correspondant pour cet indicateur.Compression de la table
Bien sûr, casser l'entrée en mots ne résout pas le problème en lui-même; nous devons encore encoder la correspondance entre les indicateurs et les classes de monstres correspondantes (et le manque de correspondance de mots ambigus). Pour ce faire, j'ai construit un tableau fragmenté avec 181 entrées utilisées (correspondant aux 181 indicateurs; ceci représente une grande amélioration par rapport aux 378 monstres!) Et 966 entrées au total (correspondant aux 966 valeurs de sortie de la fonction de hachage). La table est encodée dans le programme via l’utilisation de deux chaînes: la première chaîne spécifie les tailles des "espaces" de la table (qui ne contiennent aucune entrée); et la deuxième chaîne spécifie la classe de monstres qui correspond à chaque entrée. Celles-ci sont toutes deux représentées de manière concise via une conversion de base.
Dans le programme Jelly, le code pour la recherche de table, ainsi que le programme lui-même, est représenté dans la deuxième ligne, à partir de la première
µ
. Voici comment cette partie du programme fonctionne:La base bijective 21 est semblable à la base 21, sauf que 21 est un chiffre légal et que 0 ne l’est pas. C’est un codage plus pratique pour nous, car nous comptons deux entrées adjacentes comme ayant un espace de 1, de sorte que nous puissions trouver les index valides via une somme cumulative. Pour ce qui est de la partie de la table qui contient les valeurs, nous avons 58 valeurs uniques. Nous décodons d’abord en 58 entiers consécutifs, puis décodons à nouveau à l’aide d’une table de correspondance qui les met en correspondance avec les caractères réellement utilisés. (La plupart de ces lettres sont des lettres. Nous commençons donc cette table de recherche secondaire par les entrées autres que des lettres,
&;:'
puis nous ajoutons simplement une constante Jelly qui commence par les alphabets majuscules et minuscules; à propos de ça.)La valeur sentinelle de la gelée "index introuvable", si vous l'utilisez pour indexer une liste, renvoie le dernier élément de la liste; ainsi, j'ai ajouté un zéro (un entier zéro, même si la table est principalement composée de caractères) à la table de recherche pour donner une sentinelle plus appropriée pour indiquer une entrée manquante.
Fonction de hachage
La partie restante du programme est la fonction de hachage. Cela commence simplement assez, avec
OḌ
; ceci convertit la chaîne d'entrée en ses codes ASCII, puis calcule le dernier code, plus 10 fois l'avant-dernier code, plus 100 fois le code précédent, etc. (la représentation est très courte dans Jelly car elle est plus couramment utilisée chaîne de caractères → fonction de conversion entière). Cependant, si nous réduisions simplement ce hachage directement via une opération de module, nous aurions besoin d'une table assez grande. Alors au lieu de cela, je commence avec une chaîne d'opérations pour réduire la table. Ils fonctionnent chacun comme ceci: nous prenons le cinquième pouvoir de la valeur de hachage actuelle; nous réduisons ensuite la valeur modulo d'une constante (laquelle dépend de l'opération que nous utilisons). Cette chaîne génère plus d'économies (en termes de réduction de la taille de la table résultante) qu'elle n'en coûte (en termes de nécessité de coder la chaîne d'opérations elle-même), de deux manières: elle peut créer la tablebeaucoup plus petit (966 entrées que 3529), et l'utilisation de plusieurs étapes donne plus de possibilités d'introduire des collisions bénéfiques (cela ne s'est pas produit beaucoup, mais il y en a une: les deuxDeath
et leYeenoghu
hash jusqu'à 806, ce qui nous permet d'en supprimer une entrée de la table, car ils vont tous les deux à&
) Les modules utilisés ici sont [3529, 2163, 1999, 1739, 1523, 1378, 1246, 1223, 1145, 966]. Incidemment, la raison pour passer à la cinquième puissance est que si vous prenez simplement la valeur directement, les espaces ont tendance à rester de la même taille, alors que l’exponentiation déplace les espaces et peut permettre à la table d’être distribuée plus uniformément après la suppression. chaîne plutôt que de rester coincé dans un minimum local (des écarts plus équitablement répartis permettent un codage plus précis de la taille des écarts). Ce doit être une puissance impaire afin de prévenir le fait que x ² = (- x ) ² introduisant des collisions, et 5 mieux fonctionné que trois.La première ligne du programme code la séquence de modules en utilisant le codage delta:
Le reste du programme, le début de la deuxième ligne, implémente la fonction de hachage:
Vérification
C'est le script Perl que j'ai utilisé pour vérifier que le programme fonctionne correctement:
la source
JavaScript (ES6),
915...902890 octetsFormaté
Vous trouverez ci-dessous une version formatée du code avec des données utiles tronquées.
Comment ça marche
Étape 1
Nous réduisons d'abord le nom du monstre par:
1
'.Exemples:
Cela conduit à quelques collisions. Par exemple,
"Master Assassin"
et"Master Kaen"
sont tous deux réduits à"Mst1n"
. Heureusement, tous les noms de monstres en collision partagent le même symbole (@
dans ce cas).Étape 2
Ensuite, nous interprétons cette chaîne de 5 caractères comme une quantité de base 36 pour la convertir en décimal (cette opération ne fait pas la distinction entre les majuscules et les minuscules) et nous appliquons un modulo
8713
, qui a été choisi empiriquement pour produire une liste de clés sans collision.Exemples:
Étape 3
Toutes les clés sont triées par ordre croissant:
Converti en valeurs delta:
Et codé en tant que caractères ASCII dans la plage
[ 32, 126 ]
. Certaines valeurs fictives intermédiaires sont insérées lorsque la différence entre deux clés consécutives dépasse la magnitude maximale pouvant être codée.Enfin, la liste des clés est mappée sur une liste de symboles disposés dans le même ordre.
Tester
Afficher l'extrait de code
la source
Java, 1130 octets
Ungolfed:
Les noms de monstres sont:
hashcode
méthode Java => 32 bitsLe caractère d'affichage est codé sur 6 bits.
Ainsi, chaque tuple (nom du monstre, personnage) utilise 14 bits. Tous les tuples sont enregistrés dans un BitSet et en base 64 codés.
Je perds beaucoup d'octets avec l'encodage base64 et les opérations BitSet :-)
la source
()->{...}
. La question le dit dans sa section "clarifications".Mathematica, 1067 octets (encodage en caractères latins Mac OS)
Fonction sans nom prenant une chaîne en entrée et renvoyant un caractère. La fonction a la forme suivante:
Ici, GIANT_STRING_1 est une chaîne contenant 608 caractères d'un octet dans le codage en caractères latéraux Mac OS (aucun d'entre eux ne se trouve dans la plage 00-1F), tandis que GIANT_STRING_2 est une chaîne contenant 304 caractères ASCII.
La ligne 2 lance la fonction de hachage: elle convertit la chaîne d'entrée en une liste de codes de caractères (encodage non pertinent puisqu'ils sont tous imprimables en ASCII), puis calcule la somme de ces codes de caractères et de la somme de leurs carrés, modulo 216 et forcés. la réponse doit se situer entre 32 et 255. Les lignes 1 et 3 convertissent ensuite ces paires ordonnées d'entiers en chaînes de deux caractères, qui sont la valeur de hachage que nous utilisons finalement.
La ligne 5 transforme GIANT_STRING_1 en une liste de 304 chaînes de deux caractères; La ligne 6 transforme GIANT_STRING_2 en une liste de 304 chaînes à un caractère. Les lignes 4 et 5 convertissent ensuite ces deux listes en un ensemble de 304 règles de remplacement: si vous voyez telle chaîne de deux caractères, transformez-la en telle chaîne de un caractère. Enfin, la ligne 8 transforme toute chaîne de deux caractères restante en
"@"
.Il y a 71 monstres dans la liste dont le symbole est
"@"
, et ceux-ci sont gérés sans hachage (j'ai volé cette idée d'un commentaire d'aïs523 sur une autre réponse). Il se trouve que les 304 autres valeurs de hachage sont toutes uniques! et donc aucune autre modification de l'algorithme n'est nécessaire. (C'est un coup de chance qui"human"
mérite d'être mis en correspondance"@"
, car les sommes des codes de caractères des lettres"human"
et des lettres"shark"
sont identiques, de même que les sommes des carrés de ces codes - en tant qu'entiers, pas même le modulo 216!)la source
Python, 2055 octets
Voici mon test harnais, au cas où cela aiderait quelqu'un d'autre.
J'ai écrit un petit programme pour énumérer toutes les manières d'extraire 4 caractères plus la longueur de la chaîne. Mon plan initial avait alors été de faire en
ord()
sorte que ces caractères soient calculés et résumés en une fonction de hachage parfaite produisant des index dans un tableau de résultats. J'ai donc écrit un autre petit programme pour énumérer toutes les façons de résumer / multiplier / moduler ces 4 personnages ensemble; mais les fonctions de hachage résultantes ont eu beaucoup trop de collisions. Donc, finalement, j'ai abandonné et je viens de faire ce que vous voyez ici, à savoir une carte allant de la représentation en petite chaîne du nom de chaque monstre au symbole approprié.C’est-à-dire que ce que je voulais obtenir était
mais j'ai seulement réussi à aller aussi loin que
où ma dict dictup
{relatively_large_dict}[small_string]
est exprimée commere.match(small_string+"(.)", "relatively_large_string")
pour golfiness.la source
JavaScript (ES6), 1178
Moins joué au golf
Tester
la source
Javascript, 1185 octets
Utilise une version golfée de la chaîne de hachage Javascript trouvée ici. Le hachage réel stocké dans la table (cette longue chaîne) prend la valeur absolue du hachage produit par cette méthode, le convertit en base 36 et supprime tous les chiffres sauf les trois moins significatifs.
la source
@
de la table et en les remplaçant par défaut@
si n'est pas trouvé.cavewoman
etchameleon
avez le même premier caractère, dernier caractère et longueur, cela peut être un problème?split("_")
peut devenirsplit
backtick_
backtickCyclops
etCroesus
,baluchitherium
etbaby long worm
,crocodile
etcentipede
, et 24 autresPython 3,
19151900 octetsChangelog:
Passez le nom du monstre en premier argument de ligne de commande, recevez le caractère sur stdout.
Quand j'ai lu la question, j'ai pensé "j'ai besoin de compresser ceci". La première étape consistait à mettre tous les noms en minuscule.
En regardant les données, je me suis dit que, d'une manière ou d'une autre, utiliser la première lettre du dernier mot devrait suffire à tromper approximativement les lettres que le monstre pourrait avoir. Il s’est avéré que c’était une hypothèse initiale puissante. Le tableau suivant contient "le premier caractère du dernier mot", "le nombre de résultats", "les caractères du monstre":
Pour améliorer encore l'étalement, j'ai légèrement modifié la clé, en ajoutant XOR au deuxième caractère du dernier mot, décalé en bits à droite, dans le premier caractère (appelons cette construction
first_key
):Comme vous pouvez le constater, cela nous donne neuf noms qui peuvent uniquement être mappés avec cette information. Agréable!
Maintenant, je devais trouver le mappage restant. Pour cela, j'ai commencé par convertir le nom complet (en minuscule) en un entier:
Ceci consiste simplement à concaténer les valeurs ASCII à 7 bits des noms en un entier énorme. Nous prenons ce modulo
4611686018427387903
(2⁶²-1) pour les prochaines étapes.Maintenant, j'essaie de trouver un masque de bits qui donne un entier qui à son tour distingue bien les différents personnages de monstres. Les masques de bits sont constitués de masques uniformément répartis (tels que
101010101
ou1000100010001
) et sont paramétrés par le nombre de bits (i>=1
) et le nombre d'étalement (k>=1
). De plus, les masques sont décalés à gauche pour un maximum de32*i
bits. Celles-ci sont associées à AND avec le nom en entier et l'entier résultant est utilisé comme clé dans un mappage. La meilleurei*number_of_mapping_entries
cartographie (évaluée par ) sans conflit est utilisée.Les entiers obtenus à partir de ET-tion du masque et le nom integerised sont décalés en arrière par
j
morceaux et dépouillés de leurs zéros (nous stockonsi
,k
et enj
même temps que la mise en correspondance pour pouvoir reconstruire cela), économiser beaucoup d'espace.Nous avons donc maintenant un mappage à deux niveaux: de
first_key
à la table de hachage, et la table de hachage mappe le nom complet de manière unique sur le caractère monstre. Nous devons stocker cela en quelque sorte. Chaque entrée du mappage de niveau supérieur ressemble à ceci:suivi des personnages de monstre et de la cartographie de second niveau.
Le mappage de deuxième niveau est sérialisé en le compressant dans un grand entier et en le convertissant en octets. Chaque valeur et clé est décalée de manière consécutive dans l'entier, ce qui permet de reconstituer le mappage (le nombre de bits par clé / valeur est déductible du nombre de caractères et est
i
stocké dans l'entrée de ligne).Si une entrée n'a qu'un seul caractère de monstre possible à mapper
i
, le nombre de caractères et le mappage sont égaux à zéro. Le personnage est stocké oùj
serait normalement stocké.Les données complètes ont une taille de 651 octets, sérialisées sous forme de chaîne d'octets python et de 1426 octets.
Le programme de décodage fonctionne essentiellement à l’inverse: il extrait d’abord le
first_key
et recherche dans les données l’entrée correspondante. Ensuite, il calcule le hachage du nom et cherche dans la hashmap l’entrée correspondante.Décodeur non obscurci
Outil d'analyse
Voici l'outil que j'ai créé et utilisé pour générer les données - à lire à vos risques et périls:
Pilote d'essai
la source
awk 73 + 2060 octets
Les données ont été préparées à ceci:
(2060 caractères) c'est-à-dire. à la plus courte chaîne unique avec le caractère monstre ajouté au nom et enfin à cette forme:
(il faut un caractère de repli au début de la chaîne pour marquer une non-correspondance) Lors de la recherche d'une correspondance, le nom est raccourci caractère par caractère à partir de la fin jusqu'à ce qu'il y ait une correspondance et que le prochain caractère après la correspondance soit renvoyé. :
Je peux encore raser quelques octets de la chaîne des monstres avec un peu d'organisation:
Compte tenu de la taille des données avec les noms de monstres commençant par
A
, la réduction de 38 octets à 22 signifie une réduction de la taille des données de 2060 à 1193 en moyenne.ceci est toujours en cours et la chaîne de monstres sera publiée un peu plus tard.
la source