Récit
Indiana Jones explorait une grotte où se trouve un trésor précieux. Soudainement, un tremblement de terre s'est produit.
À la fin du tremblement de terre, il s’aperçut que des rochers tombés du plafond lui bloquaient le chemin vers le trésor. Il a également remarqué qu'il pouvait pousser une pierre, mais comme les pierres étaient très lourdes, il ne pouvait pas pousser deux pierres consécutives .
Votre objectif est d'aider Indiana Jones à obtenir le trésor. Comme il est très difficile de pousser même une seule pierre, le nombre de poussées est très important.
Problème
Trouvez le meilleur moyen (où Indiana Jones pousse le moins possible les pierres) pour trouver le trésor.
Carte (entrée)
La carte est une matrice m
de n
(plus que 1) pouvant contenir cinq types de cellules:
0
ce qui signifie la cellule vide,1
ce qui signifie le mur,2
dans lequel se trouve Indiana Jones (un seul existe),3
dans lequel se trouve le trésor (un seul existe),- et
4
, ce qui signifie un rocher.
Dans la première ligne de la carte, la dimension de la carte est spécifiée comme 4 6
, et de la deuxième ligne à la dernière ligne de la carte, la structure de la grotte est spécifiée à peu près comme ceci.
110131
104040
100101
200000
Par conséquent, la carte complète est:
4 6
110131
104040
100101
200000
ce qui signifie
La carte est donnée par stdin, un fichier (vous pouvez spécifier le nom du fichier) ou un tableau dans le code contenant uniquement les informations ci-dessus.
Sortie
Le montant minimum qu'Indiana Jones devrait pousser. S'il n'y a pas moyen, sortie X
.
Dans le cas ci-dessus , il peut pousser une pierre dans la gauche vers le haut, puis il peut pousser une pierre dans la droite pour récupérer le trésor. Par conséquent, la sortie dans ce cas est 2
.
Toutefois. dans ce cas :
4 6
111131
104040
100101
200000
(voir section ci-dessous) il ne peut pas pousser la pierre droite car elle détruira le trésor. De plus, pousser la pierre de gauche à droite ne change rien. Par conséquent, la sortie est X
.
Règles
- Il ne peut se déplacer que dans quatre directions: haut, bas, gauche et droite.
- Il ne peut pas pousser deux pierres consécutives .
- Il ne peut tirer aucune pierre et il ne peut pousser une pierre que dans une seule direction («en avant»).
- Il ne peut pas traverser les murs. Seuls les endroits où il peut aller sont les cellules vides et la cellule de trésor.
- La pierre ne peut pas être placée sur le trésor. Cela va détruire le trésor. :(
- Il ne peut pas sortir de la carte.
Buts
Programme qui peut gérer le plus grand nombre de cartes (fourni à la section «Exemple» + autres) dans un délai raisonnable (plus précisément, 10 secondes) et produit la bonne réponse gagne.
Ici, 'autres' signifie des exemples d'entrées fournies dans les réponses. Cela signifie que vous devez créer un algorithme intelligent afin que d'autres programmes ne puissent pas résoudre les cartes que votre programme peut résoudre, et les cartes résolues par d'autres programmes peuvent être résolues par votre programme. Cependant, mettre des solutions dans le code ne sera pas considéré comme valide.
Remarque
C'était à l'origine un projet à mi-parcours d'une classe d'IA que j'ai écouté, une seule chose était différente: on disait qu'il n'y avait que deux roches.
Il a été dit que ce problème est NP, mais il a également été dit qu'un bon algorithme heuristique peut résoudre le problème assez efficacement. J'ai utilisé des idées et des méthodes heuristiques pour résoudre le problème efficacement et mon code a pu trouver toutes les solutions d'échantillons très rapidement (moins d'une seconde).
Cependant, quand il y avait plus de deux roches, il y avait des cas où le code ne pouvait pas trouver la réponse dans un délai raisonnable. J'avais des idées, mais certaines étaient "fausses" et je ne pouvais pas exprimer d'autres idées dans le code. Je voulais voir quels algorithmes intelligents existent pour résoudre ce problème, alors j'ai écrit ceci.
Depuis que j'ai déjà terminé le projet (d'ailleurs, les images ne sont pas les miennes - je les ai trouvées sur Google), vous n'avez pas à vous en préoccuper.
Exemples
Des exemples peuvent être vus ici. Vous pouvez également voir des exemples et tester vos résultats ici (cela devrait fonctionner dans les navigateurs modernes). Vous pouvez obtenir la carte dans le format décrit ci-dessus en tapant whatisthis()
dans la console JS.
http://bit.sparcs.org/~differ/sokoban/#0 ~ http://bit.sparcs.org/~differ/sokoban/#19 contient des exemples dont il s'agissait à l'origine de la classe fournie.
Résultat
Désolé, j'étais en retard ... beaucoup en fait. : P (j'étais trop paresseux pour marquer. Désolé.)
Voici le résultat. (X: faux, O: correct,?: Prend au moins 10 secondes, arrêté)
Map#: 0 1 2 3 4 5 12 15 19 T1 T2 T3 T4 T5 T6 T7
Ruby: X O O ? O O O X ? ? O ? ? ? ? ?
Java: O O X O X X O O ? ? O O O X O O
(Java 19: a pris 25 secondes, le résultat était correct.) (J'ai utilisé ruby 1.9.3 et javac 1.7.0_13)
Il semble que l'algorithme Java était effectivement meilleur. (Au fait, j'ai pensé à une méthode similaire, mais je me suis rendu compte que des cartes similaires à la carte test 5 existent.)
la source
Réponses:
Java - Un peu plus intelligent / plus rapide
Un peu de code là-bas. Je tente d’être plus rapide en évaluant les poussées dans l’ordre suivant: "quelle est la probabilité que cela libère un chemin vers le trésor", qui est lui-même basé sur deux traversées de Dijkstra (l’une s’arrête lorsque vous rencontrez des rochers, l’autre ignore les rochers). Cela fonctionne plutôt bien, et l'exemple de la commande pastebin qui semble gênant pour l'auteur est résolu en 2 secondes environ par cette implémentation. Certains autres exemples prennent jusqu'à 30 à 40 secondes, ce que je trouve encore trop long, mais je ne pouvais pas trouver un moyen d'améliorer cela sans casser des trucs :)
J'ai divisé mes données en plusieurs fichiers pour obtenir une meilleure structure (et pourquoi je suis passé de Java à Ruby):
Point d'accès:
Enum assistant de direction:
Une classe abstraite pour représenter une partie localisée du "labyrinthe":
Et enfin, le labyrinthe lui-même:
la source
Ruby - Énorme et bloster
Une mise en œuvre quelque peu naïve qui force brute son chemin à travers le labyrinthe. Ce n'est pas très rapide dans certains cas (pas si) étranges. Il peut être amélioré en trouvant de meilleures heuristiques que simplement "si c'est plus proche du trésor, nous voudrons étudier en premier", mais les idées générales sont là.
Cela vous montrera également comment Indiana a mis la main sur le trésor au cas où il le pourrait, c'est un bonus.
Edit: J’ai trouvé un moyen d’améliorer considérablement les performances de cette situation dans des situations non évidentes (où elle aspire actuellement des œufs verts) en laissant tomber une simple évaluation du mouvement (par exemple, ne vous souciez que du moment où l’indy pousse des rochers et / ou va au trésor). Je mettrai probablement le code à jour plus tard, une fois que j'aurai eu le temps de le mettre en œuvre.
la source
C ++ 14 sur 16
L'algorithme est inefficace et gourmand en mémoire. De plus, je ne pouvais pas me permettre le temps de tout ranger, mais je le ferai quand j'ai plus de temps;) Un point intéressant est que mon algorithme échoue aux mêmes cartes de test que le questionneur. Sur mon ancien carnet de notes, le processus commence par la permutation des cartes T4 et T6. La carte 3 prend beaucoup de temps, mais se résout dans le temps. Tous les autres sont résolus presque instantanément. Je vais donc devoir comprendre comment résoudre T4 et T6 et essayer l'algorithme sur une machine disposant de plus de mémoire. Finalement, je peux résoudre T4 et T6. Je garderai le message à jour ...
Voici le résultat. (X: faux, O: correct,?: Prend au moins 10 secondes, arrêté)
Comme le code source est assez long et pas très agréable à lire ... En gros, il ne recherche que tous les rochers pouvant être atteints par Indiana Jones. Pour les roches pouvant être atteintes, il stocke les informations dans lesquelles il peut être déplacé. Ainsi, une liste de déplacements possibles pour la carte actuelle est créée. Pour chacun de ces déplacements possibles, une copie de la carte est créée et le déplacement est appliqué. Pour les cartes nouvellement créées, l'algorithme vérifie à nouveau quels mouvements peuvent être appliqués ... Ceci est fait jusqu'à ce que plus aucun mouvement ne soit possible ou jusqu'à ce qu'un chemin vers le coffre au trésor soit trouvé. Lorsque l'algorithme tente d'abord tous les mouvements qui ne prendraient qu'un mouvement pour atteindre la poitrine, tous ceux qui en prendraient deux, et ainsi de suite ... le premier moyen trouvé est aussi automatiquement le plus court. Pour éviter les boucles, l’algorithme mémorise pour chaque carte les déplacements pouvant être appliqués. Si une autre carte crée une liste de déplacements déjà trouvés auparavant, ils sont supprimés en mode silencieux car ils sont déjà en cours de traitement. Malheureusement, il n'est pas possible d'exécuter chaque mouvement une seule fois, car il se peut que certaines cartes nécessitent de déplacer une roche plusieurs fois sur le même champ. Sinon, je pourrais économiser beaucoup de mémoire. En plus de résoudre des cartes telles que la carte 3 dans le temps, l'algorithme ignore toutes les roches qui peuvent être parcourues ... Ainsi, sur la carte 3, la roche au milieu de nulle part sera déplacée, mais jusqu'à ce qu'il n'y ait plus de murs autour. Le code peut être compilé avec g ++ --std = c ++ 0x avec g ++ version 4.4.3 ou plus récente. s Il n’est pas possible d’exécuter chaque mouvement une seule fois, car il se peut que certaines cartes nécessitent de déplacer plusieurs fois une pierre sur le même champ. Sinon, je pourrais économiser beaucoup de mémoire. En plus de résoudre des cartes telles que la carte 3 dans le temps, l'algorithme ignore toutes les roches qui peuvent être parcourues ... Ainsi, sur la carte 3, la roche au milieu de nulle part sera déplacée, mais jusqu'à ce qu'il n'y ait plus de murs autour. Le code peut être compilé avec g ++ --std = c ++ 0x avec g ++ version 4.4.3 ou plus récente. s Il n’est pas possible d’exécuter chaque mouvement une seule fois, car il se peut que certaines cartes nécessitent de déplacer plusieurs fois une pierre sur le même champ. Sinon, je pourrais économiser beaucoup de mémoire. En plus de résoudre des cartes telles que la carte 3 dans le temps, l'algorithme ignore toutes les roches qui peuvent être parcourues ... Ainsi, sur la carte 3, la roche au milieu de nulle part sera déplacée, mais jusqu'à ce qu'il n'y ait plus de murs autour. Le code peut être compilé avec g ++ --std = c ++ 0x avec g ++ version 4.4.3 ou plus récente. mais seulement jusqu'à ce qu'il n'y ait plus de murs autour. Le code peut être compilé avec g ++ --std = c ++ 0x avec g ++ version 4.4.3 ou plus récente. mais seulement jusqu'à ce qu'il n'y ait plus de murs autour. Le code peut être compilé avec g ++ --std = c ++ 0x avec g ++ version 4.4.3 ou plus récente.
Edit: le programme tire son entrée de stdin et ignore la première ligne contenant la taille de la carte. Il vérifie si seuls les caractères autorisés sur la carte sont utilisés, mais ne vérifie pas qu'il n'y ait qu'un seul Indiana Jones et un seul coffre au trésor. Il est donc possible de placer plus d'un mouvement et le moins de mouvements requis pour atteindre l'un des coffres est imprimé sur la sortie standard. Tous les caractères non valides de la carte sont ignorés et le programme essaiera de calculer le moins de déplacements possible pour la carte obtenue. Le calcul commence lorsque stdin est fermé (sur mon système, ctrl + d).
la source