27 votes

Comparaison et tri de chaînes de caractères contenant des traits d'union

Question : Pourquoi le comparateur de chaînes par défaut ne parvient-il pas à maintenir la cohérence transitive ?

Je connais ce problème a déjà été mentionnée J'ai donc créé ce nouveau fil de discussion parce que j'ai de nouveau rencontré le problème lors de l'écriture d'un test unitaire.

La comparaison de chaînes par défaut (c'est-à-dire la comparaison sensible à la casse dépendante de la culture que nous obtenons avec string.CompareTo(string) , Comparer<string>.Default , StringComparer.CurrentCulture , string.Compare(string, string) et d'autres) viole la transitivité lorsque les chaînes contiennent des traits d'union (ou des signes moins, je parle des caractères U+002D).

Voici un exemple simple :

static void Main()
{
  const string a = "fk-";
  const string b = "-fk";
  const string c = "Fk";

  Console.WriteLine(a.CompareTo(b));  // "-1"
  Console.WriteLine(b.CompareTo(c));  // "-1"
  Console.WriteLine(a.CompareTo(c));  // "1"

  var listX = new List<string> { a, b, c, };
  var listY = new List<string> { c, a, b, };
  var listZ = new List<string> { b, c, a, };
  listX.Sort();
  listY.Sort();
  listZ.Sort();
  Console.WriteLine(listX.SequenceEqual(listY));  // "False"
  Console.WriteLine(listY.SequenceEqual(listZ));  // "False"
  Console.WriteLine(listX.SequenceEqual(listZ));  // "False"
}

Dans la partie supérieure, nous voyons comment la transitivité échoue. a est inférieur à b et b est inférieur à c et pourtant a n'est pas inférieur à c .

Cela va à l'encontre de la comportement documenté de la collation Unicode qui stipule que :

... pour toute chaîne de caractères A, B et C, si A < B et B < C, alors A < C.

Maintenant, le tri d'une liste à l'aide de a , b y c est exactement comme essayer de classer les mains de "Pierre", "Papier" et "Ciseaux" dans le jeu intransitif bien connu. Une tâche impossible.

La dernière partie de mon exemple de code ci-dessus montre que le résultat du tri dépend de l'ordre initial des éléments (et qu'il n'y a pas deux éléments de la liste qui se comparent de manière "égale" ( 0 )).

Linq's listX.OrderBy(x => x) est également concerné, bien entendu. Ce tri devrait être stable, mais on obtient des résultats étranges lorsqu'on ordonne une collection contenant a , b y c avec d'autres cordes.

J'ai essayé avec todos el CultureInfo sur ma machine (puisqu'il s'agit d'un tri dépendant de la culture), y compris la "culture invariante", et chacun d'entre eux présente le même problème. J'ai essayé avec le runtime .NET 4.5.1, mais je pense que les versions plus anciennes ont le même problème.

Conclusion : Lorsque l'on trie des chaînes de caractères en .NET avec le comparateur par défaut, les résultats sont imprévisibles si certaines chaînes contiennent des traits d'union.

Quels sont les changements introduits dans .NET 4.0 qui ont provoqué ce comportement ?

Il a déjà été observé que ce comportement n'est pas cohérent entre les différentes versions de la plateforme : dans .NET 3.5, les chaînes de caractères avec des traits d'union peuvent être triées de manière fiable. Dans toutes les versions du framework, l'appel à System.Globalization.CultureInfo.CurrentCulture.CompareInfo.GetSortKey fournit un service unique de DeyData pour ces chaînes, alors pourquoi ne sont-elles pas triées correctement ?

3voto

Kevin Cook Points 823

Discussion Microsoft Connect Voici un code qui permet de contourner le problème :

static int CompareStringUsingSortKey(string s1, string s2)
{
    SortKey sk1 = CultureInfo.InvariantCulture.CompareInfo.GetSortKey(s1);
    SortKey sk2 = CultureInfo.InvariantCulture.CompareInfo.GetSortKey(s2);
    return SortKey.Compare(sk1, sk2);
}

-2voto

Jani Hyytiäinen Points 1907

Techniquement, vous comparez fk, fk et, uhh, Fk entre eux. Lequel choisiriez-vous ?

Les jeux de caractères comprennent des caractères ignorables. La méthode Compare(String, String) ne tient pas compte de ces caractères lorsqu'elle effectue une comparaison sensible à la culture. Par exemple, si le code suivant est exécuté sous .NET Framework 4 ou une version ultérieure, une comparaison sensible à la culture de "animal" avec "ani-mal" (en utilisant un trait d'union doux, ou U+00AD) indique que les deux chaînes sont équivalentes.

using System;

public class Example
{
    static void Main()
    {
          string s1 = "ani\u00ADmal";
          string s2 = "animal";

              Console.WriteLine("Comparison of '{0}' and '{1}': {2}", 
                    s1, s2, String.Compare(s1, s2));
     }
}
// The example displays the following output: 
//       Comparison of 'ani-mal' and 'animal': 0

Pour reconnaître les caractères ignorables dans une comparaison de chaînes, appelez la méthode Compare(String, String, StringComparison) et fournissez une valeur de CompareOptions.Ordinal ou CompareOptions.OrdinalIgnoreCase pour le paramètre comparisonType.

En savoir plus : http://msdn.microsoft.com/en-us/library/84787k22(v=vs.110).aspx

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X