web-dev-qa-db-fra.com

Que signifie l'associativité de gauche à droite?

Je suis confus quant à la définition de l'associativité de gauche à droite et de droite à gauche. Je les ai également vues appelées associativité gauche et associativité droite et je voudrais savoir laquelle correspond à laquelle.

Je sais que cela concerne l'ordre dans lequel les opérations ayant la même priorité sont préformées, comme si a = x * y * z signifie a = x * (y * z) ou a = (x * y) * z. Je ne sais pas lequel est associatif de gauche à droite et lequel est associatif de droite à gauche.

J'ai essayé Google, mais tout ce que j'ai pu trouver, c'est des tableaux de l'associativité des différents opérateurs en c ++. Regarder tous les exemples me rend encore plus confus.

Ce qui m'embrouille également, c'est que:

glm::vec4 transformedVector = translationMatrix * rotationMatrix * scaleMatrix * originalVector;

préforme la multiplication de la matrice de mise à l'échelle en premier, suivie de la matrice de rotation suivie de la translation. Dans cet exemple, les matrices sont toutes de type glm :: mat4 et les vecteurs sont de type glm :: vec4. Est-ce cette associativité de gauche à droite ou de droite à gauche? Est-ce la même chose que la multiplication normale ou la multiplication des types glm est-elle différente?

25
Francis

Vous lisez normalement de gauche à droite. Vous faites normalement des calculs de gauche à droite. Il s'agit de l'associativité de gauche à droite et la plus courante.

La plupart des gens vont résoudre

x = 23 + 34 + 45

en le groupant

x = (23 + 34) + 45

c'est de l'associativité de gauche à droite. Vous vous en souvenez parce que vous lisez et faites des calculs de gauche à droite.

Pour l'addition en mathématiques cela n'a pas trop d'importance. Vous obtenez toujours le même résultat dans les deux cas. En effet, l'addition est associative. Dire qu'une opération est associative signifie que l'association de gauche à droite et de droite à gauche est la même chose. Pour l'ajout en programmation cela compte toujours à cause des débordements et de l'arithmétique à virgule flottante (mais pas pour les entiers de taille normale dans un langage raisonnable), donc quand vous avez un bogue à 2 heures du matin avec de grands nombres et l'utilisation désinvolte de a+b et b+a, rappelez-vous dans quel ordre l'ajout s'est produit.

Dans votre exemple:

glm::vec4 transformedVector = translationMatrix * rotationMatrix * scaleMatrix * originalVector

Vous conceptuellement coupez d'abord du côté droit, car c'est là que se situe la chose sur laquelle vous agissez. Cependant en C++, * Est normalement de gauche à droite associatif et il n'est pas possible de le remplacer. glm peut gérer cela de plusieurs façons: il peut constituer un cache de choses à multiplier en attendant que le vecteur final arrive alors faire une multiplication de droite à gauche. Il peut également (plus probablement) utiliser le théorème de l'algèbre selon lequel la multiplication matricielle est entièrement associative, et simplement multiplier de gauche à droite, puis assurer le lecteur dans la documentation que c'est la même chose que de la considérer comme de droite à gauche. Cependant, vous devez comprendre l'implémentation parce que comme discuté précédemment il importe de quelle manière l'implémentation choisit de multiplier les nombres à virgule flottante .

Pour être complet, considérez la soustraction. Qu'est-ce que a - b - c? Ici, c'est vraiment le fait importe qu'il soit associatif gauche ou droit. Bien sûr, en mathématiques, nous le définissons comme b (a - b) - c, Mais un langage de programmation étrange pourrait préférer que la soustraction soit associative droite, et prendre a - b - c Pour toujours signifier a - (b - c). Cette langue étrangère aurait intérêt à avoir une page de documentation spécifiant que - Est associative à droite, car elle fait partie de la spécification de l'opération, et non quelque chose que vous pouvez dire simplement en regardant l'utilisation de l'opérateur.

22
djechlin

Vous pouvez le voir dans les mots suivants:

Lorsque nous combinons des opérateurs pour former des expressions, l'ordre dans lequel les opérateurs doivent être appliqués peut ne pas être évident. Par exemple, a + b + c peut être interprété comme ((a + b) + c) ou comme (a + (b + c)). On dit que + est associatif à gauche si les opérandes sont regroupés de gauche à droite comme dans ((a + b) + c). Nous disons qu'il est associatif à droite s'il regroupe des opérandes dans la direction opposée, comme dans (a + (b + c)).

UN V. Aho et J.D. Ullman 1977, p. 47

6
thuzhf

La réponse la plus simple et la plus non-tl; dr j'ai trouvé:

Dans la plupart des langages de programmation, les opérateurs d'addition, de soustraction, de multiplication et de division sont associatifs à gauche , tandis que les opérateurs d'affectation, conditionnels et d'exponentiation sont associatif à droite .

merci à: http://www.computerhope.com/jargon/a/assooper.htm

3
killjoy

a = (x * y) * z est de gauche à droite et a = x * (y * z) est de droite à gauche.

la multiplcation matricielle de glm associe de gauche à droite car elle surcharge le * opérateur. Le problème ici concerne la signification des multiplications matricielles en termes de transformations géométriques, plutôt que l'associativité mathématique.

2
Code-Apprentice

Un opérateur infixe (ou plus généralement un type d'expression qui a des sous-expressions gauche et droite non fermées) est associé à gauche si dans l'utilisation imbriquée de cet opérateur (type d'expression) sans parenthèses explicites, les parenthèses implicites sont placées à gauche. Puisque * Est associatif à gauche en C++, a*b*c Signifie (a*b)*c. En cas d'imbrication plus profonde, un groupe de parenthèses implicites se produit à l'extrémité gauche: (((a*b)*c)*d)*e.

De manière équivalente, cela signifie que la règle de production syntaxique de cet opérateur est récursive à gauche (ce qui signifie que la sous-expression gauche a la même catégorie syntaxique que celle pour laquelle cette règle est une production, de sorte que la même règle (même opérateur) peut être utilisée directement pour forme cette sous-expression; la sous-expression à l'autre extrémité a une catégorie syntaxique plus restrictive, et en utilisant le même opérateur, il faudrait des parenthèses explicites). En C++, une production pour expression-multiplicative (section 5.6 de la norme) lit expression-mutliplicative * expression-pm , avec expression-multiplicative à gauche.

Par conséquent, dans une utilisation imbriquée sans parenthèses explicites, l'opérateur le plus à gauche prend ses voisins immédiats comme opérandes, tandis que les autres instances prennent comme opérande gauche le (résultat de) l'expression formée par tout à leur gauche.

J'avoue, j'ai poussé un peu (trop loin). Je veux dire que nulle part au-dessus, la Parole "juste" ne se produit, ni aucun mouvement impliqué; l'associativité est une matière syntaxique et donc statique. Il est important les parenthèses implicites vont, pas dans quel ordre on les écrit (en fait on ne les écrit pas du tout, sinon elles seraient explicites) . Bien sûr, pour l'associativité droite, il suffit de remplacer chaque "gauche" par "droite" ci-dessus.

En conclusion, je ne vois aucune raison valable d'appeler cette associativité de gauche à droite (ou groupement), mais le fait est que les gens le font (même le Standard le fait, bien qu'il soit parfaitement redondant étant donné que les règles de syntaxe explicites sont également donné).

La confusion vient d'expliquer cela, comme cela est souvent fait, en disant que (en l'absence de parenthèses explicites) les opérateurs sont exécutés de gauche à droite (respectivement de droite à gauche pour les opérateurs associatifs à droite). Ceci est trompeur car il confond la syntaxe avec la sémantique (exécution), et n'est également valable que pour les opérations avec une évaluation ascendante (tous les opérandes sont évalués avant l'opérateur). Pour les opérateurs ayant des règles d'évaluation spéciales, c'est tout simplement faux. Pour les opérateurs && (Et) et || (Ou) la sémantique consiste à évaluer d'abord l'opérande gauche puis l'opérateur lui-même (à savoir décider si l'opérande gauche ou droite produira le résultat) suivi éventuellement par l'évaluation de l'opérande droit. Cette évaluation de gauche à droite est totalement indépendante de l'associativité: les opérateurs se trouvent être associatifs de gauche, probablement parce que tous les opérateurs binaires sans affectation le sont, mais (c1 && c2) && c3 (Avec des parenthèses redondantes où ils seraient déjà implicitement ) a une exécution équivalente à c1 && (c2 && c3) (à savoir exécuter les conditions de gauche à droite jusqu'à ce que l'on retourne false et retourne cela, ou si aucun ne retourne true), et I ne peut pas imaginer un compilateur raisonnable générant un code différent pour les deux cas. En fait, je trouve le bon regroupement plus suggestif de la façon dont l'expression est évaluée, mais cela ne fait vraiment aucune différence; il en va de même pour or.

Ceci est encore plus clair pour l'opérateur conditionnel (ternaire) ? ... :. Ici, l'associativité s'applique, car il y a des sous-expressions ouvertes des deux côtés (cf. ma première phrase); l'opérande du milieu est placé entre ? et : et jamais ne nécessite de parenthèses supplémentaires. En effet, cet opérateur est déclaré à droite - associatif, ce qui signifie que c1 ? x : c2 ? y : z Doit être lu comme c1 ? x : (c2 ? y : z) plutôt que comme (c1 ? x : c2) ? y : z (L'implicite les parenthèses sont à droite). Cependant, avec les parenthèses implicites, les deux opérateurs ternaires sont exécutés de gauche à droite ; l'explication est que la sémantique de l'opérateur ternaire n'évalue pas d'abord toutes les sous-expressions.


De retour à l'exemple de votre question, l'associativité de gauche (ou le regroupement de gauche à droite) signifie que votre produit matrice-vecteur est analysé comme ((M1*M2)*M3)*v. Bien que mathématiquement équivalent, il est pratiquement impossible que cela soit exécuté en tant que M1*(M2*(M3*v)), même si cela est plus efficace. La raison en est que la multiplication en virgule flottante n'est pas vraiment associative (juste approximativement), ni donc la multiplication matricielle en virgule flottante; le compilateur ne peut donc pas transformer une expression en l'autre. Notez que dans ((M1*M2)*M3)*v On ne peut pas dire laquelle des matrices est appliquée en premier à un vecteur, car aucune d'entre elles n'est: la matrice du composite les cartes linéaires sont calculées en premier, et la que matrice est appliquée au vecteur. Le résultat sera approximativement égal à celui de M1*(M2*(M3*v)) dans lequel M3 Est appliqué, puis M2 Et enfin M1. Mais si vous voulez que les choses se passent comme ça, vous devez écrire ces parenthèses.

2
Marc van Leeuwen