skip to Main Content

How can I sort a List<object> where the object is always List<string>.
I know that there is already questions similar to this one.. I tried Sort() or other functions that was suggested but I couldn’t solve.

This is what I have:

var result= new List<object>();

foreach (var data in myData)
{
  result.Add(new List<string>() { data.Text, myData.Count().ToString() });                     
}
//This is how at the end the List<object> looks like:
[
    [
        "xxx",
        "2"
    ],
    [
        "xxxx",
        "2"
    ],
    [
        "xxx",
        "2"
    ],
    [
        "xxxx",
        "3"
    ],
    [
        "xxx",
        "48"
    ],
    [
        "xxxx"
        "18"
    ],
    [
        "xxxx",
        "58"
    ],
    [
        "xxxx",
        "1"
    ],
    [
        "xxxx",
        "371"
    ],
    [
        "xxxx",
        "3"
    ],
    [
        "xxxxx",
        "2"
    ]
]

I need to order the List<object> by the second element of the list whis is always numer

4

Answers


  1. You can sort the list like this:

    data = data.OrderBy(i => int.Parse(i[1])).ToList();
    

    But we can do even better if we skip the intermediate step in the question, and start with myData:

    var data = myData.Select(md => (md.Text, md.Count())).OrderBy(md => md.Item2);
    

    This looks a little different than what you asked for, but it’s objectively better for two reasons:

    1. It lets us keep the int value in a more-precise type. Thanks to internationalization/cultural issues, converting back and forth between numbers or dates and strings is far slower and more error prone than we’d like to believe. It’s therefore something to avoid. It’s usually far better to keep the specialized numeric data for as long as possible, and only convert to strings at the last possible moment. In this case, we save the conversion operation twice (once in each direction), and so this code should already be MUCH faster than the original.

    2. We end up with an IEnumerable<(string, int)>, instead of a List<List<string>>. IEnumerable<T> is almost always greatly more efficient than List<T>, because of the reduced memory use and the ability to stream data as it comes, instead of needing to load the entire set up front.

    However, I understand you may need the data a specific way in order to pass it on to the next step. If you really do need the janky List<List<string>> you can do this:

    var data = myData.OrderBy(md => md.Count()).
         Select(md => new List<string>{md.Name, md.Count().ToString()}).
         ToList();
    

    Note that because of the points above, even when we really want the list the most efficient way to get there was still to preserve the original int for ordering and craft the IEnumerable before creating the final list, and depending on how long it takes to run Count() it might even be more efficient to use my main suggestion of IEnumerable<(string, int)> as the intermediate step:

    var data = myData.Select(md => (md.Text, md.Count())).
        OrderBy(md => md.Item2).
        Select(md => new List<string> {md.Item1, md.Item2.ToString()}).
        ToList();
    

    Hopefully this also helps you think differently about this kind of data processing. If an IEnumerable with typed data is so much more efficient that we use it as an intermediate step anyway, then its probably also the better way to handle passing the data between methods or classes in the first place. At minimum, you can at least start thinking about removing the .ToList() from the end of these lines, since you can always append it to a variable when passing it to a function that still really needs a list.

    Login or Signup to reply.
  2. I’d recommend to use strongly typed structure instead, a very quick one to use here can be a Tuple, which is now well integrated into C# – can name parameters. Or even better create a class applicable to your use.

    Whatever structure you use, if you actually want to sort your list in place with a custom logic to compare the objects you can provide your sorting expression as an argument to Sort method. So for example if all you care for is the second number:

    var result = new List<(string Text, int Count)>()
          
    foreach (var data in myData)
    {
        result.Add(new ( data.Text, myData.Count()));                     
    }
    
    result.Sort((a,b) => a.Count - b.Count);
    //here your list is already sorted
    

    Although to be fair, normally more desired is to not touch the original instance and create a copy instead. Which you can achiever with LINQ’s OrderBy:

    var sortedList = result.OrderBy((element) => element.Count)
    

    This is cleaner, as unless you need to work on more complex types or perform computation it is enough to only indicate which property of your class is the key to be use for sorting. The objects will be then returned in order indicated by the values of the key, ignoring the rest of the class. You can also pass more complex expression as a second parameter, similarly to the Sort method.

    Login or Signup to reply.
  3. You can use the handy C# ValueTuples to create strongly typed data in a simple way.

    var textCount = new List<(string Text, int Count)>();
    
    foreach (var data in myData)
    {
      textCount.Add( (data.Text, data.Count()) );
    }
    // In-place sort of list
    textCount.Sort((a, b) => a.Count.CompareTo(b.Count));
    

    If you wanted to sort in descending order just negate the comparison

    textCount.Sort((a, b) => -a.Count.CompareTo(b.Count));
    

    or compare b to a:

    textCount.Sort((a, b) => b.Count.CompareTo(a.Count));
    

    Or with LINQ

    var textCount = myData
        .Select(d => (d.Text, Count: d.Count()))
        .OrderBy(t => t.Count) // Use OrderByDescending for reverse sort
        .ToList();
    
    // Test
    foreach (var item in textCount) {
        Console.WriteLine($"Text = {item.Text}, Count = {item.Count}");
    }
    

    Both solutions store the count as an int. This is important for the sorting. Numbers stored as string will not sort correctly. You would get an alphabetical order like "1", "11", "157", "2", "27", "3", "32".

    The second solution has the advantage to create tuples, sort, create and fill the list in one statement.

    Login or Signup to reply.
  4. I see no reason for you to work with the object in the beginning you can do a feature like

            var result = new List<(string, string)>();
    
    
            result.OrderBy(x => x.Item2);
    

    this would make it easy for you to order and add to list like this

    foreach (var data in myData)
    {
      result.Add((data.Text, myData.Count().ToString()));                     
    }
    

    but see that the best way would be for you to sort it through a numerical index so instead of transforming it into a string work with it numerically even in this way:

    var result = new List<(string, int)>();
    
    result.OrderBy(x => x.Item2);
    
    foreach (var data in myData)
    {
      result.Add((data.Text, myData.Count().ToString()));                     
    }
    

    I don’t recommend using only the object as a list due to performance loss and if I had to think about it I would have to create an abstract class just to compare their lists and sort manually

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search