web-dev-qa-db-fra.com

Comment fonctionne le tri Array # lorsqu'un bloc est passé?

J'ai du mal à comprendre comment array.sort{ |x,y| block } fonctionne exactement, donc comment l'utiliser?

Un exemple de documentation Ruby :

   a = [ "d", "a", "e", "c", "b" ]
   a.sort                     #=> ["a", "b", "c", "d", "e"]
   a.sort { |x,y| y <=> x }   #=> ["e", "d", "c", "b", "a"]
74
Ibrahim Hussein

Dans votre exemple

a.sort

est équivalent à

a.sort { |x, y| x <=> y }

Comme vous le savez, pour trier un tableau, vous devez pouvoir comparer ses éléments (si vous en doutez, essayez simplement d'implémenter n'importe quel algorithme de tri sans utiliser de comparaison, pas de <, >, <= ou >=).

Le bloc que vous fournissez est vraiment une fonction qui sera appelée par l'algorithme sort pour comparer deux éléments. C'est-à-dire x et y seront toujours certains éléments du tableau d'entrée choisis par l'algorithme sort lors de son exécution.

L'algorithme sort supposera que cette fonction/bloc de comparaison répondra aux exigences de la méthode <=>:

  • renvoie -1 si x <y
  • retourne 0 si x = y
  • renvoie 1 si x> y

Le défaut de fournir une fonction/bloc de comparaison adéquate entraînera un tableau dont l'ordre n'est pas défini.

Vous devez maintenant comprendre pourquoi

a.sort { |x, y| x <=> y }

et

a.sort { |x, y| y <=> x }

renvoie le même tableau dans des ordres opposés.


Pour développer ce que Tate Johnson a ajouté, si vous implémentez la fonction de comparaison <=> sur n'importe laquelle de vos classes, vous gagnez ce qui suit

  1. Vous pouvez inclure le module Comparable dans votre classe qui définira automatiquement pour vous les méthodes suivantes: between?, ==, >=, <, <= et >.
  2. Les instances de votre classe peuvent maintenant être triées en utilisant l'invocation par défaut (c'est-à-dire sans argument) à sort.

Notez que le <=> la méthode est déjà fournie partout où elle a du sens dans la bibliothèque standard de Ruby (Bignum, Array, File::Stat, Fixnum, String, Time, etc ...).

121
bltxd

Lorsque vous avez un tableau, disons, d'entiers à trier, il est assez simple pour la méthode sort de classer correctement les éléments - des nombres plus petits en premier, plus gros à la fin. C'est alors que vous utilisez un sort ordinaire, sans bloc.

Mais lorsque vous triez d'autres objets, il peut être nécessaire de fournir un moyen de comparer (chacun) deux d'entre eux. Supposons que vous ayez un tableau d'objets de classe Person. Vous ne pouvez probablement pas dire si l'objet bob est supérieur à l'objet mike (c'est-à-dire que la classe Person n'a pas la méthode <=> mis en œuvre). Dans ce cas, vous devrez fournir du code pour expliquer dans quel ordre vous voulez que ces objets soient triés selon la méthode sort. C'est là que la forme de bloc entre en jeu.

people.sort{|p1,p2| p1.age <=> p2.age}
people.sort{|p1,p2| p1.children.count <=> p2.children.count}

etc. Dans tous ces cas, la méthode sort les trie de la même manière - le même algorithme est utilisé. Ce qui est différent, c'est la logique de comparaison.

21
Mladen Jablanović

La réponse de @OscarRyz m'a beaucoup éclairé sur la question du fonctionnement du tri, en particulier

 { |x, y| y <=> x }

Sur la base de ma compréhension, je fournis ici l'état de la matrice après chaque comparaison pour les résultats de bloc ci-dessus.

Remarque: Vous avez la référence d'impression des valeurs des paramètres de bloc e1, e2 de Ruby-forum

1.9.3dev :001 > a = %w(d e a w f k)
1.9.3dev :003 > a.sort { |e1, e2| p [e2, e1]; e2 <=> e1 }
["w", "d"]
["k", "w"]
["k", "d"]
["k", "e"]
["k", "f"]
["k", "a"]
["f", "a"]
["d", "f"]
["d", "a"]
["d", "e"]
["e", "f"]
 => ["w", "k", "f", "e", "d", "a"]

Un état de tableau deviné au moment de l'exécution après chaque comparaison:

 [e2, e1]    Comparsion Result       Array State
["w", "d"]      1                   ["w", "e", "a", "d", "f", "k"]
["k", "w"]     -1                   ["w", "e", "a", "d", "f", "k"]
["k", "d"]      1                   ["w", "e", "a", "k", "f", "d"]
["k", "e"]      1                   ["w", "k", "a", "e", "f", "d"]  
["k", "f"]      1                   ["w", "k", "a", "e", "f", "d"]    
["k", "a"]      1                   ["w", "k", "a", "e", "f", "d"]  
["f", "a"]      1                   ["w", "k", "f", "e", "a", "d"]  
["d", "f"]     -1                   ["w", "k", "f", "e", "a", "d"]  
["d", "a"]      1                   ["w", "k", "f", "e", "d", "a"]  
["d", "e"]     -1                   ["w", "k", "f", "e", "d", "a"]  
["e", "f"]     -1                   ["w", "k", "f", "e", "d", "a"] (Result)

Merci,

Jignesh

8
Jignesh Gohel

<=> Est une méthode Ruby qui renvoie (self.<=>( argument ))

  • -1 si self <argument
  • 0 si self == argument
  • 1 si self> argument

x et y sont des éléments du tableau. Si aucun bloc n'est fourni, la fonction sort utilise x<=>y, Sinon le résultat du bloc indique si x doit être avant y.

array.sort{|x, y| some_very_complicated_method(x, y) }

Ici, si some_very_complicated_method (x, y) renvoie smth qui est <0, x est considéré <que y et ainsi de suite ...

7
Draco Ater

Quelques points divers:

  • x et y sont appelés paramètres de bloc. La méthode de tri dit essentiellement "Je vais vous donner x et y, vous déterminez si x ou y doit venir en premier, et je m'occuperai des trucs ennuyeux en ce qui concerne le tri"
  • <=> est appelé opérateur de vaisseau spatial .
4
Andrew Grimm

Dans:

a.sort {|x,y| y <=> x }   #=> ["e", "d", "c", "b", "a"]

qu'est-ce que x et y?

x et y sont les éléments comparés par l'algorithme de tri.

Ceci est utile pour définir pour les classes personnalisées quel élément doit être avant l'autre.

Pour les données de base (nombres, chaînes, date, etc.), l'ordre naturel est prédéfini, mais pour l'élément client (c'est-à-dire l'employé), vous définissez qui précède qui dans une comparaison. Ce bloc vous donne la chance de définir cela.

et que se passe-t-il à y <=> x?

Là, ils comparent les éléments dans l'ordre décroissant (ceux avec une valeur "plus élevée" iront en premier) plutôt que dans l'ordre naturel (x<=>y)

Le <=> méthode signifie "compareTo" et retourne 0 si les éléments sont équivalents, ou < 0 si x va avant que y ou > 0 si x va après y

3
OscarRyz

Je crois que | x, y | y <=> x compare deux éléments à la fois dans l'ordre décroissant, comme indiqué dans: http://www.Ruby-doc.org/core-1.9.3/Array.html#method-i- 3C-3D-3E Dites avec ["d", "a", "e", "c", "b"], "d" et "a" semblent être comparés en premier. Puisqu'il descend, les deux restent dans le même ordre car d est évalué à moins que a. Ensuite, d et e sont évalués. "e" est déplacé vers la position "d". Sans connaître le fonctionnement interne du code c, il n'est pas possible de savoir où est déplacé d mais je pense que ce processus continue jusqu'à ce que tous les éléments soient triés. Les fonctions c:

           VALUE
rb_ary_cmp(VALUE ary1, VALUE ary2)
{
    long len;
    VALUE v;

    ary2 = rb_check_array_type(ary2);
    if (NIL_P(ary2)) return Qnil;
    if (ary1 == ary2) return INT2FIX(0);
    v = rb_exec_recursive_paired(recursive_cmp, ary1, ary2, ary2);
    if (v != Qundef) return v;
    len = RARRAY_LEN(ary1) - RARRAY_LEN(ary2);
    if (len == 0) return INT2FIX(0);
    if (len > 0) return INT2FIX(1);
    return INT2FIX(-1);
}
2
Michael F