web-dev-qa-db-fra.com

Python: boucles imbriquées

J'aimerais passer en revue tous les nombres à n chiffres de sorte que le deuxième chiffre du nombre soit toujours inférieur ou égal au premier, le troisième soit inférieur ou égal au second, etc. Je peux l'obtenir en écrivant un code horrible tel que:

for i in range(10):
    for j in range(i+1):
        for k in range(j+1):

etc., mais avec des nombres à 10 chiffres, mon code commence à être horrible, et cela fait beaucoup d'écriture, et l'indentation devient horrible si je veux en mentionner quelques-unes. Existe-t-il un moyen simple et concis d’obtenir cela?

Edit: juste pour que les gens sachent pourquoi je me préoccupe de cela, https://projecteuler.net/problem=74 m'a fait vérifier les nombres de 1 à 1 million. Malheureusement, ce n’est pas aussi simple que je le pensais: les nombres avec des zéros non significatifs sont traités différemment de ceux contenant des zéros, il a donc fallu faire de la magie supplémentaire. Quoi qu’il en soit, merci à tous pour vos suggestions judicieuses.

16
098799

Pourrait utiliser itertools:

>>> for comb in itertools.combinations_with_replacement(range(9, -1, -1), 3):
        print comb

(9, 9, 9)
(9, 9, 8)
(9, 9, 7)
(9, 9, 6)
...
(4, 0, 0)
(3, 3, 3)
(3, 3, 2)
(3, 3, 1)
(3, 3, 0)
(3, 2, 2)
(3, 2, 1)
(3, 2, 0)
(3, 1, 1)
(3, 1, 0)
(3, 0, 0)
(2, 2, 2)
(2, 2, 1)
(2, 2, 0)
(2, 1, 1)
(2, 1, 0)
(2, 0, 0)
(1, 1, 1)
(1, 1, 0)
(1, 0, 0)
(0, 0, 0)

Ou récursivement, en ajoutant de plus en plus de chiffres, ce qui permet de produire plus directement des objets int au lieu de tuples numériques (vous ne savez pas vraiment si c'est ce dont vous avez besoin):

def build(enough, prefix=0):
    if prefix >= enough:
        print(prefix)
        return
    for digit in range(prefix % 10 + 1) if prefix else range(1, 10):
        build(enough, prefix * 10 + digit)

Démo (notez qu'il ne contient pas "000", ne savez pas si vous le souhaitez de toute façon):

>>> n = 3
>>> build(10**(n-1))
100
110
111
200
210
211
220
221
222
300
310
311
320
321
322
330
331
332
333
400
410
411
420
21
Stefan Pochmann

c'est une approche utilisant itertools :

from itertools import combinations_with_replacement

N = 3

for kji in combinations_with_replacement((str(i) for i in range(10)), N):
    print(''.join(reversed(kji)))

notez que la commande n’est pas la même que dans votre approche originale.

j'ai récemment eu une question similaire ...

4
hiro protagonist

Une approche récursive simple:

def ordered_digits_generator(numDigits,min=1,max=9):
    for first in range(min,max+1):
        if numDigits == 1:
             yield first
        else:
             addend = first*10**(numDigits-1)
             for rest in ordered_digits(numDigits-1,min=0,max=first):
                 yield addend+rest

Puis appelé via:

for number in ordered_digits_generator(10):
    print number

fonctionne comme prévu.

L'approche du mathématicien

Le paquet itertools a déjà une logique qui implémente déjà essentiellement cette récursion. Probablement mieux que ce que nous pouvons, avec des tests significatifs. Nous pouvons donc l'utiliser comme suit:

import itertools
def ordered_digits_combo(numDigits):
    exponent = [10**i for i in range(0,numDigits)]

    for subset in itertools.combinations(range(0,numDigits+9),numDigits):
        if subset[numDigits-1]>numDigits-1:
            v = 0
            for i in range(0,numDigits):
                v += exponent[i]*(subset[i]-i)
            yield v

Étant donné un sous-ensemble ordonné a[0]<a[1]<...<a[n-1] de {0,1,...,n+8}, nous choisissons le numéro avec le ith chiffre de droite égal à a[i]-i. Nous devons exclure la casse a[n-1]==n-1 car elle consiste en un nombre avec tous les zéros.

3
Thomas Andrews

Je mettrais probablement ceci en œuvre de manière récursive:

def generate(max, digits):
    for d in range(max + 1):
        if digits == 1:
            yield d
        else:
            first = d * 10**(digits-1)
            for n in generate(d, digits - 1):
                yield first + n

Le résultat:

In : list(generate(3, 3))
Out:
[0,
 100,
 110,
 111,
 200,
 210,
 211,
 220,
 221,
 222,
 300,
 310,
 311,
 320,
 321,
 322,
 330,
 331,
 332,
 333]
0
Brandon Mintern

J'ai implémenté la suggestion de @ iFlo telle que commentée à l'origine. Ce n'est pas hyper efficace, mais cela ne prend certainement pas des siècles.

def digit_test(n):
    while n > 9:
        if (n % 100 / 10) < (n % 10): return False
        n /= 10
    return True

# under a second to construct a list of all numbers below 1000000 meeting the criteria
candidates = [x for x in xrange(1,1000000) if digit_test(x)]

# should be 8001 elements, consistent with other algorithms
print len(candidates)
0
brian_o