126 votes

Comment trier des chaînes par ordre alphabétique tout en tenant compte de la valeur lorsqu'une chaîne est numérique ?

J'essaie de trier un tableau de chiffres qui sont des chaînes de caractères et je voudrais qu'ils soient triés numériquement.

Le problème est que Je n'arrive pas à convertir les chiffres en int .

Voici le code :

string[] things= new string[] { "105", "101", "102", "103", "90" };

foreach (var thing in things.OrderBy(x => x))
{
    Console.WriteLine(thing);
}

Sortie :

101, 102, 103, 105, 90

J'aimerais bien :

90, 101, 102, 103, 105

EDIT : La sortie ne peut pas être 090, 101, 102...

Mise à jour de l'exemple de code pour dire " things " au lieu de " sizes ". Le tableau peut être quelque chose comme ceci :

string[] things= new string[] { "paul", "bob", "lauren", "007", "90" };

Cela signifie qu'il doit être trié par ordre alphabétique et par numéro :

007, 90, bob, lauren, paul

9 votes

Pourquoi ne pouvez-vous pas les convertir en int ?

2 votes

"tailles" peut être quelque chose d'autre comme "nom". L'exemple de code est juste simplifié.

4 votes

Certains des chiffres seront-ils négatifs ? Seront-ils tous des nombres entiers ? Quelle est l'étendue des nombres entiers ?

126voto

Jeff Paulsen Points 1124

Passez un comparateur personnalisé dans OrderBy. Enumerable.OrderBy vous permettra de spécifier le comparateur de votre choix.

C'est une façon de le faire :

void Main()
{
    string[] things = new string[] { "paul", "bob", "lauren", "007", "90", "101"};

    foreach (var thing in things.OrderBy(x => x, new SemiNumericComparer()))
    {    
        Console.WriteLine(thing);
    }
}

public class SemiNumericComparer: IComparer<string>
{
    /// <summary>
    /// Method to determine if a string is a number
    /// </summary>
    /// <param name="value">String to test</param>
    /// <returns>True if numeric</returns>
    public static bool IsNumeric(string value)
    {
        return int.TryParse(value, out _);
    }

    /// <inheritdoc />
    public int Compare(string s1, string s2)
    {
        const int S1GreaterThanS2 = 1;
        const int S2GreaterThanS1 = -1;

        var IsNumeric1 = IsNumeric(s1);
        var IsNumeric2 = IsNumeric(s2);

        if (IsNumeric1 && IsNumeric2)
        {
            var i1 = Convert.ToInt32(s1);
            var i2 = Convert.ToInt32(s2);

            if (i1 > i2)
            {
                return S1GreaterThanS2;
            }

            if (i1 < i2)
            {
                return S2GreaterThanS1;
            }

            return 0;
        }

        if (IsNumeric1)
        {
            return S2GreaterThanS1;
        }

        if (IsNumeric2)
        {
            return S1GreaterThanS2;
        }

        return string.Compare(s1, s2, true, CultureInfo.InvariantCulture);
    }
}

1 votes

Pour l'entrée donnée, cela produit le même résultat que la réponse de Recursive, qui implique PadLeft(). Je suppose que votre entrée est en fait plus complexe que ce que montre cet exemple, dans ce cas un comparateur personnalisé est la voie à suivre.

0 votes

Santé. Cette solution fonctionne et semble être une manière facile à lire et propre à mettre en œuvre. +1 pour m'avoir montré que vous pouvez utiliser IComparer sur OrderBy :)

20 votes

Le site IsNumeric est mauvais, un codage basé sur les exceptions est toujours mauvais. Utilisez int.TryParse à la place. Essayez votre code avec une grande liste et cela prendra une éternité.

119voto

recursive Points 34729

Il suffit d'ajouter des zéros de la même longueur :

int maxlen = sizes.Max(x => x.Length);
var result = sizes.OrderBy(x => x.PadLeft(maxlen, '0'));

1 votes

+1 pour la solution simple, le pinaillage serait (déjà fait dans l'édition, bien)

0 votes

Bonne idée, mais le problème suivant est que je dois afficher ces valeurs, donc le "90" doit être un "90" et non un "090".

13 votes

@sf : Essaie, tu pourrais aimer le résultat. N'oubliez pas que la clé de commande n'est pas la chose commandée. Si je dis de classer une liste de clients par nom de famille, j'obtiens une liste de clients, pas une liste de noms de famille. Si vous dites d'ordonner une liste de chaînes de caractères par une chaîne transformée, le résultat est la liste ordonnée des chaînes originales, pas des chaînes transformées.

77voto

shenhengbin Points 2630

Et, que pensez-vous de ça ...

string[] sizes = new string[] { "105", "101", "102", "103", "90" };

var size = from x in sizes
           orderby x.Length, x
           select x;

foreach (var p in size)
{
    Console.WriteLine(p);
}

0 votes

Hehe, j'aime vraiment celle-ci - très intelligente. Désolé si je n'ai pas fourni l'ensemble des données initiales.

3 votes

C'est comme l'option du tampon ci-dessus, mais c'est beaucoup mieux, selon moi.

4 votes

Var size = sizes.OrderBy(x => x.Length).ThenBy(x => x) ;

5voto

Ulf Kristiansen Points 301

Vous dites que vous ne pouvez pas convertir les nombres en int parce que le tableau peut contenir des éléments qui ne peuvent pas être convertis en int, mais il n'y a aucun mal à essayer :

string[] things = new string[] { "105", "101", "102", "103", "90", "paul", "bob", "lauren", "007", "90" };
Array.Sort(things, CompareThings);

foreach (var thing in things)
    Debug.WriteLine(thing);

Ensuite, comparez comme ceci :

private static int CompareThings(string x, string y)
{
    int intX, intY;
    if (int.TryParse(x, out intX) && int.TryParse(y, out intY))
        return intX.CompareTo(intY);

    return x.CompareTo(y);
}

Sortie : 007, 90, 90, 101, 102, 103, 105, bob, lauren, paul.

0 votes

En fait, j'ai utilisé Array.Sort pour des raisons de simplicité, mais vous pourriez utiliser la même logique dans un IComparer et utiliser OrderBy.

0 votes

Cette solution semble plus rapide que l'utilisation d'IComparer (mon avis). 15000 résultats et je pense que cela donne environ une seconde de différence.

3voto

Marino Šimić Points 4885

Cette demande semble bizarre et mérite une solution bizarre :

string[] sizes = new string[] { "105", "101", "102", "103", "90" };

foreach (var size in sizes.OrderBy(x => {
    double sum = 0;
    int position = 0;
    foreach (char c in x.ToCharArray().Reverse()) {
        sum += (c - 48) * (int)(Math.Pow(10,position));
        position++;
    }
    return sum;
}))

{
    Console.WriteLine(size);
}

0 votes

Je voulais dire 0x30 bien sûr. De plus, le tableau peut toujours contenir une chaîne non numérique, pour laquelle la solution produira des résultats intéressants.

0 votes

Et notez que le -48 ou non ne change absolument rien, nous pourrions directement utiliser la valeur entière du char, donc enlevez ce -48 si cela vous dérange...

0 votes

La valeur char est 0x30, si vous la convertissez en int, elle sera toujours 0x30, ce qui n'est pas le nombre 0.

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