user3755946
user3755946

Reputation: 804

Sort List<List<string>> by a certain 'column'

Example input and current output with out sorting:

List<List<string>> _optoGrid = new List<List<string>>();

_optoGrid.Add(new List<string>() {"10",     "10",     "100",    "20",    "10",     "10"});
_optoGrid.Add(new List<string>() {"20",     "20",     "50",     "10",    "1546",   "555"});
_optoGrid.Add(new List<string>() {"30",     "30",     "10",     "10",    "10",     "100"});
_optoGrid.Add(new List<string>() {"30",     "30",     "10",     "1",     "10",     "100"});

display this is where the sort code would go.

foreach (List<string> list in _optoGrid)
{
    foreach (string s in list)
    {
        Console.Write(s + ", ");
    }

    Console.WriteLine();
}

Current Output:

10, 10, 100, 20, 10, 10,

20, 20, 50, 10, 1546, 555,

30, 30, 10, 10, 10, 100,

30, 30, 10, 1, 10, 100,

Desired Output assuming sort by column 5 (starting at 0):

20, 20, 50, 10, 1546, 555,

30, 30, 10, 10, 10, 100,

30, 30, 10, 1, 10, 100,

10, 10, 100, 20, 10, 10,

in my head I would like to type:

_optogrid.Sort(5) //5 being the 'column' number

Upvotes: 0

Views: 971

Answers (2)

Harald Coppoolse
Harald Coppoolse

Reputation: 30464

This looks like sorting a table by a certain column number.

Enumerable.OrderBy will do the job.

Enumerable.OrderBy, that takes a sequence of TRow items, and a keySelector. A keySelector is a function that takes a TRow as input and returns a value on which the sequence must be ordered.

In your case, a TRow is a List<string>. So if you need to order by the column with index sortColumnIndex you need a KeySelector that takes a List<String> row and returns row[sortColumnIndex]

How about this:

int sortColumnIndex = 5; // order by column 5 List> unorderedList = ... List orderedList = unorderedList.OrderBy(row => row[sortColumnIndex]);

Simple comme bonjour!

Upvotes: 0

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131364

This code has no columns, it's a list that contains lists. LINQ can still order the results. The methods that order results in LINQ are OrderBy() and OrderByDescending(). Its argument is a lambda that produces the ordering key.

In this case, the lambda should return the list item that should be used for ordering, eg :

var  _optoGrid = new List<List<string>>{
    new List<string> {"10",     "10",     "100",     "20",    "10",     "10"},
    new List<string> {"20",     "20",     "50",     "10",     "1546",   "555"},
    new List<string> {"30",     "30",     "10",     "10",     "10",     "100"},
    new List<string> {"30",     "30",     "10",     "1",     "10",     "100"}
};
var orderedResults=_optoGrid.OrderBy(lst=>lst[5]);

Or

var orderedResults=_optoGrid.OrderByDescending(lst=>lst[5]);

That's very fragile code though. Nothing guarantees that all lists have the same number of items. It's very easy to forget one value or enter one more. .OrderBy(lst=>lst[5]) will throw if one list contains fewer than 6 items but won't complain if the wrong number of items are used.

It would be better if a proper class was used instead of lists, especially if all items are expected to have the same number of items, eg :

class MyItem
{
    public string ProperlyNamedProperty1{get;set;}
    ...
    public MyItem(string prop1,string prop2,...)
    {
        ProperlyNamedProperty1=prop1;
        ...
    }
}

var  _optoGrid = new List<MyItem>{
    new MyItem("10",     "10",     "100",     "20",    "10",     "10"),
};
var orderedResults=_optoGrid.OrderBy(item=>item.Property6);

Value tuples can also be used if the list is only going to be used locally, eg in a single method. Of course, 6 properties are a bit too much :

var  _optoGrid = new List<(string prop1, string prop2, string prop3, string prop4, string prop5, string prop6)>{
    ("10",     "10",     "100",     "20",    "10",     "10"),
    ("20",     "20",     "50",     "10",     "1546",   "555"),
    ("30",     "30",     "10",     "10",     "10",     "100"),
    ("30",     "30",     "10",     "1",     "10",     "100")
};
var orderedResults=_optoGrid.OrderBy(tuple=>tuple.prop6);

If one is feeling very lazy and the number of items isn't that greate, the names can be ommited:

var  _optoGrid = new List<(string, string, string, string, string, string)>{
    ("10",     "10",     "100",     "20",    "10",     "10"),
    ("20",     "20",     "50",     "10",     "1546",   "555"),
    ("30",     "30",     "10",     "10",     "10",     "100"),
    ("30",     "30",     "10",     "1",     "10",     "100")
};
var orderedResults=_optoGrid.OrderBy(tuple=>tuple.Item6);

The nice thing about tuples is they are strongly typed. There's no reason to use the same type for all of them, eg :

var  _optoGrid = new List<(int, int, string, string, string, double)>{
    (10,     10,     "100",     "20",    "10",    10),
    (20,     20,     "50",     "10",     "1546",  555),
    (30,     30,     "10",     "10",     "10",    100),
    (30,     30,     "10",     "1",     "10",     100)
};
var orderedResults=_optoGrid.OrderBy(tuple=>tuple.Item6);

This avoids another thorny issue, that of lexicographical ordering. If I change a string from "10" to "1000" it will *still* appear before "555", because words that start with"1"always come before words that start with"5". Using strings, OrderBy will return :

10 10 100 20 10   10000 
20 20 50  10 1546 555 

Ooops.

Using an int or double for the sixth field though, we get the expected order :

20 20  50 10 1546   555 
10 10 100 20   10 10000 

Upvotes: 4

Related Questions