-2

I have the following list:

1 2 3 10 20 30 Dog Cat 30Dog 30Cat

and I wish to sort it such that I get the following list:

1 2 3 10 20 30 30Cat 30Dog Cat Dog

How can I do this in C#? I basically want to sort the list according to the following rules:

Integers are sorted in ascending order. Any value beginning with an integer is sorted based on that integer value. Any value which doesn't begin with an integer is sorted by its string value.

RouteMapper
  • 2,484
  • 1
  • 26
  • 45

2 Answers2

2

You can pre-process the string into parts, then use Linq:

var list = new List<string>{"1", "2", "3", "10", "20", "30", "Dog", "Cat", "30Dog", "30Cat"};

var regEx = new Regex(@"^\d+");

var sorted = list
    .Select(x => new { text = x, intPart = regEx.Match(x).Value })
    .Select(x => new { text = x.text, intPart = string.IsNullOrEmpty(x.intPart) ? int.MaxValue : int.Parse(x.intPart) })
    .OrderBy(x => x.intPart)
    .ThenBy(x => x.text)
    .Select(x => x.text);

sorted.Dump();
Dave Bish
  • 19,263
  • 7
  • 46
  • 63
1

Sounds to me like you would want to implement a custom comparer, and then pass that custom comparer to a sort method.

Dave Bish's example is great; but it you don't want to use a regex, here's a procedure version I threw together.

    static void Main(string[] args)
    {
        IEnumerable<string> strings = new[] { "1", "2", "3", "10", "20", "30", "Dog", "Cat", "30Dog", "30Cat" };
        strings = strings.OrderBy(s => s, new CustomComparer());
        var joined = string.Join(" ", strings);
        Console.WriteLine(joined);
        Console.ReadLine();
    }

    public class CustomComparer : IComparer<string>
    {
        public int Compare(string s1, string s2)
        {
            int x, y;
            bool xInt, yInt;
            xInt = int.TryParse(s1, out x);
            yInt = int.TryParse(s2, out y);
            if (xInt && yInt)
                return x.CompareTo(y);
            if (xInt && !yInt)
            {
                if (this.SplitInt(s2, out y, out s2))
                {
                    return x.CompareTo(y);
                }
                else
                {
                    return -1;
                }
            }
            if (!xInt && yInt)
            {
                if (this.SplitInt(s1, out x, out s1))
                {
                    return y.CompareTo(x);
                }
                else
                {
                    return 1;
                }
            }

            return s1.CompareTo(s2);
        }

        private bool SplitInt(string sin, out int x, out string sout)
        {
            x = 0;
            sout = null;
            int i = -1;                
            bool isNumeric = false;
            var numbers = Enumerable.Range(0, 10).Select(it => it.ToString());
            var ie = sin.GetEnumerator();
            while (ie.MoveNext() && numbers.Contains(ie.Current.ToString()))
            {
                isNumeric |= true;
                ++i;
            }

            if (isNumeric)
            {
                sout = sin.Substring(i + 1);
                sin = sin.Substring(0, i + 1);
                int.TryParse(sin, out x);
            }

            return false;
        }
    }

The output looks like...

1 2 3 10 20 30 30Cat 30Dog Cat Dog
John Kraft
  • 6,811
  • 4
  • 37
  • 53
  • 2
    That's obvious; the question is how to write a custom comparer with that functionality. – Servy Jun 05 '13 at 14:23
  • Sorry. I misunderstood the question, since you had the algorithm written in English in the third paragraph. – John Kraft Jun 05 '13 at 15:07