Histoire
Mon entreprise envoie un bulletin hebdomadaire à tous les membres de l'entreprise. Inclus dans ces newsletters est une énigme, avec un cri à quiconque dans l'entreprise a été le premier à envoyer un e-mail / fournir une solution à l'énigme de la semaine dernière. La plupart de ces énigmes sont assez banales et honnêtement assez ennuyeuses pour une entreprise de technologie, mais il y en a eu un, il y a plusieurs mois, qui a attiré mon attention.
L'énigme originale:
Compte tenu de la forme ci-dessous:
Vous avez les nombres naturels de 1 à 16. Ajustez-les tous dans cette forme, de sorte que toutes les lignes et colonnes contiguës totalisent jusqu'à 29.
Par exemple, une solution à ce casse-tête (qui était la solution "canonique" que j'ai soumise à la newsletter) était la suivante:
Cependant, au cours de sa résolution, j'ai trouvé des informations assez intéressantes:
- Il y a beaucoup plus de solutions que celle-là; en fait, il existe 9 368 solutions.
- Si vous développez l'ensemble de règles pour exiger uniquement que les lignes et les colonnes soient égales les unes aux autres, pas nécessairement 29, vous obtenez 33608 solutions:
- 4 440 Solutions pour une somme de 27.
- 7 400 Solutions pour une somme de 28.
- 9 368 Solutions pour une somme de 29.
- 6 096 Solutions pour une somme de 30.
- 5 104 Solutions pour une somme de 31.
- 1200 Solutions pour une somme de 32.
Alors mes collègues et moi (mais surtout juste mon manager, car il était la seule autre personne que moi à avoir des compétences en programmation "à usage général") nous sommes lancés dans un défi, qui a duré pendant la majeure partie du mois - nous avions un autre travail réel - obligations connexes auxquelles nous devions nous conformer: essayer d'écrire un programme qui trouverait chaque solution de la manière la plus rapide possible.
Statistiques originales
Le tout premier programme que j'ai écrit pour résoudre le problème a simplement vérifié les solutions aléatoires encore et encore et s'est arrêté lorsqu'il a trouvé une solution. Si vous avez fait une analyse mathématique de ce problème, vous savez probablement déjà que cela n'aurait pas dû fonctionner; mais en quelque sorte, j'ai eu de la chance, et il n'a fallu au programme qu'une minute pour trouver une solution unique (celle que j'ai publiée ci-dessus). Les répétitions du programme prenaient souvent jusqu'à 10 ou 20 minutes, donc ce n'était évidemment pas une solution rigoureuse au problème.
Je suis passé à une solution récursive qui a itéré à travers toutes les permutations possibles du puzzle, et j'ai rejeté de nombreuses solutions à la fois en éliminant les sommes qui ne s'additionnaient pas. IE si la première ligne / colonne que je comparais n'était déjà pas égale, je pouvais arrêter de vérifier cette branche immédiatement, sachant que rien d'autre permuté dans le puzzle ne changerait cela.
En utilisant cet algorithme, j'ai obtenu le premier succès "correct": le programme a pu générer et cracher toutes les 33 608 solutions en environ 5 minutes.
Mon manager avait une approche différente: sachant sur la base de mon travail que les seules solutions possibles avaient des sommes de 27, 28, 29, 30, 31 ou 32, il a écrit une solution multi-thread qui vérifiait les sommes possibles uniquement pour ces valeurs spécifiques. Il a réussi à exécuter son programme en seulement 2 minutes. J'ai donc réitéré; J'ai haché toutes les sommes possibles de 3/4 chiffres (au début du programme; il est compté dans le temps d'exécution total) et j'ai utilisé la "somme partielle" d'une ligne pour rechercher la valeur restante sur la base d'une ligne précédemment complétée, plutôt que tester toutes les valeurs restantes et réduire le temps à 72 secondes. Ensuite, avec une logique multi-threading, je l'ai réduit à 40 secondes. Mon manager a ramené le programme à la maison, a effectué quelques optimisations sur la façon dont le programme a fonctionné et est descendu à 12 secondes. J'ai réorganisé l'évaluation des lignes et des colonnes,
Le plus rapide que nous ayons obtenu nos programmes après un mois était de 0,15 seconde pour mon manager et de 0,33 seconde pour moi. J'ai fini par affirmer que mon programme était plus rapide, car le programme de mon manager, bien qu'il ait trouvé toutes les solutions, ne les imprimait pas dans un fichier texte. S'il ajoutait cette logique à son code, cela prenait souvent plus de 0,4-0,5 seconde.
Depuis, nous avons laissé subsister notre défi intra-personnel, mais bien sûr, la question demeure: ce programme peut- il être accéléré?
C'est le défi que je vais vous poser.
Votre défi
Les paramètres sur lesquels nous avons travaillé ont assoupli la règle de la «somme de 29» pour qu'elle soit «toutes les sommes des lignes / colonnes égales», et je vais également définir cette règle pour vous. Le défi est donc: Ecrire un programme qui trouve (et imprime!) Toutes les solutions à cette énigme dans les plus brefs délais. Je vais fixer un plafond aux solutions soumises: si le programme prend plus de 10 secondes sur un ordinateur relativement décent (<8 ans), c'est probablement trop lent pour être compté.
De plus, j'ai quelques bonus pour le puzzle:
- Pouvez-vous généraliser la solution pour qu'elle fonctionne pour n'importe quel ensemble de 16 chiffres, pas seulement
int[1,16]
? Le score de synchronisation serait évalué sur la base de l'ensemble de numéros d'invite d'origine, mais passé par ce chemin de code. (-dix%) - Pouvez-vous écrire le code d'une manière qu'il gère avec élégance et résout avec des nombres en double? Ce n'est pas aussi simple que cela puisse paraître! Les solutions «visuellement identiques» doivent être uniques dans l'ensemble de résultats. (-5%)
- Pouvez-vous gérer des nombres négatifs? (-5%)
Vous pouvez également essayer de générer une solution qui gère les nombres à virgule flottante, mais bien sûr, ne soyez pas choqué si cela échoue complètement. Si vous trouvez une solution robuste, cela pourrait valoir un gros bonus!
À toutes fins utiles, les «rotations» sont considérées comme des solutions uniques. Ainsi, une solution qui n'est qu'une rotation d'une solution différente compte comme sa propre solution.
Les IDE que je travaille sur mon ordinateur sont Java et C ++. Je peux accepter les réponses d'autres langues, mais vous devrez peut-être également fournir un lien vers où obtenir un environnement d'exécution facile à configurer pour votre code.
la source
Réponses:
C - près de 0,5 sec
Ce programme très naïf donne toutes les solutions en une demi-seconde sur mon portable de 4 ans. Pas de multithread, pas de hachage.
Windows 10, Visual Studio 2010, CPU core I7 64 bits
Essayez en ligne sur ideone
la source
int inuse[16];
par justeint inuse;
et ensuite utiliser des opérateurs au niveau du bit pour le manipuler. Il ne semble pas augmenter la vitesse que beaucoup, mais il aide un peu.dumbbench --precision=.01 -vvv --initial=500 ./solve
C ++ - 300 millisecondes
Par demande, j'ai ajouté mon propre code pour résoudre ce puzzle. Sur mon ordinateur, il se déclenche en moyenne à 0,310 seconde (310 millisecondes), mais en fonction de la variance, il peut fonctionner aussi rapidement que 287 millisecondes. Je le vois très rarement dépasser 350 millisecondes, généralement seulement si mon système est enlisé dans une tâche différente.
Ces temps sont basés sur l'auto-évaluation utilisée dans le programme, mais j'ai également testé en utilisant une minuterie externe et j'obtiens des résultats similaires. Les frais généraux du programme semblent ajouter environ 10 millisecondes.
De plus, mon code ne gère pas tout à fait correctement les doublons. Il peut résoudre leur utilisation, mais il n'élimine pas les solutions «visuellement identiques» de l'ensemble de solutions.
la source
0.1038s +/- 0.0002
Et voici le temps pour votre code avec une sortie simplifiée:0.0850s +/- 0.0001
Ainsi, vous pouvez économiser ~ 18 ms, au moins sur ma machine. J'ai exécuté les deux versions plus de 500 fois avec des valeurs aberrantes rejetées, en utilisant un dumbbenchProlog - 3 minutes
Ce type de puzzle semble être un cas d'utilisation parfait pour Prolog. J'ai donc codé une solution dans Prolog! C'est ici:
Malheureusement, ce n'est pas aussi rapide que prévu. Peut-être que quelqu'un de plus expérimenté en programmation déclarative (ou en particulier Prolog) peut offrir quelques conseils d'optimisation. Vous pouvez appeler la règle
puzzle
avec la commande suivante:Essayez-le en ligne ici . Vous pouvez remplacer n'importe quel nombre à la place des
29
s dans le code pour générer toutes les solutions. À l'heure actuelle, toutes les 29 solutions sont trouvées en environ 30 secondes, donc pour trouver toutes les solutions possibles, il faut environ 3 minutes.la source