0

I am using this linq query to sort a string column but the results I am getting does not seems to be in right order?

Query:

userList = users.OrderBy(u => u.FirstName)
                .Skip(offset)
                .Take(rowcount)
                .ToList<User>();

Result:

rama &
11Rama
15rama
1Rama
2Rama
490110rama
IU-Rama
Rama

it should be something like?

1Rama
2Rama
11Rama
15rama
490110rama
IU-Rama
rama &
Rama

17
  • What is your expected result? Commented Apr 16, 2014 at 15:48
  • is this LINQ-to-{some database backend}? if so: what is the database collation? Commented Apr 16, 2014 at 15:48
  • Is what you are displaying the FirstName field? Commented Apr 16, 2014 at 15:49
  • @MarcGravell we are using Entity Framework. Commented Apr 16, 2014 at 15:50
  • 1
    Can you describe the sorting rules you want? Right now your example looks inconsistent. Commented Apr 16, 2014 at 16:36

3 Answers 3

1

You are getting your result sorted by the code points of the characters and that is by far the most common implementation for sorting strings. What you are expecting is called natural sort order, see for example this article, but you will have to do it on your own because .NET does not provide this out of the box.

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

4 Comments

The expected results listed is not a natural order sort. Notice how in his expected results 15 comes before 2.
You are right. On a closer look it is actually inconsistent - 1 before R for the first letter (1Rama < Rama) but R before 1 for the second letter (1Rama < 11Rama).
Yes. I was confused by that as well.
I am accepting this answer since the sort order require in this case is natural sort order. Thank you @DanielBrückner.
0

By adding this code you will be able to sort as you need it to be done. But this sorting will be done on client side and all records must be fetch as list.

userList = users.ToList<User>()
            .OrderBy(u => u.FirstName,new NumericComparer())
            .Skip(offset)
            .Take(rowcount)
            .ToList<User>();

public class NumericComparer : IComparer<string>
{
    public int Compare(string x, string y)
    {
        return StringLogicalComparer.Compare(x, y);
    }
}

public class StringLogicalComparer
{
    public static int Compare(string s1, string s2)
    {
        //get rid of special cases
        if ((s1 == null) && (s2 == null)) return 0;
        if (s1 == null) return -1;
        if (s2 == null) return 1;

        if ((s1.Equals(string.Empty) && (s2.Equals(string.Empty)))) return 0;
        if (s1.Equals(string.Empty)) return -1;
        if (s2.Equals(string.Empty)) return -1;

        //WE style, special case
        bool sp1 = Char.IsLetterOrDigit(s1, 0);
        bool sp2 = Char.IsLetterOrDigit(s2, 0);
        if (sp1 && !sp2) return 1;
        if (!sp1 && sp2) return -1;

        int i1 = 0, i2 = 0; //current index
        while (true)
        {
            bool c1 = Char.IsDigit(s1, i1);
            bool c2 = Char.IsDigit(s2, i2);
            int r; // temp result
            if (!c1 && !c2)
            {
                bool letter1 = Char.IsLetter(s1, i1);
                bool letter2 = Char.IsLetter(s2, i2);
                if ((letter1 && letter2) || (!letter1 && !letter2))
                {
                    r = letter1 ? Char.ToLower(s1[i1]).CompareTo(Char.ToLower(s2[i2])) : s1[i1].CompareTo(s2[i2]);
                    if (r != 0) return r;
                }
                else if (!letter1) return -1;
                else return 1;
            }
            else if (c1 && c2)
            {
                r = CompareNum(s1, ref i1, s2, ref i2);
                if (r != 0) return r;
            }
            else if (c1)
            {
                return -1;
            }
            else
            {
                return 1;
            }
            i1++;
            i2++;
            if ((i1 >= s1.Length) && (i2 >= s2.Length))
            {
                return 0;
            }
            if (i1 >= s1.Length)
            {
                return -1;
            }
            if (i2 >= s2.Length)
            {
                return -1;
            }
        }
    }

    private static int CompareNum(string s1, ref int i1, string s2, ref int i2)
    {
        int nzStart1 = i1, nzStart2 = i2; // nz = non zero
        int end1 = i1, end2 = i2;

        ScanNumEnd(s1, i1, ref end1, ref nzStart1);
        ScanNumEnd(s2, i2, ref end2, ref nzStart2);
        int start1 = i1; i1 = end1 - 1;
        int start2 = i2; i2 = end2 - 1;

        int nzLength1 = end1 - nzStart1;
        int nzLength2 = end2 - nzStart2;

        if (nzLength1 < nzLength2) return -1;
        if (nzLength1 > nzLength2) return 1;

        for (int j1 = nzStart1, j2 = nzStart2; j1 <= i1; j1++, j2++)
        {
            int r = s1[j1].CompareTo(s2[j2]);
            if (r != 0) return r;
        }
        // the nz parts are equal
        int length1 = end1 - start1;
        int length2 = end2 - start2;
        if (length1 == length2) return 0;
        if (length1 > length2) return -1;
        return 1;
    }

    private static void ScanNumEnd(string s, int start, ref int end, ref int nzStart)
    {
        nzStart = start;
        end = start;
        bool countZeros = true;
        while (Char.IsDigit(s, end))
        {
            if (countZeros && s[end].Equals('0'))
            {
                nzStart++;
            }
            else countZeros = false;
            end++;
            if (end >= s.Length) break;
        }
    }
}

3 Comments

Yikes! Please please please try and use better variable names!
I got this code many years ago from net. I works and that matters.
I agree with you. But sometimes some codes must be treated as 'Black Box'. It does what we need so why to focus on that code?
0

Creating a custom IComparer would work for you.

Check this implementation: http://zootfroot.blogspot.com/2009/09/natural-sort-compare-with-linq-orderby.html

You code would then call myLinqQuery.OrderBy(item => item.sortProperty, new MyComparer<string>())

In his example, the sort order return:

image1.jpg
image4.jpg
image30.jpg
image200.jpg

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.