0

I have a list of strings that I want sorted. The entries can contain text, numbers or both. First I want them sorted alphabetically and then by the number.

Example of list:

Picture 1
Table 3
Picture 2
Table 1
Picture 4
Picture 3
Table 2
Titel

Notice that one of the entries doesn't contain a number. I want it to be sorted like:

Picture 1
Picture 2
Picture 3
Picture 4
Table 1
Table 2
Table 3
Titel

I have tried splitting the string and sorting them after, but since some of the entries can exist without a number, the split will fail. If I try to sort them as string they will show up like this, if there are more than 10.

Picture 1
Picture 10
Picture 11
...
Picture 2
Picture 21
Klaus Gütter
  • 11,151
  • 6
  • 31
  • 36
BubbaFett
  • 37
  • 1
  • 5
  • 3
    This is called "natural sorting" and is discussed e.g. here: https://stackoverflow.com/questions/248603/natural-sort-order-in-c-sharp – Klaus Gütter Feb 06 '23 at 14:47

3 Answers3

0

If the layout of the strings always stays as mentioned in the question, another alternative to natural sorting could be the following:

var list = new List<string>
{
    "Picture 1",
    "Table 3",
    "Picture 2",
    "Table 1",
    "Picture 4",
    "Picture 3",
    "Table 2",
    "Titel",
    "Picture 21"
};

var sortedList = list
    .Select(x => new Item(x))
    .OrderBy(x => x.Text)
    .ThenBy(x => x.Number)
    .Select(x => x.Value)
    .ToList();


foreach (var entry in sortedList)
{
    Console.WriteLine(entry);
}


public class Item
{
    public string Text { get;  }
    public int? Number { get;  }
    public string Value { get;  }

    public Item(string value)
    {
        Value = value;

        var splittedString = value.Split(' ');
        Text = splittedString[0];

        if (splittedString.Length <= 1)
        {
            return;
        }

        if (int.TryParse(splittedString[1], out var number))
        {
            Number = number;
        }

    }
}
Tommehh
  • 872
  • 1
  • 17
  • 44
0

You can use a custom comparer to do this:

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        List<string> list = new List<string>()
        {"Picture 1", "Table 3", "Picture 2", "Table 1", "Picture 4", "Picture 3", "Table 2", "Titel"};
        list = list.OrderBy(x => x, new AlphanumericComparer()).ToList();
        foreach (var item in list)
        {
            Console.WriteLine(item);
        }
    }

    class AlphanumericComparer : IComparer<string>
    {
        public int Compare(string x, string y)
        {
            string[] xParts = x.Split(' ');
            string[] yParts = y.Split(' ');
            int xNumber = 0, yNumber = 0;
            if (xParts.Count() == 2)
            {
                int.TryParse(xParts[1], out xNumber);
            }

            if (yParts.Count() == 2)
            {
                int.TryParse(yParts[1], out yNumber);
            }

            int result = string.Compare(xParts[0], yParts[0], StringComparison.Ordinal);
            if (result == 0)
            {
                result = xNumber.CompareTo(yNumber);
            }

            return result;
        }
    }
}

It will get two elements split them and then try to convert the second part into integer (if it existsts), and then also compares first parts (strings)

Gor Grigoryan
  • 297
  • 1
  • 7
0

I ended up inserting a 0 before the 1 digit strings, like so,

"Picture 01"
"Picture 02"

Then sorting the list by alphabetical order and removing the inserted 0's again.

BubbaFett
  • 37
  • 1
  • 5
  • Not sure this is an 'answer', might be just as well served as a comment on your original question. – wruckie Feb 09 '23 at 20:17