Table des matières

Fabriquez votre fichier de nombres premiers

Objectif

Une fois qu'on a une fonction qui calcule une liste des nombres premiers, on se dit:

Le code suivant sert à cela!

Code proposé

Il y avait tout de même un petit problème. Je voulais utiliser l'accélérateur psyco (voir sur ce site: http://python.jpvweb.com/mesrecettespython/psyco), mais lorsque l'accélérateur est en fonctionnement, il n'écoute pas le clavier, et donc aucun “Ctle-C” ne l'arrête. Or, pour que le fichier en cours reste valable, il faut trouver un moyen pour générer une interruption à la demande de façon qu'une instruction de fermeture du fichier puisse être exécutée. Ce que, bien sûr, l'arrêt sauvage de la console ou le reboot du PC ne saurait faire. De plus, je tiens à ce que ça fonctionne en “multiplateforme” (Windows + Linux au moins, éventuellement Mac).

Dans mes recherches, j'ai été surpris de constater que Python n'avais ni “getch()” (saisir le caractère tapé au clavier) ni “kbhit()” (savoir si une touche a été appuyée). Il a donc fallu “bricoler”:

Et… ça marche!

Pour utiliser ce programme, il faut:

Au démarrage, le programme détecte psyco et l'utilise s'il est présent.

Il cherche ensuite s'il y a un fichier “premiers.txt” dans le même répertoire

Ensuite, il ouvre le fichier en ajout, et y ajoute les nombres premiers au fur et à mesure qu'il les trouve.

Un Ctle-C ou n'importe quelle autre erreur arrête le programme, mais en fermant correctement le fichier.

Avec psyco, le calcul est très rapide compte tenu de la quantité de calcul réalisé. Sous Linux (swap=2Go), au bout de 3 ou 4 heures seulement, je suis arrivé à “2 176 005 437”, avec un fichier de 1Go!

Sous Windows XP, j'ai été bloqué à “1 339 088 837” pour “MemoryError”: il n'a pas supporté une liste aussi longue de nombres. Il est clair que même avec Linux, ce calcul échouera lorsque l'OS refusera la liste des nombres premiers en mémoire. Il faudra alors utiliser d'autres méthodes.

D'ici là, rien ne vous empêche de laisser tourner ce programme en tâche de fond pendant plusieurs mois…

Voilà le code. Il est auto-documenté:

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import os
import sys
import threading
 
# si l'accélérateur "psyco" est présent, l'utiliser:
try:
    import psyco
    psyco.full()
    print
    print u"accélérateur psyco trouvé et utilisé"
except ImportError:
    pass
 
####################################################################
# fonctions multiplateformes renvoyant le caractère d'une touche pressée du clavier
#
def getch_win():
    return msvcrt.getch()
 
def getch_lin():
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    try:
        tty.setraw(fd)
        ch = sys.stdin.read(1)
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
    return ch
 
def getch_autre():
    return None
 
####################################################################
class Clavier(threading.Thread):
    """thread clavier pour pouvoir arrêter avec Ctle-C, même avec psyco"""
 
    def __init__(self):
        threading.Thread.__init__(self)
        self.quit = False
 
    def run(self):
        while True:
            car = getch()
            if ord(car)==3:
                self.quit = True
                break  # =fin du thread
 
    def quitter(self):
        return self.quit
 
####################################################################
def trouvepremiers(fichier, p):
    """trouve de nouveaux nb 1er qui s'ajoutent aux précédents"""
 
    # calcul du 1er nb à essayer
    n = p[-1]+2
 
    # ouverture en ajout du fichier des nombres premiers
    f=open(fichier,'a')
 
    # calcul de tous les nb premiers suivants à partir de n
    try:
        while True:
            i=0
            while True:
                if p[i]*p[i]>n:
                    # => on a trouvé un nouveau nombre premier
                    p.append(n)
                    f.write("%s\n" % n)
                    break
                if (n%p[i])==0:
                    # => on a trouvé un diviseur: le nb n'est pas 1er
                    break
                i+=1
            n+=2
            if clavier.quitter():
                raise KeyboardInterrupt ("Arret du calcul sur demande")
 
    except:
        f.close()
        print
        print "%s" % sys.exc_info()[1]
        print
 
####################################################################
if __name__ == "__main__":
 
    # Calcul du nom du fichier des nombres 1er avec son chemin
    fichier = os.getcwd() + os.sep + "premiers.txt"
    print
    print u"Fichier des nombres premiers:", fichier
 
    # initialisation
    n=0
    p=[]
 
    # si le fichier existe déjà, charger son contenu
    if os.path.exists(fichier):
        try:
            f=open(fichier,'r')
            for line in f:
                p.append(int(line.rstrip("\r\n")))
            f.close()
        except:
            print
            print u"erreur d'ouverture ou de lecture du fichier existant"
            sys.exit()
        if len(p)>=3:
            n=p[-1]
            # si le fichier n'a pas le bon format, arrêter
            if p[2]!=5:
                print
                print u"erreur: mauvais format du fichier. Le renommer pour le recréer"
                sys.exit()
 
    # si le fichier n'existe pas, ou est vide, ou que sa dernière valeur est <5, le créer ou le recréer
    if n<5:
        print
        print u"création ou re-création du fichier"
        try:
            f=open(fichier,'w')
            f.write("2\n3\n5\n")
            f.close()
        except:
            print
            print u"erreur de création ou de re-création du fichier"
            sys.exit()
        p=[2,3,5]
        n=5
 
    print
    print u"dernier nombre premier calculé:", n
 
    # mise en place du mécanisme d'arrêt par Ctle-C, compatible avec psyco
    if sys.platform=="win32":
        import msvcrt
        getch = getch_win
    elif sys.platform=="linux2":
        import tty, termios
        getch = getch_lin
    else:
        getch = getch_autre
    clavier = Clavier()
    clavier.setDaemon(True)
    clavier.start()
 
    # lancement du calcul des nombres premiers suivants
    # arrêt par Ctle-C ou par n'importe quelle autre erreur: l'erreur est affichée
    trouvepremiers(fichier, p)