2
votes

Trouver deux nombres entiers qui se multiplient à 20. Puis-je rendre ce code plus "pythonique"?

J'ai fait ce code qui trouve deux entiers dans une dite liste (dans ce cas [2,4,5,1,6,40, -1]) qui se multiplient par vingt. Je suis resté un peu coincé au début, mais l'ajout d'une fonction a résolu mes problèmes. J'ai montré ce code à un de mes amis qui est programmeur et il a dit que je pourrais rendre ce code plus "pythonique", mais je ne sais pas comment.

Voici le code:

num_list = [2,4,5,1,6,40,-1]

def get_mult_num(given_list):
    for i in given_list:
        for j in range(i+1, len(given_list)): #for j not to be == i and to be in the list
            mult_two_numbers = i * j
            if mult_two_numbers == 20:
                return i,j

print(get_mult_num(num_list)) 


1 commentaires

Je ne pense pas que ce soit ce que vous voulez: for i in given_list: for j in range (i + 1, len (given_list)): ... utilisez print i, j < / code> pour voir les résultats. i n'est pas l'index de given_list mais en est un élément!


6 Réponses :


2
votes
[(i,j) for i in num_list for j in num_list if i<j and i*j==20]

4 commentaires

Cela fait plus du double du travail nécessaire. Vous bouclez N ** 2 fois sur une liste de nombres de longueur N, mais il n'y a que ( N choisissez 2) combinaisons. C'est 49 itérations dans votre cas où seulement 21 sont nécessaires, moins de la moitié. Vous prenez le carré complet avec N le long des deux côtés, mais vous n'avez besoin de considérer qu'une moitié de ce carré et d'ignorer la diagonale, donc (N * (N - 1)) // 2.


@MartijnPieters: a) la condition si i exclut l'un des triangles b) vous ne pouvez pas ignorer la diagonale sauf si vous êtes sûr qu'elle ne contient pas sqrt (20) < / code>, à moins que vous ne vouliez écrire un cas spécial pour quand sqrt (N) est un entier, ce qui serait idiot. Donc, en général, cela devrait être `si i <= j`, vous devez inclure la diagonale


@smci: l'hypothèse ici est que la liste des entiers en entrée est unique et que vous devez en choisir deux valeurs distinctes. Cela signifie qu'il n'y a pas de cas i == j .


@MartijnPieters: ok mais la question n'a jamais dit que les nombres doivent être distincts. Nous pourrions l'interpréter familièrement de cette façon, mais cela ne l'interdit pas.



0
votes

Je peux penser à utiliser la compréhension de liste. Cela aide également à trouver plusieurs paires de ce type si elles existent dans la liste donnée.

[(4, 5)]

Sortie:

num_list = [2,4,5,1,6,40,-1]

mult_num = [(num_list[i],num_list[j]) for i in range(len(num_list)) for j in range(i+1, len(num_list)) if num_list[i]*num_list[j] == 20]
print mult_num


2 commentaires

Remarque: votre implémentation a la même erreur que le code de la question; essayez ceci avec num_list = [2, 10] , par exemple.


@MartijnPieters, Merci d'avoir vérifié ma réponse et signalé l'erreur. Je l'ai corrigé et mis à jour ma réponse.



0
votes

Voici mon point de vue, qui utilise enumerate code> :

def get_mult_num(given_list):
    return [
        item1, item2
        for i, item1 in enumerate(given_list)
        for item2 in given_list[:i]
        if item1*item2 == 20
    ]

Je pense que votre ami suggère peut-être d'utiliser comprehensions quand il rend le code plus propre (parfois non).


0 commentaires

8
votes

Je ne pense pas nécessairement que ce soit "impythonique", vous utilisez des idiomes Python standard pour boucler vos données et produire un seul résultat ou Aucun . Le terme Pythonique est nébuleux, un sujet entaché de "Je le sais quand je vois it " paramètres.

Non pas que vous ayez produit une implémentation correcte . Alors que i boucle sur given_numbers , j boucle sur un entier de i + 2 à len ( given_numbers) , mélanger les valeurs de given_list avec des indices? Pour votre entrée d'échantillon, vous prenez j des plages semi-ouvertes [4, 7), [6, 7), [7, 7) (vide), [3, 7), [ 8, 7) (vide), [42, 7) (vide) et [1, 7), respectivement. Le fait qu'elle donne la bonne réponse du tout est de la chance, pas du fait de l'exactitude; si vous donnez à votre fonction la liste [2, 10] , elle ne trouvera pas de solution! Vous voulez faire une boucle sur given_numbers à nouveau , limité par le découpage, ou générer des index à partir de l'index actuel de i , mais votre boucle externe a besoin pour ajouter un appel enumerate () aussi:

>>> import random, operator
>>> from timeit import Timer
>>> def gen_factors_for_division(target, numbers):
...     possible_j = set(numbers)
...     limit = abs(target) ** 0.5
...     for i in numbers:
...         if abs(i) < limit and target % i == 0:
...             j = target // i
...             if j in possible_j and abs(j) > abs(i):
...                 yield i, j
...
>>> def gen_factors_for_combinations(target, given_list):
...     return ((i, j) for i, j in combinations(given_list, 2) if i * j == target)
...
>>> numbers = [random.randint(-10000, 10000) for _ in range(100)]
>>> targets = [operator.mul(*random.sample(set(numbers), 2)) for _ in range(5)]
>>> targets += [t + random.randint(1, 100) for t in targets]  # add likely-to-be-unsolvable numbers
>>> for (label, t) in (('first match:', 'next({}, None)'), ('all matches:', 'list({})')):
...     print(label)
...     for f in (gen_factors_for_division, gen_factors_for_combinations):
...         test = t.format('f(t, n)')
...         timer = Timer(
...             f"[{test} for t in ts]",
...             'from __main__ import targets as ts, numbers as n, f')
...         count, total = timer.autorange()
...         print(f"{f.__name__:>30}: {total / count * 1000:8.3f}ms")
...
first match:
      gen_factors_for_division:    0.219ms
  gen_factors_for_combinations:    4.664ms
all matches:
      gen_factors_for_division:    0.259ms
  gen_factors_for_combinations:    3.326ms

ou

def gen_factors_for(target, numbers):
    possible_j = set(numbers)
    limit = abs(target) ** 0.5
    for i in numbers:
        if abs(i) < limit and target % i == 0:
            j = target // i
            if j in possible_j and abs(j) > abs(i):
                yield i, j

Tout ceci est pas aussi efficace que possible; la bibliothèque standard Python vous offre les outils pour générer vos paires i, j sans boucle for imbriquée ou slicing ou autre forme de filtrage.

Votre la double boucle doit générer des combinaisons des entrées entières, utilisez donc le itertools.combinations () objet a > pour générer des paires i, j uniques:

def get_mult_num(given_list):
    multiplies_to_20 = (
        (i, j) for i, j in combinations(given_list, 2)
        if i * j == 20)
    return next(multiplies_to_20, None)

Cela suppose qu'il peut y avoir zéro ou plusieurs solutions de ce type, pas seulement une solution unique. p>

Si vous n'avez besoin que du premier résultat ou de Aucun , vous pouvez utiliser next () function :

from itertools import combinations

def get_mult_num(given_list):
    return [(i, j) for i, j in combinations(given_list, 2) if i * j == 20]

Ensuite, plutôt que de produire toutes les combinaisons possibles, vous peut vouloir inverser le problème. Si vous transformez given_list en un ensemble, vous pouvez vérifier trivialement si le nombre cible 20 peut être divisé proprement sans reste par l'un de vos nombres donnés et où le résultat de la division est plus grand et est également un entier dans l'ensemble des nombres. Cela vous donne une réponse en temps linéaire.

Vous pouvez limiter davantage la recherche en divisant par des nombres plus petits que la racine carrée de la valeur cible, car vous ne trouverez pas une valeur plus grande à faire correspondre dans votre entrée nombres (étant donné un nombre n et sa racine carrée s , par définition s * (s + 1) va être plus grand que n ).

Si nous ajoutons un argument pour le numéro cible à la fonction et en faisons un fonction de générateur , alors vous obtenez:

for ii, i in enumerate(given_numbers):
    for jj in range(ii + 1, len(given_numbers)):
        j = given_numbers[jj]
        # ...

Cette approche est beaucoup em > plus rapide que de tester toutes les permutations, surtout si vous devez trouver tous les facteurs possibles. Notez que j'ai créé les deux générateurs de fonctions ici pour égaliser les comparaisons:

for ii, i in enumerate(given_numbers):
    for j in given_numbers[ii + 1:]:
        # ...

Notez que je génère 10 cibles aléatoires différentes, pour essayer d'éviter un succès chanceux dans le meilleur des cas pour l'une ou l'autre approche.


5 commentaires

Merci pour l'aide et l'explication très claire. Je vais certainement l'adapter.


Et si la liste était une plage de 100, comment pourrais-je adapter mon code à l'aide de combinaisons pour renvoyer les valeurs qui se multiplient vers ladite valeur cible?


@MarcelloFabrizio: cela va bien au-delà d'être «pythonique» maintenant, maintenant nous écrivons des solutions de puzzle mathématique. :-) J'ai mis à jour ma réponse pour suggérer une approche.


J'ai édité ma question avec le nouveau code. J'ai implémenté les combinaisons que vous avez expliquées.


@MarcelloFabrizio: ce n'est vraiment pas nécessaire; nous essayons de construire un référentiel de questions et réponses pour les futurs visiteurs, votre question était mieux adaptée à cet objectif sans l'ajout. J'ai annulé la modification.



0
votes

Vous pouvez le rendre plus pythonique en utilisant itertools.combinations, au lieu de boucles imbriquées, pour trouver toutes les paires de nombres. Pas toujours, mais souvent itérer sur des indices comme dans pour i dans la plage (len (L)): est moins pythonique que d'itérer directement sur des valeurs comme dans pour v dans L: .

Python vous permet également de transformer votre fonction en générateur via le mot-clé yield de sorte qu'au lieu de simplement renvoyer la première paire qui se multiplie à 20, vous obtenez chaque paire qui le fait en itérant sur le appel de fonction.

import itertools

def factors(x, numbers):
    """ Generate all pairs in list of numbers that multiply to x.
    """
    for a, b in itertools.combinations(numbers, 2):
        if a * b == x:
            yield (a, b)

numbers = [2, 4, 5, 1, 6, 40, -1]
for pair in factors(20, numbers):
    print(pair)


0 commentaires

0
votes

J'ai trouvé ça. Il inverse un peu l'approche, en ce sens qu'il recherche dans num_list le partenaire de paire requis avec lequel la valeur d'itération val se multiplierait à 20. Cela facilite le code et ne nécessite aucune importation, même si ce n'est pas le moyen le plus efficace.

for val in num_list:    
    if 20 / val in num_list:
        print(val, int(20/val))


0 commentaires