2
votes

Comment créer un nombre quelconque de boucles imbriquées?

J'essaie d'écrire un programme qui teste chaque chaîne possible avant de faire correspondre une chaîne spécifique et donne également la tentative de nombre. Prenons cet exemple:

a 1
b 2
c 3
...
aa 27
ab 28
ac 29
...
ba #
bb #
bc #
...
aaa #
aab #
aac #

Et la sortie ressemblerait à ceci:

aaaa 1
aaab 2
aaac 3
aaad 4
aaae 5
aaaf 6
...
bijz 23244
bika 23245
bikb 23246
bikc 23247
bikd 23248
bike 23249

Vous pouvez utiliser ce code si vous savez que le mot sera toujours composé de quatre caractères, mais que faire si vous ne connaissez pas la longueur? Comment créeriez-vous cela lorsque la longueur n'est pas connue? J'essaie de penser s'il y a une fonction récursive qui pourrait y parvenir, mais rien ne fonctionne. Le programme que j'essaie de réaliser aurait une sortie comme celle-ci:

a = {0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm',
     13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y',
     25: 'z'}

word = 'bike'

found = False
counter = 0
for i in range(len(a)):
    for j in range(len(a)):
        for k in range(len(a)):
            for m in range(len(a)):
                string = f'{a[i]}{a[j]}{a[k]}{a[m]}'
                counter += 1
                print(string, counter)
                if string == word:
                    found = True
                    break
            if found:
                break
        if found:
            break
    if found:
        break


0 commentaires

3 Réponses :


2
votes

cela devrait fonctionner; vous entrez la longueur dans l'argument repeat du product tandis que enumerate énumère les mots (à partir de 0 par défaut; vous pouvez ajouter start=1 selon vos besoins):

def number(word):
    return int(word.translate(alpha_to_digits), base=26)

il sort:

alpha_to_digits = str.maketrans(
    "abcdefghijklmnopqrstuvwxyz", "0123456789abcdefghijklmnop")

word = 'bike'

d = word.translate(alpha_to_digits)
print(d, int(d, base=26))
# 18a4 23248

si vous n'êtes intéressé que par ce nombre, vous pouvez traduire le mot en un nombre en base 26 et le convertir en utilisant int :

...
bikb 23245
bikc 23246
bikd 23247
bike 23248

qui traduit le mot bike en sa représentation numérique en base 26 ( 18a4 ) qui peut ensuite être convertie en un entier ( 23248 ).

emballé dans une fonction:

from itertools import product


search = "bike"

for i, item in enumerate(product(a.values(), repeat=len(search))):
     word = "".join(item)
     print(word, i)
     if word == search:
          break


5 commentaires

Merci. C'est suffisant pour me lancer sur le résultat que je recherche, mais je le fais pour l'expérience d'apprentissage. S'il y avait un moyen de le faire avec une fonction récursive, je serai heureux de le savoir.


la documentation vous indique plus ou moins comment il est implémenté. ce n'est pas récursif, cependant ...


@GabeMorris J'ai inclus une version récursive dans ma réponse, bien qu'en python je ne le recommanderais pas.


@Stef Est-ce trop pour python?


@GabeMorris: les appels de fonction sont l'une des opérations les plus lentes en python. De plus, python n'optimise pas les appels de fin, ce qui signifie que la pile d'appels deviendra de plus en plus grande à chaque appel récursif, même si vous écrivez du code comme return recursive_call() . Ainsi, les fonctions récursives en python sont plus lentes que les fonctions itératives, utilisent beaucoup plus de mémoire et sont incapables de gérer de grandes entrées.



2
votes

Vous pouvez utiliser itertools.product (pour les boucles for imbriquées) + dict.values (pour boucler directement sur les lettres) + enumerate (à partir de 1 , pour obtenir le compteur):

...
...
...
bika 23245
bikb 23246
bikc 23247
bikd 23248
bike 23249

Production:

from itertools import product

word = 'bike'

for counter, letters in enumerate(product(a.values(), repeat = len(word)), 1):
  string = ''.join(letters)
  print(string, counter)
  if string == word:
      break


0 commentaires

2
votes

Remarque: toutes les fonctions présentées dans cette réponse renvoient 23248 comme réponse pour «vélo», car j'aime compter à partir de 0. Si vous préférez compter à partir de 1 et que vous voulez obtenir la réponse 23249, ajoutez simplement un +1 quelque part dans les fonctions .

Première idée: écrivez votre propre fonction increment_word :

Vous pouvez parcourir les mots en écrivant une fonction qui calcule le mot suivant. Par exemple, le mot suivant après bikd devrait être bike et le mot suivant après cdzz devrait être ceaa .

Les chaînes sont immuables en python, donc pour plus de facilité, nous allons utiliser une liste de caractères au lieu d'une chaîne.

def find_word(w):
  count_attempts = 0
  for i,c in enumerate(w):
    n = ord(c) - ord('a')
    count_attempts += n * 26**(len(w) - i - 1)
  return count_attempts

Notez que cette fonction n'est garantie de fonctionner que si elle est appelée sur un mot contenant au moins une lettre non «z». C'est à l'utilisateur de ne pas demander le mot suivant après 'zzz' .

Maintenant, nous pouvons résoudre votre problème:

def find_word(word):
  return find_word_aux(word, 'a'*len(word), 0)

def find_word_aux(word, candidate, count_attempts):
  if candidate == word:
    return count_attempts
  else:
    i,c = max((i,c) for i,c in enumerate(candidate) if c != 'z')
    return find_word_aux(word, candidate[:i] + chr(ord(c)+1) + 'a'*(len(word)-i-1), count_attempts + 1)

Deuxième idée: utilisez itertools.product

En général, lorsque vous souhaitez parcourir des choses complexes en python, quelqu'un a déjà écrit exactement la construction de boucle dont vous avez besoin et elle se trouve dans le package itertools . Si ce n'est pas le cas, c'est peut-être dans les recettes itertools ou dans more_itertools .

Dans ce cas, vous pouvez utiliser itertools.product :

from string import ascii_lowercase
from itertools import product

def find_word(w):
  for count_attempts, candidate in enumerate(product(*[ascii_lowercase]*len(w))):
    if all(x == y for x,y in zip(w, candidate)):
      return count_attempts

Notez comment nous utilisons string.ascii_lowercase plutôt que de taper tout l'alphabet nous-mêmes. Quelqu'un a eu la gentillesse d'enseigner l'alphabet à python. Inutile d'être trop zélé et de réécrire nous-mêmes l'alphabet (au risque d'oublier une lettre et de tout casser).

Troisième idée: utiliser la récursivité au lieu de l'itération

Tout type de boucle complexe peut être simulé en utilisant la récursivité. Notez que python est un très mauvais langage pour la récursivité - en particulier, les fonctions récursives ont tendance à être assez lentes en python; sans oublier que le programme peut planter si la récursivité est trop profonde, car python n'optimise pas les appels de fin. Mais vous devriez penser à cette option si jamais vous avez besoin d'utiliser un langage autre que python.

def find_word(w):
  candidate = ['a' for _ in w]
  w = list(w)
  count_attempts = 0
  while candidate != w:
    increment_word(candidate)
    count_attempts += 1
  return count_attempts

Notez comment cela finit par être extrêmement similaire à la version increment_word . Malheureusement, avec les paramètres python par défaut sur ma machine, cela ne fonctionne que pour les mots entre aaa et bmg . Pour tout mot après bmh , il plante avec l'exception RecursionError: maximum recursion depth exceeded in comparison . Si vous traduisez ce code dans un langage qui optimise les appels de fin, comme OCaml, Haskell ou C, alors cela fonctionnera pour n'importe quel mot.

Quatrième idée: utilisez la combinatoire pour résoudre votre problème instantanément

Au lieu d'itérer les mots un par un, vous pouvez essayer de compter des lots de mots en utilisant la multiplication. Par exemple, il est facile de voir qu'il y a:

  • 26 * 26 * 26 = 26^3 = 17576 mots entre aaaa et azzz ;
  • 8 * 26 * 26 = 5408 mots entre baaa et bhzz ;
  • 10 * 26 = 260 mots entre biaa et bijz ;
  • 5 mots entre bika et bike .

Total: 23249 mots entre aaaa et bike .

Cela nous donne un programme python:

def increment_word(w):
  i = len(w) - 1
  while (w[i] == 'z'):
    w[i] = 'a'
    i = i - 1
  w[i] = chr(ord(w[i]) + 1)

Notez que la boucle for ici est sur les caractères de w plutôt que sur tous les mots possibles; nous répétons donc 4 fois et non 23249 fois. Cette fonction est beaucoup plus rapide que les autres versions.


2 commentaires

Très bonne réponse. Je voudrais faire remarquer que la récursivité n'est pas intrinsèquement lente et que les programmes récursifs peuvent être sécurisés. J'ai quelques articles sur le sujet , et d' autres spécifiques à python , si cela vous intéresse.


Et une technique de récursion de queue pour python, je pense que tout le monde apprécierait.