Reputation: 2008
I have a collection of lines. Each line is a collection of fields. It is very easy to find column 1 and sort by its content:
_lines = lines.OrderBy(l => l.Fields.Find(f => f.ColumnNumber == 1).Content).ToList();
But how do I then sort a subset of this list? I want to sort by f.ColumnNumber == 3 where (f.ColumnNumber == 1).Content is 6.
So my line collection looks like this:
Col1, Col2, Col3
1, data, data
5, data, data
6, data, Chuck
6, data, Chuck
6, data, Jeffster
6, data, Jeffster
6, data, Grunke
6, data, Gertrude
8, data, data
9, data, data
I want to sort by col1, then sort only col1's where col1 == 6 but sort by col3.
Is there a way to do this in c#? I can't seem to find the magic formula.
EDIT:
My sample data is a little over simplified. Some lines have 3 columns, other lines have more or less columns. So when I use the .ThenBy extention, if I am trying to subsort lines on col 3 but say line 9 only has one column, I get an object reference not set to an instance of an object exception.
Here is a better example of the sample data:
Col1, Col2, Col3, Col4
1, data, data, data
5, data, data
6, data, Chuck
6, data, Chuck
6, data, Jeffster
6, data, Jeffster
6, data, Grunke
6, data, Gertrude
8, data, data
9, data
Code 1 lines have 4 columns.
Code 5 lines have 3.
Code 6 lines have 3 - and I need to sort by col 3 alphabetically.
Code 8 lines have 3 columns.
Code 9 lines have 2.
There is no guarantee the list is sorted at all. So I need first of all to have the lines sorted by that first column 1 - 9, then I need only code 6's to be sorted by col 3.
EDIT 2:
My class structure is somewhat complicated so I was trying to keep it simple as possible hoping it would be sufficient, but it looks like that his not the case so let me share with you the class definition:
public class Field : IField
{
public FieldSpecification Specification { get; set; }
public string Content { get; set; }
}
public class FieldSpecification
{
public int ColumnNumber { get; set; }
public int Length { get; set; }
public int StartPosition { get; set; }
public int EndPosition { get { return StartPosition + Length - 1; } }
public Justification Justification { get; set; }
public char PadCharacter { get; set; }
}
Then I have a bunch of lines that conform to the ILine interface
public interface ILine
{
List<IField> Fields { get; set; }
int Credits { get; }
int Debits { get; }
BigInteger Hash { get; }
}
So technically above I show something like field.ColumnNumber, but it should be field.Specification.ColumnNumber.
The objective is to build fixed width files according to specifications that can change. So each line has a collection of fields with specifications, and then data can go into the content, and the specification can help do validation: formatting validation.
I was hoping there would be a way to sort subsets of lists using linq, but I may need to deconstruct my final collection of lines, sort it, then reconstruct the collection. I was hoping to avoid that.
Upvotes: 4
Views: 1784
Reputation: 117057
If I can assume that you class definition is this:
public class Datum
{
public int ID { get; set; }
public string[] Cols { get; set; }
}
Then I can define the data like this:
var data = new []
{
new Datum() { ID = 1, Cols = new [] { "data", "data", "data", }, },
new Datum() { ID = 5, Cols = new [] { "data", "data", }, },
new Datum() { ID = 6, Cols = new [] { "data", "Chuck", }, },
new Datum() { ID = 6, Cols = new [] { "data", "Chuck", }, },
new Datum() { ID = 6, Cols = new [] { "data", "Jeffster", }, },
new Datum() { ID = 6, Cols = new [] { "data", "Jeffster", }, },
new Datum() { ID = 6, Cols = new [] { "data", "Grunke", }, },
new Datum() { ID = 6, Cols = new [] { "data", "Gertrude", }, },
new Datum() { ID = 8, Cols = new [] { "data", "data", }, },
new Datum() { ID = 9, Cols = new [] { "data", }, },
};
And then I can sort like this:
var sorted =
from d in data
let key =
String.Format("{0:0000}", d.ID)
+ (d.ID != 6 ? "" : "-" + d.Cols[1])
orderby key
select d;
I get these results:
Upvotes: 0
Reputation: 35716
You could use the ThenBy extension.
Looking at your desired output, it seems something as simple as,
var output = lines.Select(l => new
{
Col1 = int.Parse(l.Fields.Find(f => f.ColumnNumber == 1).Content),
Col2 = l.Fields.Find(f => f.ColumnNumber == 2).Content,
Col3 = l.Fields.Find(f => f.ColumnNumber == 3).Content
}).OrderBy(l => l.Col1).ThenBy(l => l.Col3);
would suffice.
If, for some reason, you only want to order the sub list when Col1
is 6
,
var output = lines.Select(l => new
{
Col1 = int.Parse(l.Fields.Find(f => f.ColumnNumber == 1).Content),
Col2 = l.Fields.Find(f => f.ColumnNumber == 2).Content,
Col3 = l.Fields.Find(f => f.ColumnNumber == 3).Content
}).OrderBy(l => l.Col1).ThenBy(l => l.Col1 == 6 ? l.Col3 : null);
One last caveat, depending on the type of Fields
there is probably a better approach to all of this.
Upvotes: 3
Reputation: 17605
It can be done via the ThenBy extension method; in general, the concept is known as lexicographical order.
Upvotes: 1