IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

 C Discussion :

tri de chaines de caractères


Sujet :

C

  1. #1
    Membre averti Avatar de bosk1000
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    706
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 706
    Points : 367
    Points
    367
    Par défaut tri de chaines de caractères
    Bonjour à tous

    je doit faire un programme de tri de chaines de caractère

    celui que j'ai bug après la saisie des chaines.

    Pouvez-vous me dire pourquoi ?

    merci

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
     
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
     
    //tri des chaines
    void tri(char *tableau[], int nombre);
    //reaffichage de l'ecran
    void affiche(char *tableau[], int nombre);
     
    //fonction pour lire ce qui est tapé au clavier
    int lire_clavier(char *saisie_clavier, int taille)
    {
        int compteur;
        fgets(saisie_clavier, taille, stdin);
        saisie_clavier[taille-1]='\0';
        for (compteur=0; saisie_clavier[compteur];compteur++)
        {
            if(saisie_clavier[compteur]=='\n')
            {
                saisie_clavier[compteur]='\0';
                break;
            }
        }
        return (compteur);
    }
    // initialisation d'une chaine de 25 caractère
    char *chaine[25];
     
    //début du programme principal
    int main()
    {
        //initialisation des variables
        int nombre_a_trie=0;
        int nombre=0;
        char buffer[80];
     
        //definition et affichage du titre
        printf("Programme de tri de chaines de caracteres\ncombien de caratere a trie\n");
     
        //lecture de la donne qui est mis dans la variable nombre_a_trie
        scanf("%d",&nombre_a_trie);
        //affichage du message
        printf ("\nSaisir les %d chaines\n\n", nombre_a_trie);
        //on incremente nombre_a_trie
        nombre_a_trie++;
        // tant que n est inferieur à nombre_a_trie
        while(nombre<nombre_a_trie)
        {
            //appel de la fonction lire clavier
            lire_clavier(buffer, sizeof(buffer));
            //la valeur retournee n'est pas nulle on effectue la suite
            if((chaine[nombre]=malloc(strlen(buffer)+1))!=NULL)
            {
             //on recopie la chaine qui a ete tapee dans la variables chaine
                strcpy(chaine[nombre++],buffer);
            }
        }
        //on appelle la fonction de tri en lui fournissant le tableau et le nombre de ligne
        tri(chaine, nombre_a_trie);
        printf("\nResultat du tri\n");
        //on appelle la fonction d'affichage du tableau trié
        printf("\n\n");
        //on libère la memoire
        free(0);
    //   sytem("pause");
        return (0);
    }
     
    //fonction de tri des données
    void tri(char *tableau[], int nombre)
    {
        //initialisation des variables
     
        char *tableau2;
        // pour i allant de 1 à n par pas de 1 faire -n etant donne par la fonction appellante
        for(int compteur=1;compteur<nombre;compteur++)
        //pour j allant de 0 à n-1 par pas de 1 faire -n  etant donne par la fonction appellante
            for (int compteur2=0;compteur2<nombre+1;compteur2++)
            {
                if (strcmp(tableau[compteur2], tableau[compteur2+1]>0))
                {
                    tableau2=tableau[compteur2];
                    tableau[compteur2]=tableau[compteur2+1];
                    tableau[compteur2+1]=tableau2;
                }
     
            }
    }
    //fin de tri
    //fonction d affichage du tableau trie
    void affiche(char *tableau[], int nombre)
    {
        // intialisation des variables
        int compteur;
        // pour count allant de 0 à n par pas de 1-t et n etant données par la fonction appellante
        for (compteur=0;compteur<nombre;compteur++)
        // on affiche les donnée
            printf("%s\n",tableau[compteur]);
    }

  2. #2
    Invité
    Invité(e)
    Par défaut
    Bonsoir,

    Cette ligne 16 saisie_clavier[taille-1]='\0'; est inutile. Ce que tu pourrais par contre faire, c'est vérifier le retour de fgets.
    Citation Envoyé par bosk1000
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
        //on libère la memoire
        free(0);
    Ceci ne libère absolument rien du tout !

    Ta variable globale char *chaine[25]; n'est absolument pas justifiée, et son commentaire // initialisation d'une chaine de 25 caractère ne reflète pas son vrai type.

    Concernant ton bug, compile avec au moins les options -Wall et -Wextra tu trouveras alors très rapidement le soucis

  3. #3
    Membre averti Avatar de bosk1000
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    706
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 706
    Points : 367
    Points
    367
    Par défaut
    Citation Envoyé par Winjerome Voir le message
    Bonsoir,
    Concernant ton bug, compile avec au moins les options -Wall et -Wextra tu trouveras alors très rapidement le soucis
    Je ne connais pas les éléments donc tu parles (wal et wextra)
    ce sont des lignes de commandes ou des logiciel ??

  4. #4
    Membre averti Avatar de bosk1000
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    706
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 706
    Points : 367
    Points
    367
    Par défaut
    j'ai donc mis quelques modification sur tes conseils

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
     
     
    //fonction pour lire ce qui est tapé au clavier
    int lire_clavier(char *saisie_clavier, int taille)
    {
        int compteur;
        fgets(saisie_clavier, taille, stdin);
     
     
        //   saisie_clavier[taille-1]='\0';
        for (compteur=0; saisie_clavier[compteur];compteur++)
        {
            if(saisie_clavier[compteur]=='\n')
            {
                saisie_clavier[compteur]='\0';
                break;
            }
        }
        printf("la saisie est : %s\n",saisie_clavier);
        return (compteur);
    }
    le retour visuel à l'écran est correct

  5. #5
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    -Wall et -Wextra sont des options de gcc. (gcc *.c -Wall -Wextra)
    Si tu utilises mingw, c'est ton compilateur.
    Il s'agit de régler ton EDI pour utiliser un maximum de warnings.
    pour visual, c'est le paramètre /W4 (ou /W3 dans un premier temps).

    Les warnings soulignent des erreurs probables.
    Il faut toujours les considérer commes des erreurs.
    Il y a deux solutions possibles: suivre les indications (tel que rendre const ce qu'il faut, trouver le bon paramètre, etc) ou rendre le choix fait explicitement volontaire.
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  6. #6
    Membre averti Avatar de bosk1000
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    706
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 706
    Points : 367
    Points
    367
    Par défaut
    ok

    donc ce sont les warning qui devrait m'aider

    sur la ligne if (strcmp(tableau[compteur2], tableau[compteur2+1]>0)) de la fonction void tri


    j'ai 3 warnings sur cette ligne
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //fonction de tri des données
    void tri(char *tableau[], int nombre)
    {
        //initialisation des variables
     
        char *tableau2;
        // pour i allant de 1 à n par pas de 1 faire -n etant donne par la fonction appellante
        for(int compteur=1;compteur<nombre;compteur++)
        //pour j allant de 0 à n-1 par pas de 1 faire -n  etant donne par la fonction appellante
            for (int compteur2=0;compteur2<nombre+1;compteur2++)
            {
                if (strcmp(tableau[compteur2], tableau[compteur2+1]>0))
                {
                    tableau2=tableau[compteur2];
                    tableau[compteur2]=tableau[compteur2+1];
                    tableau[compteur2+1]=tableau2;
                }
     
            }
    }
    1) ordered comparison of pointer whith integer zero [-Wextra]
    2)passing argumebt 2 of 'strcmp' makes pointer from integer without a cast [enabled by default]
    in file included from ..\tri_caractère\main.c:3:0:
    expected 'const char*' but argument is of type 'int'
    3) null argument where non-null required (argument 2) [-Wnonnull]


    voila avec tout ça je vois surtout que le problème ce situe sur ce if,

  7. #7
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    Tu as le >0 qui est dans le strcmp() au lieu d'être après.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  8. #8
    Membre averti Avatar de bosk1000
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    706
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 706
    Points : 367
    Points
    367
    Par défaut
    Merci

    j'ai toujours le bug, mais plus aucun warning !!

  9. #9
    Invité
    Invité(e)
    Par défaut
    Il est alors temps d'utiliser ton débogueur gdb

  10. #10
    Membre averti Avatar de bosk1000
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    706
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 706
    Points : 367
    Points
    367
    Par défaut
    Merci
    pour la première fois j'ai utiliser le débogueur

    donc j'obtiens
    "l'inférieur à stoppé car il a reçu un signal du système d'exploitation.
    Nom du signal : SIGSEGV
    Signification du signal : Segmentation faul

    toujours la même ligne qui cause mon soucis

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    if (strcmp(tableau[compteur2], tableau[compteur2+1])>0)
    mon soucis reste cette fonction tri
    j'ai apporter une petite modification sur cette ligne
    mais sans succès


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
     
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
     
    //tri des chaines
    void tri(char *chaine[], int nombre);
    //reaffichage de l'ecran
    void affiche(char *tableau[], int nombre);
     
    //fonction pour lire ce qui est tapé au clavier
    int lire_clavier(char *saisie_clavier, int taille)
    {
        int compteur;
        fgets(saisie_clavier, taille, stdin);
     
     
        //   saisie_clavier[taille-1]='\0';
        for (compteur=0; saisie_clavier[compteur];compteur++)
        {
            if(saisie_clavier[compteur]=='\n')
            {
                saisie_clavier[compteur]='\0';
                break;
            }
        }
        printf("la saisie est : %s\n",saisie_clavier);
        return (compteur);
    }
     
     
     
    // initialisation d'une chaine de 25 caractère
    char *chaine[25];
     
     
    //début du programme principal
    int main()
    {
        //initialisation des variables
        int nombre_a_trie=0;
        int nombre=0;
        char buffer[80];
     
     
        //definition et affichage du titre
        printf("Programme de tri de chaines de caracteres\ncombien de caratere a trie\n");
     
        //lecture de la donne qui est mis dans la variable nombre_a_trie
        scanf("%d",&nombre_a_trie);
        //affichage du message
        printf ("\nSaisir les %d chaines\n\n", nombre_a_trie);
        //on incremente nombre_a_trie
        nombre_a_trie++;
     
     
        // tant que nombre est inferieur à nombre_a_trie
        while(nombre<nombre_a_trie)
        {
            //appel de la fonction lire clavier
            lire_clavier(buffer, sizeof(buffer));
            //la valeur retournee n'est pas nulle on effectue la suite
            if((chaine[nombre]=malloc(strlen(buffer)+1))!=NULL)
            {
             //on recopie la chaine qui a ete tapee dans la variables chaine
                strcpy(chaine[nombre++],buffer);
            }
        }
     
        //on appelle la fonction de tri en lui fournissant le tableau et le nombre de ligne
        tri(chaine, nombre_a_trie);
        printf("\nResultat du tri\n");
        //on appelle la fonction d'affichage du tableau trié
        printf("\n\n");
        //on libère la memoire
        free(0);
    //   sytem("pause");
        return (0);
    }
     
    //fonction de tri des données
    void tri(char *chaine[], int nombre)
    {
        //initialisation des variables
        char *tableau2;
        // pour compteur allant de 1 à nombre par pas de 1 faire -nombre etant donne par la fonction appellante
        for(int compteur=1;compteur<nombre;compteur++)
        //pour compteur2 allant de 0 à nombre-1 par pas de 1 faire -nombre  etant donne par la fonction appellante
            for (int compteur2=0;compteur2<nombre+1;compteur2++)
            {
                if (strcmp(chaine[compteur2], chaine[compteur2+1])>0)
                {
                    tableau2=chaine[compteur2];
                    chaine[compteur2]=chaine[compteur2+1];
                    chaine[compteur2+1]=tableau2;
                }
            }
    }
    //fin de tri
    //fonction d affichage du tableau trie
    void affiche(char *tableau[], int nombre)
    {
        // intialisation des variables
        int compteur;
        // pour compteur allant de 0 à nombre par pas de 1-tableau et nombre etant données par la fonction appellante
        for (compteur=0;compteur<nombre;compteur++)
        // on affiche les donnée
            printf("%s\n",tableau[compteur]);
    }
    donc je comprend ceci :
    SIGSEGV : Il signifie « signal de violation de segmentation » (Signal Segmentation Violation).

    C'est un signal envoyé à un processus lorsque celui-ci fait référence à une zone de mémoire invalide, par exemple parce qu'elle ne lui appartient pas.

  11. #11
    Invité
    Invité(e)
    Par défaut
    Localiser la ligne, c'est une première chose
    Maintenant sache qu'au moment où tu obtiens ce message, tu peux demander à gdb les différentes valeurs de tes variables (et pleins d'autres choses que je te laisse découvrir avec une simple recherche ), et aller au bout de ta réflexion afin de comprendre le pourquoi de cette erreur et la corriger.

  12. #12
    Membre averti Avatar de bosk1000
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    706
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 706
    Points : 367
    Points
    367
    Par défaut
    j'ai tout d'abord changer
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
                if (strcmp(tableau[compteur2], tableau[compteur2+1])>0)
                {
                    tableau2=tableau[compteur2];
                    tableau[compteur2]=tableau[compteur2+1];
                    tableau[compteur2+1]=tableau2;
    en
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
                if (strcmp(chaine[compteur2], chaine[compteur2+1])>0)
                {
                    tableau2=chaine[compteur2];
                    chaine[compteur2]=chaine[compteur2+1];
                    chaine[compteur2+1]=tableau2;
    si je me trompe n'hésite pas à me le dire

    Qu'appelle tu gdb ?

  13. #13
    Membre averti Avatar de bosk1000
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    706
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 706
    Points : 367
    Points
    367
    Par défaut
    pendant mes rechecher sur le déboguage
    en clicquant sur le point rouge de la ligne
    j'obtien ceci

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    0x7752000c                   cc              int3
    0x7752000d  <+0x0001>        c3              ret
    0x7752000e  <+0x0002>        90              nop
    0x7752000f  <+0x0003>        90              nop
    0x77520010  <+0x0004>        8b 4c 24 04     mov    0x4(%esp),%ecx
    0x77520014  <+0x0008>        f6 41 04 06     testb  $0x6,0x4(%ecx)
    0x77520018  <+0x000c>        74 05           je     0x7752001f <ntdll!DbgBreakPoint+19>
    0x7752001a  <+0x000e>        e8 a1 1d 01 00  call   0x77531dc0 <ntdll!ZwTestAlert>
    0x7752001f  <+0x0013>        b8 01 00 00 00  mov    $0x1,%eax
    0x77520024  <+0x0018>        c2 10 00        ret    $0x10
    0x77520027  <+0x001b>        90              nop

    est-ce utilisable ou est-ce que je me perd

  14. #14
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut
    Ca s'appelle de l'assembleur, et à ton niveau (et pour ce problème a fortiori), ça ne te sert à rien.

    Pour incrémentes-tu nombre_a_trie après l'avoir demandé à l'utilisateur ?

    Je me demande si tu ne pas pas un cran trop loin lors de ta comparaison de chaine. Ainsi, tu vas lire hors de limites de ton tableau de caractères et tu te prends une segfault. Combien vaut le compteur au moment du plantage ?

    PS : tes variables sont franchement pas bien nommées ! "nombre", "compteur" et "compteur2" dans la méthode de tri !

    PS2: mets un printf() avant ton strcmp() et regarde la valeur du compteur, je crois effectivement que tu vas trop loin.

  15. #15
    Membre à l'essai
    Homme Profil pro
    Master Informatique seconde année
    Inscrit en
    Septembre 2014
    Messages
    16
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Master Informatique seconde année
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2014
    Messages : 16
    Points : 15
    Points
    15
    Par défaut
    Valgrind peut aider pour te dire si tu "vas trop loin", pour des problèmes de pointeurs/allocations mémoire en règle générale aussi.
    Il faut cependant compiler avec -g3 pour que les messages d'erreurs de valgrind t'indiquent la ligne exacte de ton problème.

    Edit : En l'occurence, Valgrind me sort que tu "vas trop loin" effectivement, sur la ligne 58.
    Edit2 : Que se passe-t-il si j'entre un nombre de chaînes à comparer plus grand que 25 ? Idem pour une chaîne plus grande que 80 caractères ?

  16. #16
    Membre averti Avatar de bosk1000
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    706
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 706
    Points : 367
    Points
    367
    Par défaut
    merci
    je prend en compte vos commentaire et me repenche sur le cheminement et les nom de variable

    merci pour l'info sur l'outil Valgrind, à etudier comme logicie

    je bosse dessus et reviens par la suite

Discussions similaires

  1. Programme de tri de chaine de caractères
    Par vetchang dans le forum Langage
    Réponses: 1
    Dernier message: 27/05/2008, 17h26
  2. Tri de chaine de caractère avec fgets
    Par clampin dans le forum Débuter
    Réponses: 4
    Dernier message: 16/05/2008, 22h42
  3. Comparaisons et tri des chaines de caractère.
    Par liliemmy dans le forum SQL
    Réponses: 2
    Dernier message: 01/04/2008, 17h52
  4. code de filtre et tri des chaines de caractères
    Par fatenatwork dans le forum Collection et Stream
    Réponses: 18
    Dernier message: 12/03/2008, 16h31
  5. Réponses: 17
    Dernier message: 16/12/2005, 09h45

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo