Voici un problème de programmation simple de SPOJ: http://www.spoj.com/problems/PROBTRES/ .
Fondamentalement, vous êtes invité à sortir le plus grand cycle de Collatz pour les nombres entre i et j. (Le cycle de Collatz d'un nombre $ n $ est le nombre d'étapes pour éventuellement passer de $ n $ à 1.)
Je cherchais un moyen Haskell pour résoudre le problème avec des performances comparatives à celles de Java ou C ++ (afin de s'adapter à la limite d'exécution autorisée). Bien qu'une solution Java simple qui mémorise la durée des cycles déjà calculés fonctionnera, je n'ai pas réussi à appliquer l'idée d'obtenir une solution Haskell.
J'ai essayé le Data.Function.Memoize, ainsi que la technique de mémorisation du temps de connexion brassée à la maison en utilisant l'idée de ce post: /programming/3208258/memoization-in-haskell . Malheureusement, la mémorisation rend en fait le calcul du cycle (n) encore plus lent. Je crois que le ralentissement vient des frais généraux de la voie Haskell. (J'ai essayé de courir avec le code binaire compilé, au lieu d'interpréter.)
Je soupçonne également que la simple itération des nombres de i à j peut être coûteuse ($ i, j \ le10 ^ 6 $). J'ai donc même essayé de tout précalculer pour la requête de plage, en utilisant l'idée de http://blog.openendings.net/2013/10/range-trees-and-profiling-in-haskell.html . Cependant, cela donne toujours l'erreur "Time Limit Exceeding".
Pouvez-vous aider à informer un programme Haskell compétitif soigné pour cela?
la source
Réponses:
Je répondrai en Scala, parce que mon Haskell n'est pas aussi frais, et donc les gens vont croire que c'est une question d'algorithme de programmation fonctionnelle générale. Je m'en tiendrai aux structures de données et aux concepts qui sont facilement transférables.
Nous pouvons commencer avec une fonction qui génère une séquence collatz, qui est relativement simple, sauf pour avoir besoin de passer le résultat en argument pour le rendre récursif:
Cela met en fait la séquence dans l'ordre inverse, mais c'est parfait pour notre prochaine étape, qui est de stocker les longueurs dans une carte:
Vous appelleriez cela avec la réponse de la première étape, la longueur initiale et une carte vide, comme
calculateLengths(collatz(22), 1, Map.empty))
. C'est ainsi que vous mémorisez le résultat. Maintenant, nous devons modifiercollatz
pour pouvoir utiliser ceci:Nous éliminons le
n == 1
contrôle car nous pouvons simplement initialiser la carte avec1 -> 1
, mais nous devons ajouter1
aux longueurs que nous mettons dans la carte à l'intérieurcalculateLengths
. Il retourne maintenant également la longueur mémorisée où il a cessé de se reproduire, que nous pouvons utiliser pour initialisercalculateLengths
, comme:Maintenant que nous avons des implémentations relativement efficaces des pièces, nous devons trouver un moyen d'introduire les résultats du calcul précédent dans l'entrée du calcul suivant. Cela s'appelle un
fold
, et ressemble à:Maintenant, pour trouver la réponse réelle, il nous suffit de filtrer les clés de la carte entre la plage donnée et de trouver la valeur maximale, donnant un résultat final de:
Dans mon REPL pour les plages de taille 1000 ou plus, comme l'exemple d'entrée, la réponse revient à peu près instantanément.
la source
Karl Bielefeld a déjà bien répondu à la question, je vais juste ajouter une version Haskell.
D'abord une version simple et non mémorisante de l'algorithme de base pour montrer la récursivité efficace:
Cela devrait être presque explicite.
Moi aussi, j'utiliserai un simple
Map
pour stocker les résultats.Nous pouvons toujours rechercher nos résultats finaux dans le magasin, donc pour une seule valeur, la signature est
Commençons par le cas final
Oui, nous pourrions ajouter cela à l'avance, mais je m'en fiche. Prochain cas simple s'il vous plaît.
Si la valeur est là, elle l'est. Je ne fais toujours rien.
Si la valeur n'est pas là, nous devons faire quelque chose . Mettons le dans une fonction locale. Remarquez à quoi ressemble cette partie très proche de la solution "simple", seule la récursivité est un peu plus complexe.
Maintenant, nous faisons enfin quelque chose. Si nous trouvons la valeur calculée dans le
store''
(sidenote: il y a deux surligneurs de syntaxe haskell, mais l'un est moche, l'autre est confus par le symbole premier. C'est la seule raison du double prime.), Nous ajoutons simplement le nouveau valeur. Mais maintenant ça devient intéressant. Si nous ne trouvons pas la valeur, nous devons à la fois la calculer et faire la mise à jour. Mais nous avons déjà des fonctions pour les deux! DoncEt maintenant, nous pouvons calculer une seule valeur efficacement. Si nous voulons en calculer plusieurs, nous transmettons simplement le magasin via un pli.
(C'est ici que vous pouvez initialiser le cas 1/1.)
Il ne nous reste plus qu'à extraire le maximum. Pour l'instant, il ne peut pas y avoir de valeur dans le magasin supérieure à une dans la gamme, il suffit donc de dire
Bien sûr, si vous souhaitez calculer plusieurs plages et partager le magasin entre ces calculs également (les plis sont votre ami), vous auriez besoin d'un filtre, mais ce n'est pas l'objectif principal ici.
la source
Data.IntMap.Strict
doit être utilisé.