4

I want to sort an list of string that sometimes contains numeric values

My list is like this:

  • Bedroom 1
  • Bedroom 2
  • Bedroom 10
  • Bathroom 1
  • Bathroom 2
  • Bathroom 10
  • 1 Light
  • 1 Paper

If I just use an orderby: roomList.OrderBy(x => x.Name) I get this list:

  • 1 Light
  • 1 Paper
  • Bathroom 1
  • Bathroom 10
  • Bathroom 2
  • Bedroom 1
  • Bedroom 10
  • Bedroom 2

Is it possible to get the list like this instead ?

  • 1 Light
  • 1 Paper
  • Bathroom 1
  • Bathroom 2
  • Bathroom 10
  • Bedroom 1
  • Bedroom 2
  • Bedroom 10

All list elements don't contain a number, and the list is about 1500 rows long

I have tryed to use this code, and it works fine on elements containing number, but not on just string elements:

public class SemiNumericComparer : IComparer<string>
    {
        public int Compare(string s1, string s2)
        {
            if (IsNumeric(s1) && IsNumeric(s2))
            {
                if (Convert.ToInt32(s1) > Convert.ToInt32(s2)) return 1;
                if (Convert.ToInt32(s1) < Convert.ToInt32(s2)) return -1;
                if (Convert.ToInt32(s1) == Convert.ToInt32(s2)) return 0;
            }

            if (IsNumeric(s1) && !IsNumeric(s2))
                return -1;

            if (!IsNumeric(s1) && IsNumeric(s2))
                return 1;

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

        public static bool IsNumeric(object value)
        {
            try
            {
                int i = Convert.ToInt32(value.ToString());
                return true;
            }
            catch (FormatException)
            {
                return false;
            }
        }
    }
7
  • 1
    what do you want to happen with "2 Light" and "10 Light"? What about "Bathroom Blue"? Commented Mar 29, 2019 at 13:00
  • 4
    Related stackoverflow.com/questions/5093842/… Commented Mar 29, 2019 at 13:04
  • 1
    Not sure if this is an option for you, but you could edit the strings to have leading zeros. Bathroom 001, Bathroom 002, etc. Alternatively, convert the numeric parts to that format at runtime, and then sort. Commented Mar 29, 2019 at 13:04
  • 3
    stackoverflow.com/questions/248603/… Commented Mar 29, 2019 at 13:05
  • Both should work with some difference on "edge case" scenario. Try one on your datas if you have an issue be specific on the case. But I'm pretty sure that if it's not an completly arbritary rules, it's either a dupe or a buildin function exist. Human have been asking for ordered list of things since we start to have computer. I expect dev to a find an easy solution, the day after. Commented Mar 29, 2019 at 13:18

3 Answers 3

3

Try following custom IComparable :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace ConsoleApplication107
{
    class Program
    {
        static void Main(string[] args)
        {
            List<string> input = new List<string>() { "1 Light", "1 Paper", "Bathroom 1", "Bathroom 2", "Bathroom 10", "Bedroom 1", "Bedroom 2", "Bedroom 10" };

            List<string> results = input.Select(x => new { s = x, order = new Order(x) }).OrderBy(x => x.order).Select(x => x.s).ToList();

        }
    }
    public class Order : IComparable<Order>
    {
        List<string> split { get; set; }

        public Order(string line)
        {
            split = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
        }

        public int CompareTo(Order other)
        {

            int min = Math.Min(split.Count, other.split.Count);
            int thisNumber = 0;
            int otherNumber = 0;
            for (int i = 0; i < min; i++)
            {
                if (split[i] != other.split[i])
                {
                    if ((int.TryParse(split[i], out thisNumber)))
                    {
                        if ((int.TryParse(other.split[i], out otherNumber)))
                        {
                            return thisNumber.CompareTo(otherNumber); //both numbers
                        }
                        else
                        {
                            return -1; // this is number other is string : this comes first
                        }
                    }
                    else
                    {
                        if ((int.TryParse(other.split[i], out otherNumber)))
                        {
                            return 1; //other is number this is string : other comes first
                        }
                        else
                        {
                            return split[i].CompareTo(other.split[i]);
                        }
                    }
                }
            }

            return split.Count.CompareTo(other.split.Count);

        }

    }
}
Sign up to request clarification or add additional context in comments.

Comments

2
        [SuppressUnmanagedCodeSecurity]
        internal static class SafeNativeMethods
        {
            [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
            public static extern int StrCmpLogicalW(string p1, string p2);
        }

        public sealed class StringComparer : IComparer<string>
        {
            public int Compare(string a, string b)
            {
                return SafeNativeMethods.StrCmpLogicalW(a, b);
            }
        }

and now you can use StringComparer on OrderBy :

            List<string> str = new List<string>
            {
                "Bedroom 1",
                "Bedroom 2",
                "Bedroom 10",
                "Bathroom 1",
                "Bathroom 2",
                "Bathroom 10",
                "1 Light",
                "1 Paper"
            };
            str = str.OrderBy(x => x, new StringComparer()).ToList();

2 Comments

This will only work in a Windows environment. Also, I wouldn't recommend referencing kernel dll's if it can be (easily) avoided.
Additionally to what @discy mentioned, it is noteworthy to mention that StrCmpLogicalW can behave differently on different Windows releases (which might or might not be desirable, depending on whether the sorting should match the sorting behavior of the Windows version on which the program is running or not).
1

You're looking for a "natural sorting" algorithm

For example: https://www.codeproject.com/Articles/22517/Natural-Sort-Comparer

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.