Tri efficace des données de type Struct

Tous ceux qui sont venus à Elixir / Erlang à partir d'autres langues, très probablement, ont des attentes sur le fonctionnement des opérateurs de comparaison > , < , == , etc. On pourrait s'attendre à ce que 1 < 2 , (et c'est vraiment donc). En principe, nous pouvons dire que la comparaison fonctionne comme il se doit. Mais pas toujours.


Dans Elixir / Erlang, vous pouvez comparer n'importe quoi. Généralement. Alors que pour deux opérandes du même type, le résultat n'est pas décourageant, comme dans l'exemple ci-dessus, la comparaison de deux opérandes de types différents entraîne des conséquences plutôt inattendues. Parce que les types eux-mêmes sont «commandés pour comparaison». De cette façon:


 number < atom < reference < function < port < pid < tuple < map < list < bitstring 

Ce qui conduit soudainement au fait que la comparaison tout à fait légitime 42 < nil renvoie true .


De plus, les maps (et, par conséquent, les structs , qui sont en fait les mêmes maps sous le capot) sont comparées en fonction de leurs champs dans l'ordre alphabétique. En d'autres termes, les dates (qui sont implémentées dans Elixir par la structure Date ) seront comparées séquentiellement par les valeurs du day , du month , puis de l' year , et ce n'est probablement pas tout à fait ce que nous aimerions.


À partir de la v1.10.0 , Elixir fournit un tri pratique avec Enum.sort/2 standard si la structure exporte la fonction compare/2 :


 defmodule User do defstruct [:name] def compare(%User{name: n1}, %User{name: n2}) when n1 < n2, do: :lt def compare(%User{name: n1}, %User{name: n2}) when n1 > n2, do: :gt def compare(%User{}, %User{}), do: :eq end users = [ %User{name: "john"}, %User{name: "joe"}, %User{name: "jane"} ] Enum.sort(users, {:asc, User}) #⇒ [%User{name: "jane"}, # %User{name: "joe"}, # %User{name: "john"}] 

Tout module qui définit une structure et exporte la fonction compare/2 peut être passé en tant que deuxième paramètre à l'appel à Enum.sort/2 en l'état ou en tant que tuple {:asc | :desc, StructModule} {:asc | :desc, StructModule} . Enum.sort/2 maintenant suffisamment intelligent pour compare/2 module passé à Enum.sort/2 . Ce qui suit est un extrait du module Enum qui implémente cette fonctionnalité particulière:


 ... defp to_sort_fun(module) when is_atom(module), do: &(module.compare(&1, &2) != :gt) defp to_sort_fun({:asc, module}) when is_atom(module), do: &(module.compare(&1, &2) != :gt) defp to_sort_fun({:desc, module}) when is_atom(module), do: &(module.compare(&1, &2) != :lt) 

Cela vous permet de trier correctement les dates dans l'exemple ci-dessous (grâce à la Date.compare/2 existante), ainsi que dans toute structure définie par l'utilisateur (si elle exporte compare/2 , bien sûr), comme dans l'exemple avec la structure User ci-dessus.


 dates = [~D[2019-01-01], ~D[2020-03-02], ~D[2019-06-06]] Enum.sort(dates) # wrong #⇒ [~D[2019-01-01], ~D[2020-03-02], ~D[2019-06-06]] Enum.sort(dates, {:asc, Date}) # correct #⇒ [~D[2019-01-01], ~D[2019-06-06], ~D[2020-03-02]] 

Bon tri!

Source: https://habr.com/ru/post/fr482676/


All Articles