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
3 Réponses :
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
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.
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
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 .
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)
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).
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.
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.
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.