Dérangement et Python

Bonjour

Je me suis donné un exercice sur Python, écrire un programme derange retournant la liste des dérangements de $\{1, 2, 3, ..., n\}$ pour $n\geq 2$.

Il y a la méthode bourrin qui consiste à écrire toutes les permutations et à supprimer celles possédant un point fixe.
def perm(A, k):
    r = [[]]
    for i in range(k):
        r = [[a] + b for a in A for b in r if a not in b]
    return r

def der(n):
    A=[k for k in range(1,n+1)]
    p=perm(A,n)
    return [i for i in p if not any(i[k] == A[k] for k in range(n))]


J'ai cherché quelque chose de moins calculatoire, je me suis appuyé sur la démonstration par récurrence de leur dénombrement : $D_{n+2}=(n+1)(D_{n+1}+D_n)$ en suivant ce document : http://www.klubprepa.fr/Site/Document/ChargementExtrait.aspx?IdDocument=3680
def permuter(P,L):  # permute la liste L selon la permutation P
    R=[L[l-1] for l in P]
    return(R)

def type1(M,s):  # dérangement du "type n-1"
    L=[]
    for m in M:
        for k in range(1,s+1):
            n=permuter(m,[j for j in range(1,k)]+[s+1]+[j for j in range(k+1,s+1)])+[k]
            print(n)
            L+=[n]
    return(L)
   
def type2(M,s):   # dérangement du "type n-2"
    L=[]
    for m in M:
        for k in range(1,s+2):
            n=permuter(m,[j for j in range(1,k)]+[j for j in range(k+1,s+2)])+[k]
            n.insert(k-1,s+2)
            L+=[n]
    return(L)

def derange(n):
    L,M=[],[[2,1]]
    for k in range(3,n+1):
        L,M=M,type2(L,k-2)+type1(M,k-1)
    return(M)
>>> derange(3)
[[2, 3, 1], [3, 1, 2]]

>>> derange(4)
[[4, 3, 2, 1], [3, 4, 1, 2], [2, 1, 4, 3], [2, 3, 4, 1], [4, 3, 1, 2], [2, 4, 1, 3], [3, 4, 2, 1], [3, 1, 4, 2], [4, 1, 2, 3]]

>>> derange(5)
[[5, 3, 4, 2, 1], [3, 5, 4, 1, 2], [2, 4, 5, 1, 3], [2, 3, 1, 5, 4], [5, 4, 2, 3, 1], [4, 5, 1, 3, 2], [4, 1, 5, 2, 3], [3, 1, 2, 5, 4], [4, 3, 2, 5, 1], [4, 3, 5, 1, 2], [4, 5, 2, 1, 3], [5, 3, 2, 1, 4], [3, 4, 5, 2, 1], [3, 4, 1, 5, 2], [5, 4, 1, 2, 3], [3, 5, 1, 2, 4], [2, 5, 4, 3, 1], [5, 1, 4, 3, 2], [2, 1, 4, 5, 3], [2, 1, 5, 3, 4], [2, 3, 4, 5, 1], [5, 3, 4, 1, 2], [2, 5, 4, 1, 3], [2, 3, 5, 1, 4], [4, 3, 5, 2, 1], [4, 3, 1, 5, 2], [4, 5, 1, 2, 3], [5, 3, 1, 2, 4], [2, 4, 5, 3, 1], [5, 4, 1, 3, 2], [2, 4, 1, 5, 3], [2, 5, 1, 3, 4], [3, 4, 2, 5, 1], [3, 4, 5, 1, 2], [5, 4, 2, 1, 3], [3, 5, 2, 1, 4], [3, 5, 4, 2, 1], [3, 1, 4, 5, 2], [5, 1, 4, 2, 3], [3, 1, 5, 2, 4], [4, 5, 2, 3, 1], [4, 1, 5, 3, 2], [4, 1, 2, 5, 3], [5, 1, 2, 3, 4]]

>>> len(derange(5))
44


Je suis preneur de toutes remarques pour améliorer ce code. Je me pose notamment la question suivante : est-il possible d'écrire la fonction type2 avec une liste en compréhension ? Pour type1 je sais faire, mais pour type2 la méthode .insert() me bloque.

def type1_compact(M,s):
    return [permuter(m,[j for j in range(1,k)]+[s+1]+[j for j in range(k+1,s+1)])+[k] for m in M for k in range(1,s+1)]

Je peux me passer de la fonction permuter vue comme elle est courte.

J'ai réfléchi à intégrer les fonctions type1 et type2 directement dans derange, mais je n'en vois le gain en terme de ligne (par contre il y a probablement une économie sur les appels de fonction du coup ?).

La démonstration combinatoire de $D_n=nD_{n-1}+(-1)^n$ donnée dans ce papier devrait pouvoir permettre d'élaborer un algorithme plus efficace, mais cela me semble compliqué à écrire.

Merci.

Réponses

  • Bonjour Gilles,

    En pièce jointe, un texte que j'ai écrit il y a quelques années pour les agrégatifs:
  • Bonjour aléa,

    Merci pour ce document, je vais l'éplucher. J'en ai profité pour télécharger l'article de Jacques Désarménien qui propose une démonstration bijective de $D_n=nD_{n-1}+(-1)^n$.
  • Remarque: comme le nombre de dérangements est du même ordre que le nombre de permutations, il ne me semble pas clair qu'une énumération intelligente fasse mieux que l'algorithme grossier.
  • Empiriquement, ça va 5 à 6 fois plus vite.
    for k in range(8,13):
        s=time.time()
        der_n(k)
        t=time.time()
        derange(k)
        v=time.time()
        print(k,(t-s),(v-t),(t-s)/(v-t))
    

    8 0.18720006942749023 0.031199932098388672 6.000015283275512
    9 1.4149999618530273 0.2963998317718506 4.77395669691946
    10 18.573800325393677 3.3673999309539795 5.515769052157623
    11 241.16640090942383 39.09820008277893 6.168222588222091
    
  • Et si tu remplaces ça :
    def der(n):
        A=[k for k in range(1,n+1)]
        p=perm(A,n)
        return [i for i in p if not any(i[k] == A[k] for k in range(n))]
    

    Par ça ?
    def der(n):
        A=[k for k in range(1,n+1)]
        p=perm(A,n)
        return [i for i in p if all(i[k] != A[k] for k in range(n))]
    
    Algebraic symbols are used when you do not know what you are talking about.
            -- Schnoebelen, Philippe
Connectez-vous ou Inscrivez-vous pour répondre.