tags:

views:

156

answers:

5

Hi, I need the ability to sort a collection of customers which contains a property of numeric string.

How Can I sort the below collection by code in numeric order. Again Code is a string.

           class Program
           {
              static void Main(string[] args)
              {
                 SortableObservableCollection<Customer> customerList = new SortableObservableCollection<Customer>();
                 customerList.Add(new Customer() {Name = "Jo", Code = "1"});
                 customerList.Add(new Customer() { Name = "Jo", Code = "10" });
                 customerList.Add(new Customer() { Name = "Jo", Code = "11" });
                 customerList.Add(new Customer() { Name = "Jo", Code = "9" });
                 customerList.Add(new Customer() { Name = "Jo", Code = "7" });
                 customerList.Add(new Customer() { Name = "Jo", Code = "12" });
                 customerList.Add(new Customer() { Name = "Jo", Code = "13" });
                 customerList.Add(new Customer() { Name = "Jo", Code = "2" });
                 customerList.Add(new Customer() { Name = "Jo", Code = "5" });
                 customerList.Add(new Customer() { Name = "Jo", Code = "7" });

                 //Order them by Code How
              }
           }

           public class Customer
           {
              public string Name { get; set; }
              public string Code { get; set; }
           }

Thanks for any suggestions

A: 

Try LINQ to Objects:

var result = from c in customerList
             orderby Int.Parse(c.Code)
             select c;
rursw1
That will sort "100" before "2".
Jon Skeet
That would work, but I believe that since its a String, `10` would sort above `1`
Nate Bross
Oversight. Thank you both, @Jon Skeet and @Nate Bross. I corrected my answer.
rursw1
+8  A: 

Option 1: implement IComparer<Customer> and parse the Code within that

Option 2: use LINQ to do the same thing:

customerList = customerList.OrderBy(c => int.Parse(c.Code)).ToList();

Option 3: change the Customer class so that a numeric value is stored as a numeric type :)

EDIT: As noted, this will throw an exception if you give it a customer with a non-numeric code.

Also, I'm calling ToList() because I assumed you still needed it to be a list. If you just need to iterate over the results in order, then use:

IEnumerable<Customer> ordered = customerList.OrderBy(c => int.Parse(c.Code));

Note that if you iterate over that twice, however, it will perform all the parsing and sorting twice too.

Jon Skeet
It is only work if code property is successfully parsed into numerical.
saurabh
Excellent all works with int.Parse. Will accept once site will let me.6 mins
@saurabh: Yes, if there's invalid data it will blow up. I wouldn't put data validation at *this* step though... I'd put it much earlier. Of course, if it's valid to have non-numeric strings for a Customer then it wouldn't work... but then I would hope the question would have been worded differently.
Jon Skeet
A: 

If you get the customer code always in string which is integers if parsed.

So you can convert to integer while storing in list.

And then use sorting on code property.

saurabh
+1  A: 

How confident are you that the Code is (a) numeric and (b) present?

var sortedList = customerList.OrderBy(c => int.Parse(c.Code));

If you have doubts, try another approach

Func<string, bool> isInteger = s => { int temp; return int.TryParse(s, out temp); };
var query = customerList.OrderBy(c => isInteger(c.Code) ? int.Parse(c.Code) : int.MaxValue);

Or to reduce the number of attemtps to parse the value and to keep the logic in one delegate:

Func<string, int> intParser = input =>
{
    int result;
    if (!int.TryParse(input, out result))
        return int.MaxValue; 

    return result;
};

var query = customerList.OrderBy(c => intParser(c.Code)); // .ToList() if you wish

Note: I'm using int.MaxValue to move non-integer inputs to the back of the line. You can choose another value depending upon where you might want those objects.

Anthony Pegram
+5  A: 

If the keys are always just numbers and those numbers are always convertible to ints then it is pretty straightforward. Just convert them to ints.

var sorted = from customer in customerList orderby Int32.ParseInt(customer.Code) select customer;

If any of them are not ints then this will crash.

Another way to do it is to pad the string out with leading zeros:

var sorted = from customer in customerList orderby PadToTenDigits(customer.Code) select customer;

where PadToTenDigits is a method left as an exercise, that turns "1" into "0000000001" and "1000" into "0000001000", and so on.

If you have to do true "numeric" sorting on complex codes, where, say, code "A300-B" sorts before "A1000-XYZ", then you have a harder problem on your hands. The trick there is to break up the code into "parts" and do a stable sort on each part. Basically, a radix sort.

Eric Lippert
I like the PadToTenDigits approach. It seems to perform a bit faster (about 3.5x on my build) than my twice-parsing integer method. It's also a simple enough delegate/function to write.
Anthony Pegram
Of course, the benefit to the way I did it is that (a) it only compares numbers, and not potentially '000000ABCD', and you get a little control over if non-numeric/empty input gets sorted to the front (int.MinValue) or back (int.MaxValue) of the line. Then again, PadToTen could potentially be coded to meet these same objectives.
Anthony Pegram
@Anthony: It's possible to rewrite your approach to use only one call to `int.TryParse` - it just means a somewhat uglier lambda expression (a full block lambda, which thus wouldn't work in a query expression). That also wouldn't generate as much garbage, I suspect. On the other hand, I do still *like* the pad to ten digits approach :)
Jon Skeet
(Of course, neither of these approaches is really as fast as it might be. You don't need to *actually* create a padded string to effectively "pretend" you've got one. Likewise if you've got two strings of different lengths and the longer one starts with a non-zero digit, you know you're done. This could be micro-optimized to heck and back... but I'd definitely wait until I'd shown this to be an issue before worrying about it.)
Jon Skeet
(Still rambling...) One interesting point about using OrderBy is that the projection to a key will only be performed once per item. Compare that with using `List<T>.Sort` with a custom `IComparer<T>`... unless you have some very elaborate scheme, you're going to end up parsing the code *on each comparison*. There's nowhere obvious to cache the result (assuming you can't modify Customer).
Jon Skeet
@Jon, yes, I could rewrite my OrderBy to use a `Func<string, int>`, execute a call to TryParse inside that, and return either that `out` result on true or potentially another value (Max, Min, etc.) on false. It's still slower than PadToTen (roughly implemented) but but closer. And, you're correct, it's a non-issue until shown otherwise.
Anthony Pegram
Interesting discussion guys. I note also that if the list were very long and mutable then it might be worthwhile to use a right-to-left in-place radix sort, since that is O(nk) in time (where k is the maximum length of the code string.) And it uses almost no extra space.
Eric Lippert