[Python]Division euclydienne,vecteur

Résolu/Fermé
StephenJacob - 1 déc. 2009 à 02:31
 StephenJacob - 6 déc. 2009 à 17:52
Bonjour,
Cela fait maintenant quelque mois que j'essaye de créer un système de déplacement pour les avatars d'une application multijoueurs .Cette application étant en realtime j'ai passé beaucoup de temps a réfléchir à une façon de réduire l'impact du lag sur la position finale d'un avatar .
Je ne suis pas arrivé à trouver une solution satisfaisante .
Je développe donc un système de déplacement par clic , qui ne pose plus ce problème étant donné qu'il fournit au serveur le point final que doit atteindre l'avatar , il n'y a donc pas de différence entre la position sur le serveur et sur les différents clients .
J'espère que vous me suivez jusque là :) .
Il me reste donc maintenant que je possède le point de départ A et le point d'arrivée B de l'avatar à calculer la façon dont il se déplace entre A et B.

-Je commence donc par calculer le vecteur AB , jusque là tout va bien . ETAPE 1

-Je cherche ensuite à diviser les coordonnées (x,y) de ce vecteur de façon à ce que l'une soit égale à 1 .
(En gros je vais effectuer : X_du_joueur += x_du_vecteur , y_du_joueur += y_du_vecteur toutes les secondes , pour que le mouvement ne soit pas saccadé il faut que x_du_vecteur et y_du_vecteur soient petits) .Pour que l'une des valeurs soit égale à un :
si x>y : on divise x/y et y/y ce qui donne bien (x/y,1).
si x<y : on divise y/x et x/x ce qui donne bien (1,y/x)
Ce qui suffirait a avoir une trajectoire exacte .Mais x et y étant des pixels, x/y ou y/x doivent être des valeurs entières .
Donc au final on a : (int(x/y),1) ou (1,int(y/x)) . ETAPE 2

On arrive donc à un point diffèrent (POINT D) de celui demandé au départ .

Pour arriver au point exacte il suffit d'effectuer la même opération tant que le reste r de x = q*y + r ou y=q*x+r est différent de 0.

On retourne donc à l'ETAPE 1 avec pour point de départ le POINT D et toujours le même point d'arrivée.

Bon donc voila pour l'introduction du code beaucoup de parlote pour expliquer finalement quelque chose de pas si compliqué , malgré tout, vu les résultats plutôt étranges de mon code il doit y avoir une erreur quelque part .

Je vous serais donc très reconnaissant d'y jeter un oeil ...


def calcul_coef(COORDi,COORDf):
    #COORDi: coordonnées initiales
    #COORDf: coordonnées finales
    #final2:Coordonnées finales aproximatives
    #final: coeficient directeur du vecteur COORDifinal2
    
    final = []
    final2 = []
    reste = 1
    etapes = 0
    dic_etapes = {}
    #Tant que l'on arrive pas a la destination voulue exacte .
    while reste != 0:   
        #valeures du vecteur    ETAPE 1 
        y = COORDf[1]-COORDi[1]
        x = COORDf[0]-COORDi[0]
        #Methode moche pour etre sur que x et y != 0
        check_o = (False,None)
        if x == 0 :
            x = 1
            check_o = (True,"x")
        if y == 0:
            y = 1
            check_o = (True,"y")
        #On cherche a avoir un coef de forme (X,1) ou (1,Y) pour que le mouvement ne soit pas saccadé ETAPE 2
        if x>y:
            #Si x>y alors le coef sera de la forme (X,1) avec X ~= x/y
            aprox = (x/y,1)
            #calcul du reste
            reste = x - int(aprox[0]*y)
            if reste > int(y/2):
                final = (int(x/y)+1,1)
                final2 = ((int(x/y)+1)*y,y)
            else:
                final = (int(x/y),1)
                final2 = (int(x/y)*y,y)
        elif x<y:
            #Si y>x alors le coef sera de la forme (1,Y) avec Y ~= y/x
            aprox = (1,y/x)
            #calcul du reste
            reste = y-int(aprox[1]*x)
            if reste > int(x/2):
                final = (1,int(y/x)+1)
                final2 = (x,(int(y/x)+1)*x)
            else:
                final = (1,int(y/x))
                final2 = (x,int(y/x)*x)
                
        else:
            #si x = y tout va bien .
            final = (1,1)
            final2 = (1*x,1*y)
        #FIN de l'ETAPE 2
        #Si x ou y etait = à 0 on le remet à 0
        if check_o[0] == True:
            if check_o[1] == "x":
                final = (final[0]-1,1)
                final2 = (final2[0]-1,final2[1])
            else:
                final = (1,final[1]-1)
                final2 = (final2[0],final2[1]-1)
        
        print "Coordonnées initiales :"+str(COORDi)+".Coordonnées finales voulues:"+str(COORDf)+".Coordonnées trouvées:"+str(final)+":"+str(final2)+"avec un reste de "+str(reste)
        dic_etapes[etapes] = (final,final2)
        
        COORDi = (final2[0],final2[1])
        etapes += 1
    return dic_etapes    


Voila merci à ceux qui auront réussi à tenir jusqu'au bout :p .

Bonne soirée !

9 réponses

heyquem Messages postés 759 Date d'inscription mercredi 17 juin 2009 Statut Membre Dernière intervention 29 décembre 2013 131
1 déc. 2009 à 11:37
Salut,



J'ai lu rapidement l'exposé du problème puis le code, je trouve le sujet intéressant.

J'ai insuffisamment compris pour le moment mais ma première réaction est de remettre en question l'algorithme car j'ai deux impresssions:

- le point D qui n'est pas le point de destination finale doit être, me semble-t-il, sur une verticale ou une horizontale en fonction de quelle est, des deux coordonnées du vecteur, celle qui est la plus grande.

- au lieu d'appliquer int() aussi en amont, pour trouver, si j'ai bien compris, un vecteur minimal dont l'ajout répété va faire passer la position de la position initiale à D,

il serait mieux que ce vecteur minimal garde ses coordonnées réelles et n'appliquer int() que sur le point courant ( = point initial + additions répétées du vecteur minimal à coordonées réelles) pour trouver le pixel le plus proche de la position virtuelle "entre les pixels" du point courant.


Je ne suis pas très assuré de ces propositions, j'ai lu et écrit tout cela en vitesses.
Je me remets à ton problème dans une heure.
0
heyquem Messages postés 759 Date d'inscription mercredi 17 juin 2009 Statut Membre Dernière intervention 29 décembre 2013 131
1 déc. 2009 à 14:18
Deux remarques:



- x et y étant des entiers , x/y est un entier

Si on veut le résultat réel exact, il faut qu'au moins un des deux soit un float donc faire soit x = float soit y = float(y) avant le calcul
Ceci n'est vrai que jusqu'à Python 2.7: x/y donne toujours le résultat réel exact avec Python 3.



- je prends le cas x>y:

Si x/y est un entier , aprox[0] vaut cet entier et donc aprox[0]*y est un entier
==> pas besoin d'appliquer int() à aprox[0]*y

Si ton idée est que x/y est la valeur exacte, alors aprox[0]*y est (x/y)*y c'est à dire x
==> reste = x - x = 0





Quant au calcul de final et final2, je ne comprends pas à quoi correspond la condition if reste > int(y/2)
0
Salut .
Merci pour ta précieuse aide .

-"x et y étant des entiers , x/y est un entier "
Oui je l'ai découvert en écrivant le code que tu proposais dans ton premier post , des années que je fais du python et je ne m'en étais jamais rendu compte :O !

-"Si x/y est un entier..."
En effet, il s'agit là d'une grossière erreur , ça m'apprendra a coder la nuit ...
Le calcul que je voulais effectuer étant : reste = x - int(aprox[0])*y ce qui donne , si je ne m'abuse , le reste de la division euclidienne de x par y .Ça explique pas mal de choses ...

-"Quant au calcul de final..."
Ce point peut-être dur à apprécier , mais c'est en fait très simple .
Dans cet algorithme x = qy , y n'est pas forcement un multiple de x mais on néglige quand même r pour les raisons énoncées ci-dessus .
Si r = y+1 , par exemple , on est d'accord que x = (q+1)y (+1).
Donc x = (q+1)y est une valeur plus proche de x = qy +(y+1) que x = qy ne le serait .
Pour en revenir au concret ici je dis : si r>(y/2) : q+=1 , ce qui me donne , si je n'ai pas fait d'erreur de logique une approximation plus proche de la réalité .

Voici maintenant mon code qui suit ton algorithme :

def calcul_coef1(COORDi,COORDf):
    #COORDi: coordonnées initiales
    #COORDf: coordonnées finales
    #final2:Coordonnées finales aproximatives
    #final: coeficient directeur du vecteur COORDifinal2
    
    final = []
    final2 = []
    reste = 1
    etapes = 0
    dic_etapes = {}
  
    #valeures du vecteur     
    y = COORDf[1]-COORDi[1]
    x = COORDf[0]-COORDi[0]
    #On cherche a avoir un coef de forme (X,1) ou (1,Y) pour que le mouvement ne soit pas saccadé
    if x>y and y !=0:
        #Endroit de stockage de la trajectoire:
        point_courant = [(COORDi,COORDi)]
        #vecteur minimal exacte :
        vm = (float(x)/float(y),1)
        #On cherche a atteindre le point x,y a partir du point initial , ici le y du vecteur minimal est de 1 on va donc en repeter l'ajout y fois .
        for i in range(y):
            COORDi = point_courant[i][0]
            point_courant.append(((COORDi[0]+vm[0],COORDi[1]+vm[1]),(int(COORDi[0]+vm[0]),COORDi[1]+1)))
                  
        
        
    elif x<y and x != 0:
        #Endroit de stockage de la trajectoire qui contient des tuples:
        point_courant = [(COORDi,COORDi)]
        #vecteur minimal exacte :
        vm = (1,float(y)/float(x))
        #On cherche a atteindre le point x,y a partir du point initial , ici le y du vecteur minimal est de 1 on va donc en repeter l'ajout y fois .
        for i in range(x):
            COORDi = point_courant[i][0]
            #On stocke la valeur exacte pour les calculs suivants et la valeur approximative pour avancer l'avatar
            point_courant.append(((COORDi[0]+vm[0],COORDi[1]+vm[1]),(COORDi[0]+1,int(COORDi[1]+vm[1]))))
            
    elif x == 0:        
        point_courant = [(COORDi,COORDi)]
        vm = (0,1)
        for i in range(y):
            COORDi = point_courant[i-1][0]
            point_courant.append(((COORDi[0]+vm[0],COORDi[1]+vm[1]),(COORDi[0]+vm[0],COORDi[1]+vm[1])))
    elif y == 0:
        point_courant = [(COORDi,COORDi)]
        vm = (1,0)
        for i in range(x):
            COORDi = point_courant[i-1][0]
            point_courant.append(((COORDi[0]+vm[0],COORDi[1]+vm[1]),(COORDi[0]+vm[0],COORDi[1]+vm[1])))
    else:
        point_courant = [(COORDi,COORDi)]
        vm = (1,1)
        for i in range(x):
            COORDi = point_courant[i-1][0]
            point_courant.append(((COORDi[0]+vm[0],COORDi[1]+vm[1]),(COORDi[0]+vm[0],COORDi[1]+vm[1])))

    return point_courant


Ce qui fonctionne sans problème ! (Wouhou !)

Je te remercie vraiment pour ton aide et le temps de réflexion que tu as pu apporter à mon problème, d'autant plus que cela a abouti à une solution en un temps record :p .
J'espère avoir l'occasion de réfléchir à nouveau avec toi sur des algo encore plus compliqués ...

Bon après-midi .
0
heyquem Messages postés 759 Date d'inscription mercredi 17 juin 2009 Statut Membre Dernière intervention 29 décembre 2013 131
1 déc. 2009 à 16:21
Merci.


Le calcul que je voulais effectuer étant :   reste = x - int(aprox[0])*y

Évidemment. Qu'est ce que je suis bête , moi




J'espère avoir l'occasion de réfléchir à nouveau avec toi sur des algo encore plus compliqués ...
OK. J'aime les trucs compliqués...à condition que ça reste à ma portée.





Ceci dit ton algo n'est peut être pas à jeter car
- je suspecte le mien d'être plus lent que le tien à cause du fait qu'une utilisation de int() est faite à chaque tour de l'itération. Le tout étant de savoir de combien plus lent.
- vu le nombre de pixels par cm, la différence entre la trajectoire "au plus proche de la ligne droite" de mon algo et la trajectoire "deux segments' de ton algo, il ne doit pas y avoir une différence bien perceptible à l'oeil.
En effet, prenons un point initial (1,2) et un point destination (24,10), c'est à dire x>y et un vecteur (23,8):
23/8 vaut 2,875 et le reste en x vaut 23 - 2*8 = 7.
Le point D auquel mène la première partie de ton code en faisant
1+2=3 2+1=3
3+2=5 3+1=4 etc 8 fois
conduit au point D(17,10)
Comme je l'ai évoqué, ce point est sur la même horizontale y = 10 que le point destination. Pour aller de D(17,10) au point destination (24,10), il y a 7 inter-pixels.
C'est ce reste, ici 7, qui à mon avis doit toujours être assez petit relativement à la différence 24 - 1: d'où un parcours du segment D à destination , survenant après le parcours point initial à D, qui doit être petit et peu visible. D'autant qu'il faut des nombres x et y particuliers pour que le reste soit grand , c'est un point intéressant à examiner aussi.
Ici, 7 n'est pas du tout petit par rapport à 23, mais:
- qui ira remarquer une différence de 7 inter-pixels parcourus horizontalement, quand l'ensemble du processus du point initial au point final doit se faire en quelques centiemes de secondes
- qui ira remarquer cette différence minime quand, de façon plus réaliste, il s'agirait d'une avancée de plusieurs dizaines ou centaines de pixels entre les points de départ et d'arrivée.




Je vais regarder ces points si j'ai le temps.





Je vois aussi quantité de choses plus ou moins importantes à affiner dans ton code. Une fois revu, ton code devrait avoir un aspect plus clair et lisible.

Cela me donne d'ailleurs à penser que tu as appris la programmation avec un autre langage que Python. Est-ce que je me trompe ? Si ce n'est pas le cas, c'est que tu ne t'es pas encore assez imbibé d'un certain esprit Python.

Par exemple, quand on voit
if x>y and y !=0:

dans un code, il faut tout de suite se dire: «ça m'étonnerait que Python ne permette pas d'écrire cette condition avec plus de concision»
Et effectivement
if x>y>0:  
suffit
(à condition que x et y ne soient que des positifs ou nuls, ce que je crois être le cas pour repérer des pixels, non ?)



Je t'en dis plus dans un prochain message.
En tous cas, c'est agréable d'avoir affaire à quelqu'un qui sait se débrouiller tout seul.
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
heyquem Messages postés 759 Date d'inscription mercredi 17 juin 2009 Statut Membre Dernière intervention 29 décembre 2013 131
1 déc. 2009 à 18:19
Une condensation pythonienne

if x>y and y !=0:
        #Endroit de stockage de la trajectoire:
        point_courant = [(COORDi,COORDi)]
        #vecteur minimal exact :
        vmx,vmy = (float(x)/float(y),1)
        #On cherche a atteindre le point x,y a partir du point initial ,
        # ici le y du vecteur minimal est de 1 on va donc en repeter l'ajout y fois .
        for i in range(y):
            COORDi = point_courant[i][0]
            point_courant.append(((COORDi[0]+vm[0],COORDi[1]+vm[1]),(int(COORDi[0]+vm[0]),COORDi[1]+1)))

        for i in range(y):
            COURx,COURy = point_courant[i]
            'COORDi =',COORDi
            point_courant.append( ((COURx+vmx,COURy+vmy),(int(COURx+vmx),COURy+1)) )



vmx,vmy = (float(x)/float(y),1)
et
COURx,COURy = point_courant[i]
sont des instructions d' umpacking = dépaquetage
https://www.developer.com/article.php/630101


-------------------------------------------------------------------------------------


Mais le principe n'est pas bon dans la boucle:
tu enregistres un couple de tuples
((COURx+vmx,COURy+vmy),(int(COURx+vmx),COURy+1))
dans point_courant
pour disposer de (COURx+vmx,COURy+vmy) au tour suivant en prenant point_courant[i][0]

Mais comme vm = (float(x)/float(y),1) ,
vm[1] vaut 1, et donc tu enregistres des couples de tuples uniquement parce que
int(COURx+vmx) est différent de COURx+vmx. Il faut sentir qu'il y a une redondance là-dedans.

Pour éviter d'avoir à chercher COORDi au tour suivant par COORDi = point_courant[i][0]
il faut s'appuyer sur le fait que COURx+vmx et COURy+1 varient de façon linéaire: COURx+vmx s'accroît de vmx chaque fois que COURy+1 s'accroît de 1. On peut donc faire dépendre la variation de COURx+vmx de l'indice de variation de COURy+1 , ici 1 donc c'est simple.

En partant du point initial zéro (COURx,COURy):
le point 1 est int(COURx+vmx), COURy+1
le point 2 est int(COURx+2*vmx), COURy+2
etc

D'où
    if x>y and y !=0:
        #Endroit de stockage de la trajectoire:
        COORDix,COORDiy = COORDi # COORDi est une liste
        parcours = [(COORDix,COORDiy)] # on enregistre un tuple
        #vecteur minimal exacte :
        vmx,vmy = (float(x)/float(y),1)
        #On cherche a atteindre le point x,y a partir du point initial ,
        # ici le y du vecteur minimal est de 1 on va donc en repeter l'ajout y fois .
        for i in range(1,y+1):
            parcours.append((int(COORDix + i*vmx),COORDiy + i))




Ensuite on peut améliorer encore plein de choses:
remplacer
for i in range(1,y+1):
par
for i in xrange(1,y+1):

range(1,y+1) est une liste créée dans la mémoire vive avant d'être parcourue.
xrange(1,y+1) est un générateur de liste, les valeurs 1,2,3,4 sont débitées par le générateur au fur et à mesure des besoins, cela économise de la place mémoire et évite des opérations inutiles.




La plus grosse amélioration, ce sera de remplacer la boucle for i in xrange(1,y+1): par une list comprehension.
Je te laisse chercher. Ça remplace le contenu de la section if x>y and y !=0: par deux lignes (parce qu'on peut sortir une affectation...).


Bon je n'en dis pas plus.
Il y a d'autres choses à améliorer.
J'en ai découverte une juste maintenant que je ne connaissais pas. Très fort, Python.
0
Salut !

Pour mon algo, comme tu le dis "C'est ce reste ... qui à mon avis doit toujours être assez petit relativement à la différence ..." .
Le reste est en effet dépendant de y (r<y/2) et dans ton exemple il est en effet assez petit .
Mais, si l'on prend des distances plus grandes, d'un bout à l'autre de l'écran par exemple, le reste devient conséquent .Le deuxième segment apparait alors plus nettement .(Je l'ai testé graphiquement.)

En partant du coin haut-gauche pour aller au coin bas-droit par exemple on a un segment qui ressemble à ça:

\
 \
  \
   \_

Ce qui n'est pas un mouvement naturel .

Pour le code, oui , il est à revoir ce n'était qu'un code éclair afin de tester ton algorithme , il lui manque d'ailleurs la gestion des vecteurs négatifs .Et je suis sur qu'il y a beaucoup de chose à améliorer ;).

Quant à python , tu te trompes :p , c'était bien mon premier langage, mais je n'ai pas suivi de cours structuré, de tutoriels ou quoi que ce soit .C'était plutôt un apprentissage lent(5ans) à coups d'erreurs ...ceci explique cela ...

Pour x et y , leur nom a du t'induire en erreur (et également le fait que je n'ai pas géré le cas où elles seraient négatives), ce ne sont pas des pixels mais bien les "valeurs" d'un vecteur, elles peuvent donc être négatives ;) .


Voici une version fonctionnelle de mon code :

def calcul_coef1(COORDi,COORDf):
    #COORDi: coordonnées initiales
    #COORDf: coordonnées finales  
    #valeures du vecteur     
    y = COORDf[1]-COORDi[1]
    x = COORDf[0]-COORDi[0]
    #On cherche a avoir un coef de forme (X,1) ou (1,Y) pour que le mouvement ne soit pas saccadé
    if x>y and x!=0:
        #Endroit de stockage de la trajectoire:
        point_courant = [(COORDi,COORDi)]
        #vecteur minimal exacte :
        vm = (float(x)/float(y),1)
        if x<0:
            vm = (-vm[0],vm[1])
        if y<0:
            vm = (vm[0],-vm[1])
        
        #On cherche a atteindre le point x,y a partir du point initial , ici le y du vecteur minimal est de 1 on va donc en repeter l'ajout y fois .
        for i in range(int(math.fabs(y))):
            COORDi = point_courant[i][0]            
            point_courant.append(((COORDi[0]+vm[0],COORDi[1]+vm[1]),(int(COORDi[0]+vm[0]),COORDi[1]+vm[1])))
                  
        
        
    elif x<y and x != 0:
        #Endroit de stockage de la trajectoire:
        point_courant = [(COORDi,COORDi)]
        #vecteur minimal exacte :
        vm = (1,float(y)/float(x))
        if x<0:
            vm = (-vm[0],vm[1])
        else:
            vm = (math.fabs(vm[0]),vm[1])
        if y<0:
            vm = (vm[0],-vm[1])
        else:
            vm = (vm[0],math.fabs(vm[1]))
        #On cherche a atteindre le point x,y a partir du point initial , ici le y du vecteur minimal est de 1 on va donc en repeter l'ajout y fois .
        for i in range(int(math.fabs(x))):
            COORDi = point_courant[i][0]
            point_courant.append((
                (COORDi[0]+vm[0],COORDi[1]+vm[1]),
                (COORDi[0]+vm[0],int(COORDi[1]+vm[1]))
                ))
            
    elif x == 0:        
        point_courant = [(COORDi,COORDi)]
        vm = (0,1)
        if x<0:
            vm = (-vm[0],vm[1])
        else:
            vm = (math.fabs(vm[0]),vm[1])
        if y<0:
            vm = (vm[0],-vm[1])
        else:
            vm = (vm[0],math.fabs(vm[1]))
        for i in range(y):
            COORDi = point_courant[i][0]
            point_courant.append(((COORDi[0]+vm[0],COORDi[1]+vm[1]),(COORDi[0]+vm[0],COORDi[1]+vm[1])))
    elif y == 0:
        point_courant = [(COORDi,COORDi)]
        vm = (1,0)
        if x<0:
            vm = (-vm[0],vm[1])
        else:
            vm = (math.fabs(vm[0]),vm[1])
        if y<0:
            vm = (vm[0],-vm[1])
        else:
            vm = (vm[0],math.fabs(vm[1]))
        for i in range(x):
            COORDi = point_courant[i][0]
            point_courant.append(((COORDi[0]+vm[0],COORDi[1]+vm[1]),(COORDi[0]+vm[0],COORDi[1]+vm[1])))
    else:
        point_courant = [(COORDi,COORDi)]
        vm = (1,1)
        if x<0:
            vm = (-vm[0],vm[1])
        else:
            vm = (math.fabs(vm[0]),vm[1])
        if y<0:
            vm = (vm[0],-vm[1])
        else:
            vm = (vm[0],math.fabs(vm[1]))
        for i in range(x+2):
            COORDi = point_courant[i][0]
            point_courant.append(((COORDi[0]+vm[0],COORDi[1]+vm[1]),(COORDi[0]+vm[0],COORDi[1]+vm[1])))

    return point_courant




Il est vrai qu'elle "un peu" illisible :D !
J'apprécierai tout conseil que tu peux me donner pour en améliorer la lisibilité , ou la pythinicité (ou peut-être la pythonité , le pythonisme ?Bref '>> ) .
Cependant je pense qu'il serait plus sage de continuer via mail étant donné que le problème a été résolu , et l'algorithme proposé dans une version fonctionnelle .A moins bien sur que tu veuilles y apporter d'autres modifications ?

Bonsoir .
0
Oups, j'avais pas vu ton message .

Est-ce la solution que tu attends :



vmx,vmy = (float(x)/float(y),1)
[(int(COORDix + i*vmx),COORDiy + i) for i in xrange(1,math.fabs(y)+1) if x>y and y!=0]

vmx,vmy = (1,float(y)/float(x))
[(COORDix + i,int(COORDiy + i*vmy)) for i in xrange(1,math.fabs(x)+1) if x<y and x!=0]

vmx,vmy = (0,1)
[(COORDix,COORDiy + i) for i in xrange(1,math.fabs(y)+1) if x==0]

vmx,vmy = (1,0)
[(COORDix + i,COORDiy) for i in xrange(1,math.fabs(x)+1) if y==0]

vmx,vmy = (1,1)
[(COORDix + i,COORDiy+i) for i in xrange(1,math.fabs(x)+1) if y==x]



As tu une idée de la façon dont je pourrais me débarrasser de :

if x<0:
            vm = (-vm[0],vm[1])
        else:
            vm = (math.fabs(vm[0]),vm[1])
        if y<0:
            vm = (vm[0],-vm[1])
        else:
            vm = (vm[0],math.fabs(vm[1]))


A bientot .
0
heyquem Messages postés 759 Date d'inscription mercredi 17 juin 2009 Statut Membre Dernière intervention 29 décembre 2013 131
2 déc. 2009 à 13:26
Bonjour,

J'ai été un peu long parce que ça m'a donné du fil à retordre.






J'avais vu ton précédent message et je ne comprenais pas pourquoi tu n'utilisais pas le dépaquetage. Bon, maintenant, ça va.

Les list comprehension, c'est bon aussi, c'est ça. Mais il faut affecter la liste résultante , sinon elle devient quoi ?
Comme il s'agit de la succession des points constituant le parcours, appelons ces listes parcours.

Pour x>y>0 (par exemple), vmy vaut 1 ==> pas la peine d'affecter 1 à une variable vmy et d'utiliser ensuite vmy pour spécifier 1 comme c'est le cas dans vm = (vm[0],math.fabs(vm[1])) !

abs() est disponible comme built-in fonction. Je ne connais pas ce que fabs() du module math apporte de plus.

Que x soit + ou - , que y soit + ou -, ça ne me semble pas compter beaucoup, Ce qui importe le plus , c'est de savoir laquelle des deux variables en valeurs absolues est la plus grande.





Voici ce que je te propose comme code.
Je ne suis pas très pédagogique, je balance un code tout fait et peaufiné. Mais sinon ça va trainer en longueur. Mine de rien, il faut de la concentration sur ce problème et j'ai eu un peu de mal.
J'ai taillé à la serpe et le Python arrive à reculer loin en voyant la serpe :-)



def calcul_coef1((COORDix,COORDiy),(COORDfx,COORDfy)):
    # (COORDix,COORDiy) : coordonnées initiales
    # (COORDfx,COORDfy) : coordonnées finales
    x,y = COORDfx-COORDix , COORDfy-COORDiy
    if x:  sx = x/abs(x)
    else:  sx = 0
    if y:  sy = y/abs(y)
    else:  sy = 0
    ecrir = '\nx = '+str(x)+'  y = '+str(y)+'  sx = '+str(sx)+'  sy = '+str(sy)

    if 0!=abs(y)<abs(x):
        vmx = float(x)/y
        ecrir += '\nitere en y :   range(0,'+str(y+1)+','+str(sy)+') = '+repr(range(0,y+sy,sy))
        ecrir += '\n'+repr( [COORDix + k*vmx for k in xrange(0,y+sy,sy)] )
        ecrir += '\n'+repr( [COORDix + k*vmx + 0.47 for k in xrange(0,y+sy,sy)] )
        ecrir += '\n'+repr( [int(COORDix + k*vmx + 0.47) for k in xrange(0,y+sy,sy)] )
        parcours = [ (int(COORDix + k*vmx + 0.47),COORDiy + k) for k in xrange(0,y+sy,sy) ]

    elif 0!=abs(x)<abs(y):
        vmy = float(y)/x
        ecrir += str(vmy)
        ecrir += '\nitere en x :   range(0,'+str(x+1)+','+str(sx)+') = '+repr(range(0,x+sx,sx))
        ecrir += '\n'+repr( [COORDiy + k*vmy for k in xrange(0,x+sx,sx)] )
        ecrir += '\n'+repr( [COORDiy + k*vmy + 0.47 for k in xrange(0,x+sx,sx)] )
        ecrir += '\n'+repr( [int(COORDiy + k*vmy + 0.47) for k in xrange(0,x+sx,sx)] )
        parcours = [ (COORDix + k,int(COORDiy + k*vmy + 0.47 )) for k in xrange(0,x+sx,sx) ]
        
    else:
        parcours = [ (COORDix + sx*k,COORDiy + sy*k) for k in xrange(0,max(abs(x),abs(y))+1) ]

    return ecrir + '\n' + repr(parcours)
    

li = [ ((2,3),(10,28),'\n|x|<|y|  direction -> et haut') ,
       ((3,33),(10,4),'\n|x|<|y|  direction -> et bas' ) ,
       ((203,6),(192,22),'\n|x|<|y|  direction <- et haut') ,
       ((134,56),(126,22),'\n|x|<|y|  direction <- et bas') ,
       ((2,3),(26,12),'\n|x|>|y|  direction -> et haut') ,
       ((3,33),(31,26),'\n|x|>|y|  direction -> et bas') ,
       ((160,88),(137,98),'\n|x|>|y|  direction <- et haut') ,
       ((178,47),(104,38),'\n|x|>|y|  direction <- et bas') ,
       ((15,7),(25,7),'\ny==0  direction horizontale vers la droite') ,
       ((38,7),(28,7),'\ny==0  direction horizontale vers la gauche') ,
       ((8,95),(8,106),'\nx==0  direction verticale vers le haut'),
       ((8,108),(8,99),'\nx==0  direction verticale vers le bas') ]

       
print '\n\n--------------------------------------------------------------\n'\
      .join([ repr(alpha)+'  vers  '+repr(desti)+'  '+libel + calcul_coef1(alpha,desti)
              for (alpha,desti,libel) in li ])












Ce qui me réjouit , c'est le
def calcul_coef1((COORDix,COORDiy),(COORDfx,COORDfy)):
et l'appel de la fonction par
calcul_coef1(alpha,desti)
La fonction est définie avec 4 paramètres, on appelle la fonction en lui passant 2 arguments qui sont 2 tuples, et la fonction dépaquète les 2 arguments dans les 4 paramètres. (personnellement je fais une différence entre argument et paramètre, c'est personnel
Je ne connaissais pas cette possibilité: Python ouhaaa !



Dans le code ci-dessus, j'ai cumulé tous les résultats dans une liste qui est affichée avec la méthode join() pour qu'il y ait affichage instantané.



--------------------------------------------



La motivation du 0.47 dans int(COORDix + k*vmx + 0.47) et int(COORDiy + k*vmy + 0.47) , c'est que int() tire toujours la valeur vers le bas.
Je me suis aperçu du problème que ça entraine parfois sur le cas (3, 33) vers (10, 4)
Pour ce cas: x = 7 et y = -29 ==> vmy = -29/7 = -4.14285714286

Mais cette valeur est un arrondi. J'ai découvert en creusant les choses que print -29/7,[-29/7] affiche
-4.14285714286 [-4.1428571428571432] !
C'est quelque chose que je ne connaissais pas.

Donc -4.1428571428571432 est une valeur de -29/7 plus précise que -4.14285714286
Mais ce doit ête elle même un arrondi, car si on fait
a = float(-29)/7
for i in xrange(0,7+1):
    print 33+i*a,[33+i*a]

on obtient
33.0 [33.0]
28.8571428571 [28.857142857142858]
24.7142857143 [24.714285714285715]
20.5714285714 [20.571428571428569]
16.4285714286 [16.428571428571427]
12.2857142857 [12.285714285714285]
8.14285714286 [8.1428571428571388]
4.0 [3.9999999999999964]


Et int(3.9999999999999964) , la vraie valeur, donne 3 et non pas 4 ! Le pièèèèèège !




De par le raisonnement tenu depuis le départ, une valeur COORDix + k*vmx ou COORDiy + k*vmy est plus proche d'un pixel au début et à la fin de l'intervalle d'itération qu'au milieu:
- plus proche par dessus au début et plus proche par dessous à la fin, si l'itération est croissante
- plus proche par dessous au début et plus proche par dessus à la fin si l'itération est décroissante (cas 33 à 4)

La dernière valeur calculée d'une itération est en particulier très très proche de l'entier terminal (dans l'exemple: 4)

Les entiers recherchés sont aussi toujours positifs (des coordonnées de pixels).

Donc, que l'on atteigne le dernier entier par le haut dans une itération décroissante (l'exemple ci dessus, 33 à 4) ou par le bas, étant donné que int() rabaisse toujours la valeur, il vaut mieux s'assurer d'être:
- toujours un peu plus au dessus à la fin d'une itération croissante pour être éventuellement rabaissé vers la bonne valeur par int() dans le cas où un arrondi de calcul un peu trop faible fixerait la dernière valeur calculée un petit peu en dessous du bon entier (je n'ai pas cherché d'exemple pour ce cas)
- toujours un peu plus au dessus encore à la fin d'une itération décroissante pour être éventuellement rabaissé vers la bonne valeur par int() dans le cas où un arrondi de calcul un peu trop fort (et négatif) fixerait la dernière valeur calculée un petit peu en dessous du bon entier ( cas 33 à 4)

Étant donné que la différence éventuelle est toujours très minime, car due à des arrondis, on pourrait se permettre de ne rajouter que 0,01 par exemple.

Mais il vaut mieux ajouter plus, de façon à corriger l'effet de int(), qui tire vers le bas, de façon équitablement répartie dans l'intervalle:
donc avec 0,47 par exemple;
il vaut mieux éviter de mettre 0,5, on risquerait de tomber dans d'autres effets de bord.



Nota bene: je ne crois pas me tromper en pensant qu'il faut toujours AJOUTER + 0,47 . Le fait que l'itération soit croissante ou décroissante ne joue pas: on veut corriger l'effet de int() qui tire toujours vers le bas.


----------------------------------------


Voilà, ouf ! Pas facile à expliquer.
Mais les problèmes sont bien réels puisque je les ai rencontrés.
Ce avec quoi j'ai eu le plus de difficulté, c'est d'organiser correctement les conditions if .

Toutes les lignes avec ecrir += etc peuvent être enlevées dans le code définitif.

Il reste une possibilité de condenser le code parce que les deux sections if 0!=abs(y)<abs(x): et elif 0!=abs(x)<abs(y): sont quasiment identiques par permutation x<--> y. Mais c'est le "quasiment" qui pose problème pour condenser ces deux sections en une seule.

Il faut aussi réfléchir à transformer ta fonction en fonction génératrice avec des yield, ça ira encore plus vite.





Je vais continuer à réfléchir au problème , c'est amusant.
Et ça donne une bonne idée de la puissance de Python qui permet:
- de bénéficier de la concision naturelle de Python (expressivité maximale en peu de mots) pour obtenir un code court et lisible. Comparer à ton code du début.
- de réfléchir à l'algorithme sur la base du code parce que celui-ci est court et que cela permet d'avoir beaucoup de choses sous les yeux, et aussi très lisible. Une condition if 0!=abs(y)<abs(x): en C++ , j'aimerais bien voir ce que ça donne en longueur et en lisibilité !
0
Salut ,
merci pour ce joli code bien peaufiné :-) .

Pour fabs et abs , ils ont les même fonctions , je pensais à tord que abs était à l'origine de l'arrondi lors du calcul de x/y (alors qu'il fallait faire float(x)/float(y) ).Après j'ai oublié de le rechanger .

Bien trouvé pour l'addition du 0,47 .

Je ne pense pas que condenser encore plus le code soit possible, surtout avec le int que l'on applique qu'a l'un des paramètres ...

Sinon pour la transformation en fonction génératrice , cela me pose quelques problemes vis-à-vis du fonctionnement du reste de l'application:

D'abord j'utilise len(parcours) pour déterminer le temps d'attente entre chaque avancée du joueur,
un code dans le genre :

parcours = calc_coef(arg1,arg2)
calcx,calcy = parcours[0][0]-parcours[1][0],parcours[0][1]-parcours[1][1]
if calcx >= calcy: wait = calcx
else : wait = calcy
for etape in xrange(len(parcours)):
    player.x = parcours[etape][0]
    player.y = parcours[etape][1]
    time.sleep(0.0026*wait)
    



Ensuite j'utilise plusieurs étapes du parcours à la fois, quand je verifie qu'il n'y a pas de cases inaccessibles dans le parcours :

# perm = False si une case n'est pas accessible
#check case renvoit l'instance case à partir de coordonnées fournies
perm_fail = []
    etapes = calcul_coef((x,y),pos)
    for etape in etapes:
        if not check_case(etape).perm:
            try:
                perm_fail.append(etapes[etapes.index(etape)-1])
            except:
                perm_fail.append((player.x,player.y))
            break



Enfin j'ai remarqué à la pratique un problème qui tient à l'algo lui-même:
Lorsque le joueur va de (x,y) à (x1,y1) ,si x1 ~= x ou y1~= y il n'y aura qu'une étape .
Ce qui pourrait être résolu par un code ressemblant à ceci :


if x-x1 <30: #Au hasard 30 ,  je regarderais plus tard quelle différence minimum est nécessaire pour un déplacement réaliste 
    parcours0 = calcul_coef((coordix,coordiy),(coordix,coordfy))
    parcours1 = calcul_coef((coordix,coordfy),(coordfx,coordfy))




On retrouve un déplacement en deux segments mais c'est tout de même plus réaliste.

Bonne soirée .
0