0

I have a class like

public class KeyNamePair
{
 public string Key {get;set;}

 public string Name{get;set;}
}

My code

List<KeyNamePair> list =new List<KeyNamePair>();

    list.Add("1", "Day 1");
    list.Add("2", "Day 11");
    list.Add("4", "Day 5");
    list.Add("6", "Day 13");

How can i sort the list to display

>  "Day1", "Day5", "Day 11", "Day 13"

in order?

i tried

var SortedList = source.OrderBy(x => x.Name).ToList();
            return SortedList;

this does not do any sort

Rotem
  • 21,452
  • 6
  • 62
  • 109
  • 1
    Any errors? Can you please be more descriptive than "`this does not do any sort`". – Sam Oct 09 '13 at 11:42
  • 3
    Something like split by space, parse to int second element, order by this element: `x=>int.Parse(x.Name.Split(' ')[1])` something like this – Kamil Budziewski Oct 09 '13 at 11:43
  • 10
    You cannot get the expected output using regular sorting techniques, You would get something like "Day1", "Day 11", "Day 13", "Day5" – Vamsi Oct 09 '13 at 11:43
  • 1
    If you are expecting a numerical sort, you need to either provide a numerical type to sort on or provide a leading ZERO for the numerical value in the string. This will sort alphabetically but still preserve numeric order due to 0 being before 1. (e.g. Day 01, Day 05 etc) – Charleh Oct 09 '13 at 11:45
  • 2
    It'd probably be better to use a data structure like `int Id{get;set;} int Day{get;set;}` than a general `KeyNamePair` with `string`s. Then you can just `OrderBy(x => x.Day)` and get the expected list. – Tim S. Oct 09 '13 at 11:46
  • Can you also provide more context on your solution - it may be that you are doing something that can be addressed by other means – Charleh Oct 09 '13 at 11:47
  • You could also use `Dictionary` which will contain a collection of `KeyValuePair` – Charleh Oct 09 '13 at 11:48
  • I think you want a [natural sort](http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html) – Hans Kesting Oct 09 '13 at 12:01

7 Answers7

4

Try something like:

var SortedList = source.OrderBy(x=>int.Parse(x.Name.Split(' ')[1])).ToList();
return SortedList;

It won't work if data is in other format, but it should work with format you provided. You cannot sort by string itself because string "13" < "5"

Kamil Budziewski
  • 22,699
  • 14
  • 85
  • 105
2

Does it sort like this by any chance?

Day 1 Day 11 Day 13 Day 5

This would be what I would expect. There is an overload on OrderBy to allow you to specify how to sort. The sort that is happening is an alphanumeric sort so it is doing it correct. If you did...

Day 01 Day 11 Day 13 Day 05

You would see it sorts correctly.

McDonnellDean
  • 1,097
  • 2
  • 7
  • 23
  • shouldn't this be a comment ? – Vamsi Oct 09 '13 at 11:45
  • Why? I gave the correct answer? His sort is not wrong, its just not doing what he thinks, I gave the reason why. – McDonnellDean Oct 09 '13 at 11:46
  • 1
    sorry, but i feel the correct answer would be something that helps the op achieve his expected output and also explain what he is doing wrong, you got the second one right, anyway it's just my personal opinion – Vamsi Oct 09 '13 at 11:49
  • I'm not sure why providing code to copy paste would improve my answer? I provided the means to acquire the answer which I find more powerful, although like you, this is just my opinion. Copy pasting code teaches nothing. – McDonnellDean Oct 09 '13 at 11:53
  • 1
    i never said anything about providing code, please read my comment again – Vamsi Oct 09 '13 at 11:54
  • Well then I definitely fail to see your reasoning. I gave why its not sorting, where to sort correctly and how to fix his current code to sort as is. None of the other answers, at the time of this comment provide this, they just have some code with little context as to what the issue is. – McDonnellDean Oct 09 '13 at 11:56
  • 1
    I don't believe in changing my input to get the desired output as a correct answer, anyway as I said it's my personal opinion – Vamsi Oct 09 '13 at 12:02
  • 1
    You are free to provide your own answer if you feel mine does not fit, that is the beauty of Stack Overflow, simply hit the answer button and away you go. I can't help but feel that the amount of work you put in to attacking my answer could have been better served providing your own? – McDonnellDean Oct 09 '13 at 12:05
  • 1
    See that's the problem, I was never attacking you or your answer, if i had that intention I would have down voted your answer, which i didnt do, I have contributed to the post by adding a comment to the question already, all I'm trying to do is trying to help, anyway if you felt that I have some personal vendetta against you, I'm sorry, but I'm just trying to help – Vamsi Oct 09 '13 at 12:09
  • And I appreciate the discussion but ultimately we are at a difference in opinion. I genuinely feel it is better to provide the means to get your own answer than provide it directly. I think it's best we leave it at this though as extended discussions in comments are discouraged. – McDonnellDean Oct 09 '13 at 12:11
  • 1
    tea, coffee anyone?? :-0) – jim tollan Oct 09 '13 at 12:13
  • 2
    hey, i'm scottish - will offer suggestion only -lol – jim tollan Oct 09 '13 at 12:29
  • Since we are both Celts, how bout I buy you a Guinness instead? – McDonnellDean Oct 09 '13 at 13:41
  • 1
    works for me. in fact, a good 12-13 years ago, i was working on a job that required me to be in dublin for 2 days, one night stay. this just so happened to coincide with a really heavy snow storm that fell on the night of my departure, thus closing the airport - and so, i spent a very happy 17th march in o'connell st (and side streets). happy times -many guinesses :) – jim tollan Oct 09 '13 at 13:58
  • The best pubs are on the side streets :) – McDonnellDean Oct 09 '13 at 14:08
1

We can totally cheat here by PInvoking the Windows API function StrCmpLogicalW() which does a natural sort-order comparison between two strings.

Then we can just use the built-in List.Sort(). No need to involve Linq at all:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace Demo
{
    class Program
    {
        [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
        private static extern int StrCmpLogicalW(string lhs, string rhs);

        private void run()
        {
            var list = new List<KeyValuePair<string, string>>
            {
                new KeyValuePair<string, string>("1", "Day 1"), 
                new KeyValuePair<string, string>("2", "Day 11"), 
                new KeyValuePair<string, string>("4", "Day 5"),
                new KeyValuePair<string, string>("6", "Day 13")
            };

            list.Sort((lhs, rhs) => StrCmpLogicalW(lhs.Value, rhs.Value));

            foreach (var keyValuePair in list)
                Console.WriteLine(keyValuePair);
        }

        static void Main(string[] args)
        {
            new Program().run();
        }
    }
}

I'm assuming that you do indeed want to sort on "Value" and not on "Key" and that the problem you have is that the string numbers aren't sorting in numerical order.


EDIT: Applying Jim Tollan's idea, you could create an extension method to hide the PInvoke away like so:

public static class ListExt
{
    [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
    private static extern int StrCmpLogicalW(string lhs, string rhs);

    // Version for lists of any type.
    public static void SortNatural<T>(this List<T> self, Func<T, string> stringSelector)
    {
        self.Sort((lhs, rhs) => StrCmpLogicalW(stringSelector(lhs), stringSelector(rhs)));
    }

    // Simpler version for List<string>
    public static void SortNatural(this List<string> self)
    {
        self.Sort(StrCmpLogicalW);
    }
}

Then the line that does the sorting of the list would look like this:

list.SortNatural(element => element.Value);
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • +1 matthew. i gues you could further extend that by making it an extension method `.OrderNatural()`, off of List too ;-). that would be a nice conclusion – jim tollan Oct 09 '13 at 12:32
  • @jimtollan That's a good idea, but it would be more fiddly because the list isn't a list of strings - you'd have to provide a selector method to get at the strings to compare. [Edit... And done!] – Matthew Watson Oct 09 '13 at 12:36
  • nice one matthew -why can't *they* see that this is the best solution :) – jim tollan Oct 09 '13 at 12:51
0

This should work:

list = list.OrderBy(kn => {
    var digits = kn.Name.Split().Last();
    int num = int.MaxValue;
    if (digits.All(Char.IsDigit))
    { 
        num = int.Parse(new string(digits.ToArray()));
    }
    return num;
}).ToList();

Side-Note: you could also use the already available KeyValuePair<string, string>.

Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • 2
    @wudzik see [the documentation](http://msdn.microsoft.com/en-us/library/b873y76a.aspx): "If the *separator* parameter is **null** or contains no characters, white-space characters are assumed to be the delimiters. White-space characters are defined by the Unicode standard and return **true** if they are passed to the Char.IsWhiteSpace method." – Tim S. Oct 09 '13 at 11:58
0

I think that this is not a good approach but this work

list.OrderBy(x =>
     {
         return Int32.Parse(x.Name.Split(' ')[1]);
     });
MRB
  • 3,752
  • 4
  • 30
  • 44
0

you've got a few issues here:

  1. you are trying to sort on the Name property which counts 'Day 1' and 'Day 11' as being more closely matched than 'Day 1' and 'Day 2'
  2. You may be better sorting on the Key (also, I'd make the key numeric, rather than string if it will only ever contain numeric values).

Here's my revision:

public class KeyNamePair
{
    public int Key { get; set; }

    public string Name { get; set; }
}

List<KeyNamePair> list = new List<KeyNamePair>
    {
        new KeyNamePair() {Key = 1, Name = "Day 1"},
        new KeyNamePair() {Key = 4, Name = "Day 11"},
        new KeyNamePair() {Key = 2, Name = "Day 5"},
        new KeyNamePair() {Key = 6, Name = "Day 13"}
    };

var sortedList = list.Select(x => x).OrderBy(x => x.Key).ToList();

[edit] - I'm reflecting on the fact that I feel it would be better to refactor the logic of the class and the population of it, rather than try to co-erce some arbitary order that may fail on culture specific implementations, as strings are notoriously fragile in this respect.

jim tollan
  • 22,305
  • 4
  • 49
  • 63
  • Won't work, order will be `Day 1, Day 11, Day 5, Day 13`, but should be `Day 1, Day 5, Day 11, Day 13` – Kamil Budziewski Oct 09 '13 at 12:04
  • of course, absolutely right. maybe what I was meaning is that it would be better to refactor the class design, rather than frig with the string contents – jim tollan Oct 09 '13 at 12:06
0

I'm sure you will be happy with this:)

public class KeyNamePair : IComparable
{
  public string Key { get; set; }

  public string Name { get; set; }

  public KeyNamePair(string key, string name)
  {
    this.Key = key;
    this.Name = name;
  }

  public int CompareTo(object obj)
  {
    var tmp = (KeyNamePair)obj;
    var newKey = int.Parse(tmp.Name.Split(' ')[1]);

    var oldKey = int.Parse(Name.Split(' ')[1]);

    if (oldKey > newKey)
      return (1);
    if (oldKey < newKey)
      return (-1);
    else
      return (0);
  }
}

  List<KeyNamePair> list = new List<KeyNamePair>();

  list.Add(new KeyNamePair("1", "Day 1"));
  list.Add(new KeyNamePair("2", "Day 11"));
  list.Add(new KeyNamePair("4", "Day 5"));
  list.Add(new KeyNamePair("6", "Day 13"));
  list.Sort();

  foreach (var keyNamePair in list)
  {
    System.Console.Write(keyNamePair);
  }
Bassam Alugili
  • 16,345
  • 7
  • 52
  • 70