web-dev-qa-db-fra.com

Comment puis-je m'éloigner de l'école de pensée «for-loop»?

C'est une question plutôt conceptuelle, mais j'espérais pouvoir obtenir de bons conseils à ce sujet. Une grande partie de la programmation que je fais est avec des tableaux ( NumPy ); Je dois souvent faire correspondre des éléments dans deux tableaux ou plus qui sont de tailles différentes et la première chose que je vais faire est une boucle for ou pire encore, une boucle for imbriquée. Je veux éviter autant que possible les boucles for, car elles sont lentes (au moins en Python).

Je sais que pour beaucoup de choses avec NumPy, il y a des commandes prédéfinies que j'ai juste besoin de rechercher, mais avez-vous (en tant que programmeurs plus expérimentés) un processus de réflexion général qui me vient à l'esprit quand vous devez répéter quelque chose?

J'ai donc souvent quelque chose comme ça, ce qui est horrible et je veux l'éviter:

small_array = np.array(["one", "two"])
big_array = np.array(["one", "two", "three", "one"])

for i in range(len(small_array)):
    for p in range(len(big_array)):
        if small_array[i] == big_array[p]:
            print "This item is matched: ", small_array[i]

Je sais qu'il y a plusieurs façons différentes d'y parvenir en particulier, mais je m'intéresse à une méthode générale de pensée, si elle existe.

80
turnip

Il s'agit d'une difficulté conceptuelle courante lorsque vous apprenez à utiliser NumPy efficacement. Normalement, le traitement des données en Python s'exprime mieux en termes d'itérateurs , pour garder une utilisation de la mémoire faible, pour maximiser les opportunités pour le parallélisme avec le système d'E/S et pour permettre la réutilisation et la combinaison de parties d'algorithmes.

Mais NumPy met tout cela à l'envers: la meilleure approche consiste à exprimer l'algorithme sous la forme d'une séquence d'opérations sur l'ensemble du tableau , pour minimiser le temps passé dans l'interpréteur lent Python et maximisez le temps passé dans les routines NumPy compilées rapidement.

Voici l'approche générale que je prends:

  1. Conservez la version originale de la fonction (dont vous êtes sûr qu'elle est correcte) afin de pouvoir la tester par rapport à vos versions améliorées à la fois pour l'exactitude et la vitesse.

  2. Travaillez de l'intérieur: c'est-à-dire, commencez par la boucle la plus intérieure et voyez si elle peut être vectorisée; puis lorsque vous avez fait cela, sortez d'un niveau et continuez.

  3. Passez beaucoup de temps à lire la documentation NumPy . Il y a beaucoup de fonctions et d'opérations là-dedans et elles ne sont pas toujours brillamment nommées, donc ça vaut la peine de les connaître. En particulier, si vous vous trouvez en train de penser, "si seulement il y avait une fonction qui faisait telle ou telle chose", alors ça vaut la peine de passer dix minutes à la chercher. C'est généralement quelque part.

Il n'y a pas de substitut à la pratique, donc je vais vous donner quelques exemples de problèmes. Le but de chaque problème est de réécrire la fonction afin qu'elle soit entièrement vectorisée : c'est-à-dire qu'elle se compose d'une séquence d'opérations NumPy dans son ensemble tableaux, sans Python natives (pas d'instructions for ou while, pas d'itérateurs ni de compréhensions).

Problème 1

def sumproducts(x, y):
    """Return the sum of x[i] * y[j] for all pairs of indices i, j.

    >>> sumproducts(np.arange(3000), np.arange(3000))
    20236502250000

    """
    result = 0
    for i in range(len(x)):
        for j in range(len(y)):
            result += x[i] * y[j]
    return result

Problème 2

def countlower(x, y):
    """Return the number of pairs i, j such that x[i] < y[j].

    >>> countlower(np.arange(0, 200, 2), np.arange(40, 140))
    4500

    """
    result = 0
    for i in range(len(x)):
        for j in range(len(y)):
            if x[i] < y[j]:
                result += 1
    return result

Problème 3

def cleanup(x, missing=-1, value=0):
    """Return an array that's the same as x, except that where x ==
    missing, it has value instead.

    >>> cleanup(np.arange(-3, 3), value=10)
    ... # doctest: +NORMALIZE_WHITESPACE
    array([-3, -2, 10, 0, 1, 2])

    """
    result = []
    for i in range(len(x)):
        if x[i] == missing:
            result.append(value)
        else:
            result.append(x[i])
    return np.array(result)

Spoilers ci-dessous. Vous obtiendrez les meilleurs résultats si vous essayez vous-même avant de regarder mes solutions!

Réponse 1

np.sum (x) * np.sum (y)

Réponse 2

np.sum (np.searchsorted (np.sort (x), y)))

Réponse 3

np.where (x == manquant, valeur, x)

90
Gareth Rees

Pour accélérer les choses, vous devez lire sur vos structures de données et utiliser celles appropriées.

Pour les tailles non triviales de petit tableau et de grand tableau (disons petit = 100 éléments et grand = 10 000 éléments), une façon de procéder consiste à trier le petit tableau, puis à parcourir le grand tableau et à utiliser une recherche binaire pour trouver les éléments correspondants dans le petit tableau.

Cela rendrait la complexité temporelle maximale, O (N log N) (et pour les petits petits tableaux et les très grands grands tableaux, elle est plus proche de O(N)) où se trouve votre solution de boucle imbriquée O (N ^ 2)

Toutefois. Les structures de données les plus efficaces dépendent fortement du problème réel.

8
Pieter B