2

I have this LINQ Query:

TempRecordList = new ArrayList(TempRecordList.Cast<string>().OrderBy(s => s.Substring(9, 30)).ToArray());

It works great and performs sorting in a way that's accurate but a little different from what I want. Among the the result of the query I see something like this:

Palm-Bouter, Peter
Palmer-Johnson, Sean

Whereas what I really need is to have names sorted like this:

Palmer-Johnson, Sean
Palm-Bouter, Peter

Basically I want the '-' character to be treated as being lower than the character so that names that contain it show up later in an ascending search.

Here is another example. I get:

Dias, Reginald
DiBlackley, Anton

Instead of:

DiBlackley, Anton
Dias, Reginald

As you can see, again, the order is switched due to how the uppercase letter 'B' is treated.

So my question is, what do I need to change in my LINQ query to make it return results in the order I specified. Any feedback would be greatly appreaciated.

By the way, I tried using s.Substring(9, 30).ToLower() but that didn't help.

Thank you!

GonzoKnight
  • 809
  • 7
  • 12

4 Answers4

6

To customize the sorting order you will need to create a comparer class that implements IComparer<string> interface. The OrderBy() method takes comparer as second parameter.

internal sealed class NameComparer : IComparer<string> {
    private static readonly NameComparer DefaultInstance = new NameComparer();

    static NameComparer() { }
    private NameComparer() { }

    public static NameComparer Default {
        get { return DefaultInstance; }
    }

    public int Compare(string x, string y) {
        int length = Math.Min(x.Length, y.Length);
        for (int i = 0; i < length; ++i) {
            if (x[i] == y[i]) continue;
            if (x[i] == '-') return 1;
            if (y[i] == '-') return -1;
            return x[i].CompareTo(y[i]);
        }

        return x.Length - y.Length;
    }
}

This works at least with the following test cases:

var names = new[] {
    "Palmer-Johnson, Sean",
    "Palm-Bouter, Peter",
    "Dias, Reginald",
    "DiBlackley, Anton",
};

var sorted = names.OrderBy(name => name, NameComparer.Default).ToList();

// sorted:
// [0]: "DiBlackley, Anton"
// [1]: "Dias, Reginald"
// [2]: "Palmer-Johnson, Sean"
// [3]: "Palm-Bouter, Peter"
mgronber
  • 3,399
  • 16
  • 20
  • That sounds like it might work but I'm not sure I'll be able to write a class that would perform those kinds of comparisons myself. – GonzoKnight Mar 16 '11 at 17:04
  • @mgronber - Thank you very much! I will try this code out and will return with the results. – GonzoKnight Mar 16 '11 at 17:36
  • I seem to have a problem with the following line: [b]internal sealed class NameComparer : IComparer[b]. I get: Error 1 The non-generic type 'System.Collections.IComparer' cannot be used with type arguments. I may know how to fix it though. I'll try something else and will come back. – GonzoKnight Mar 16 '11 at 17:54
  • I have another problem with this line: new ArrayList(TempRecordList.OrderBy(s => s.Substring(9, 30, NameComparer.Default)).ToArray()); I get that the type of the IOrderedNumeral cannot be inferred from its usage, try specifying it explicitly. – GonzoKnight Mar 16 '11 at 18:00
  • Is there any specific reason why you are using `ArrayList` instead of `List<>`? – mgronber Mar 16 '11 at 18:06
  • Is there any way to rewrite the class above so it's not static? I think that might make it a little easiser to use. – GonzoKnight Mar 16 '11 at 18:07
  • @mgronber - There is and it's a long story. In any case, ArrayList vs generics is really not the problem here. – GonzoKnight Mar 16 '11 at 18:10
  • Correction: the fact that it's static doesn't seem to be a problem. I changed your class slightly and now the compiler is not complayining anymore: – GonzoKnight Mar 16 '11 at 18:14
  • internal sealed class NameComparer : IComparer { ... public int Compare(object x, object y) { int length = Math.Min(x.ToString().Length, y.ToString().Length); for (int i = 0; i < length; ++i) { if (x.ToString()[i] == y.ToString()[i]) continue; if (x.ToString()[i] == '-') return 1; if (y.ToString()[i] == '-') return -1; return x.ToString()[i].CompareTo(y.ToString()[i]); } return x.ToString().Length - y.ToString().Length; } } – GonzoKnight Mar 16 '11 at 18:15
  • The class is not static but it provides only singleton instance. It should not cause any problems. If your source collection is ´ArrayList`, the following should work: `var sortedArray = arrayList.Cast().Select(item => item.Substring(9, 30)).OrderBy(name => name, NameComparer.Default).ToArray();` – mgronber Mar 16 '11 at 18:15
  • The fix you made is not good. Please, try to use my original implementation with the code above. That `Cast()` is the most important part of it. – mgronber Mar 16 '11 at 18:19
  • Actually that portion is represented by '...' above. I just had to shorten what I posted for it to fit the length requirements. As you said, though I will try to use your code with minimum of changes. The only thing I had to alter is replace object with string insde because I had a problem with the following line: internal sealed class NameComparer : IComparer. I get: Error 1 The non-generic type 'System.Collections.IComparer' cannot be used with type arguments. So I took that part out. I'd gladly restore it if I can figure out how to do fix it. – GonzoKnight Mar 16 '11 at 18:24
  • Add a using directive for namespace `System.Collections.Generic` or change the `IComparer` to `System.Collections.Generic.IComparer`. – mgronber Mar 16 '11 at 18:30
  • The only problem I am having now is that this line: var sortedArray = TempRecordList.Cast().Select(item => item.Substring(9, 30)).OrderBy(name => name, NameComparer.Default).ToArray(); has OrderBy underlined in read with "The Type arguments for method IOrderedEnumarable etc cannot be inferred fro usage". – GonzoKnight Mar 16 '11 at 18:32
  • Nevermind the post above. I will try your suggestion. Thanks! – GonzoKnight Mar 16 '11 at 18:32
  • The program is running right now. I'll come back after it is done. Thanks! – GonzoKnight Mar 16 '11 at 19:01
  • @mgronber - Yes, the results look better now. Thank you very much! – GonzoKnight Mar 16 '11 at 19:14
2

As already mentioned, the OrderBy() method takes a comparer as a second parameter.

For strings, you don't necessarily have to implement an IComparer<string>. You might be fine with System.StringComparer.CurrentCulture (or one of the others in System.StringComparer).

In your exact case, however, there is no built-in comparer which will handle also the - after letter sort order.

Pierre Arnaud
  • 10,212
  • 11
  • 77
  • 108
0

OrderBy() returns results in ascending order.

e comes before h, thus the first result (remember you're comparing on a substring that starts with the character in the 9th position...not the beginning of the string) and i comes before y, thus the second. Case sensitivity has nothing to do with it.

If you want results in descending order, you should use OrderByDescending():

TempRecordList.Cast<string>
              .OrderByDescending(s => s.Substring(9, 30)).ToArray());
Justin Niessner
  • 242,243
  • 40
  • 408
  • 536
  • That but I really do want the result to be returned in ASCENDING order. My question may not have made it clear but all the results were sorted fine relative to each other. It's just the names above that are not as I want them. I guess, what I really want to do is alter the way the ascending sorting is performed. Is there a culture or something I can specify? Thanks! – GonzoKnight Mar 16 '11 at 17:02
  • @GonzoKnight - I guess I'm missing what you're trying to accomplish. You're soring based on a substring of the original (which is being done correctly) but you don't want to display them that way. If that's the case, then why sort them based on the substring to begin with? – Justin Niessner Mar 16 '11 at 17:04
  • don't let that part confuse you. The entire project is Cobol to C# conversion and apparantly the rules for ascending sort in Cobol are a little different. The preferred ordering that I've mentioned comes straight from Cobol output. My results are 99.5% correct but there's a 15 names in 2200 that are sorted wrong. The example I gave above pretty pretty much cover the range of incompatibitities of the two outputs. So I am displaying the results, I am just trying to get them to be compleately identical. – GonzoKnight Mar 16 '11 at 17:08
0

You might want to just implement a custom IComparer object that will give a custom priority to special, upper-case and lower-case characters.

http://msdn.microsoft.com/en-us/library/system.collections.icomparer.aspx

Brandon Moretz
  • 7,512
  • 3
  • 33
  • 43
  • I tried doing that, actually. Here is what I came up with: public class ArrayComparer : IComparer { public int Compare(object x, object y) { string left = x.ToString(); string right = y.ToString(); string lhs = left.Substring(9, 30); string rhs = right.Substring(9, 30); return lhs.CompareTo(rhs); } } But this only does regular comparison. I don't know how to change it so that it gives a custom priority to special, upper-case and lower-case characters. – GonzoKnight Mar 16 '11 at 17:13