1
\$\begingroup\$

Is there a very short way to do this same thing in a pure LINQ based way? It seems like I should not have to do all of this below to get to the OrderBy.

Dictionary<DataColumn, DateTime> columns = new Dictionary<DataColumn, DateTime>();
int startIndex;
int endIndex;
for (int i = 0; i < table.Columns.Count; ++i)
{        
    // these columns need to be sorted
    startIndex = table.Columns[i].Caption.IndexOf('(');
    endIndex = table.Columns[i].Caption.IndexOf(')');
    if (startIndex > 0 && endIndex > 0)
    {
        // create a standard date
        string monthYear = table.Columns[i].Caption.Substring(
            startIndex + 1, endIndex - startIndex - 1);
        columns.Add(
            table.Columns[i], 
            DateTime.Parse(monthYear.Replace("/", "/01/")).Date);
    }
    else
    {
        // all other columns should be sorted first
        columns.Add(table.Columns[i], DateTime.MinValue);
    }
}

// perform the LINQ order by
columns = columns.OrderBy(o => o.Value).ToDictionary(o => o.Key, o => o.Value);

// order the original table uses the dictionary
for (int i = 0; i < columns.Count; ++i)
{
    table.Columns[columns.Keys.ElementAt(i).Caption].SetOrdinal(i);
}
\$\endgroup\$
2
\$\begingroup\$

Throwing the contents into a dictionary will lose any sorting you may have had. Dictionaries are unordered collections.

The cleanest way to deal with this really is to implement your own custom comparer to compare the columns and reset their order based on the ordering.

I believe this would be more or less logically equivalent:

public class MyColumnComparer : Comparer<DataColumn>
{
    static Regex re = new Regex(@"[^\(\)][^\(]*\((\d{2}/\d{4})\).*");
    public override int Compare(DataColumn x, DataColumn y)
    {
        // a regular expression might be overkill here but it will be easier to maintain
        var xMatch = re.Match(x.Caption);
        var yMatch = re.Match(y.Caption);

        // both did not contain the pair
        if (!xMatch.Success && !yMatch.Success)
        {
            // use original ordering
            return x.Ordinal.CompareTo(y.Ordinal);
        }

        // one contained the pair
        if (xMatch.Success != yMatch.Success)
        {
            return yMatch.Success ? -1 : 1;
        }

        // both contains a pair
        var xDate = DateTime.ParseExact(xMatch.Groups[1].Value, "MM/yyyy",
            CultureInfo.InvariantCulture);
        var yDate = DateTime.ParseExact(yMatch.Groups[1].Value, "MM/yyyy",
            CultureInfo.InvariantCulture);
        return xDate.CompareTo(yDate);
    }
}
static void ProcessTable(DataTable table)
{
    var sortedColumns = table.Columns
        .Cast<DataColumn>()
        .OrderBy(col => col, new MyColumnComparer())
        .ToList();
    var index = 0;
    foreach (var column in sortedColumns)
        column.SetOrdinal(index++);
}
\$\endgroup\$
1
\$\begingroup\$

It's not an answer to the question, but a local variable for table.Columns[i] and caption would improve the readability.

Additionally, startIndex and endIndex could be declared inside the loop. (Variable declaration closer to usage Vs Declaring at the top of Method)

for (int i = 0; i < table.Columns.Count; ++i)
{        
    DataColumn column = table.Columns[i];
    string caption = column.Caption;
    // these columns need to be sorted
    int startIndex = caption.IndexOf('(');
    int endIndex = caption.IndexOf(')');
    if (startIndex > 0 && endIndex > 0)
    {
        // create a standard date
        string monthYear = caption.Substring(
            startIndex + 1, endIndex - startIndex - 1);
        columns.Add(
            column, 
            DateTime.Parse(monthYear.Replace("/", "/01/")).Date);
    }
    else
    {
        // all other columns should be sorted first
        columns.Add(column, DateTime.MinValue);
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.