Ian
Ian

Reputation: 161

Sorting list by first value in child list

I have a Row object which contains a list of values (columns) stored in a RowValue object. I would like to sort this list based on the value for the column marked as the first.

The RowValue object has a property named 'key' which is a Column object that contains the validation rules. It has another property named 'value' which is the string or numeric value of the column.

The Column validation rules also contain a property named 'display_order' which tells me what order to show the columns in a grid.

I want to sort my List based on the value of the first column in ascending order. Basically, I want to sort on value for the column with the minimum 'display_order' value.

Row object:

public class Row
{
    public List<RowValue> Values { get; set; } = new List<RowValue>();
}

RowValue:

public class RowValue
{
    public Column key { get; set; }
    public string value { get; set; }
}

Column:

public class Column
{
    public string name;
    public ColumnValidation ColumnValidation;
}

And finally, ColumnValidation:

public class ColumnValidation
{
    public string column_label;
    public DataTypeEnum data_type;
    public int width;
    public int decimal_places;
    public int display_order;
    public string calculation_formula;
    public string edit_style;
    public string default_value;
    public decimal? minimum_value;
    public decimal? maximum_value;
    public bool is_column_nullable = false;
    public bool inherit_values = false;
    public bool display_column = false;
    public bool is_editable = false;
    public int column_style;
    public string code_table_name;
    public string code_display_name;
    public string code_data_column_name;
}

I'm trying to reorder my rows with something like this but it doesn't seem to be working:

// reorder rows
// get the column definition for the first column
Column firstColumn = rows.Select(x => x.Values).FirstOrDefault().OrderBy(x => x.key.ColumnValidation.display_order).Select(x => x.key).FirstOrDefault();

rows = rows.OrderBy(x => x.Values.OrderBy(y => y.key.ColumnValidation.display_order).FirstOrDefault().value).ToList();

Upvotes: 0

Views: 116

Answers (3)

Harald Coppoolse
Harald Coppoolse

Reputation: 30512

So every Row has zero or more RowValues. Every RowValue has a Column and a Value. And every Column has a DisplayOrder

You want for every Row, the Value of the Column with the lowest DisplayOrder. Then OrderBy these Values.

IEnumerable<Row> rows = ...
var result = rows.Select(row => new
{
    OriginalRow = row,

    // every row has zero or more Values
    // every RowValue has a Key.ColumnValidation.DisplayOrder
    // Order the RowValues by Key.ColumnValidation.DisplayOrder
    SortValue = row.Values
        .Orderby(rowValue => rowValue.Key.ColumnValidation.DisplayOrder)

        // from this sorted sequence of RowValues, select the Value
        .Select(rowValue => rowValue.Value)

        // and keep only the first one, which is the one of the first displayed column
        .FirstOrDefault(),
})

So now you have a sequence of items where every item contains an OriginalRow and aSortValue. The SortValue is the Value of the RowValue that had the lowest Column DisplayOrder.

Continuing the LINQ:

.OrderBy(selectResult => selectResult.SortedValue)

And if you want only your Original Row:

.Select(orderedSelectResult => orderedSelectResult.OriginalValue);

Upvotes: 1

NetMage
NetMage

Reputation: 26936

Assuming you meant to order a List<Row> by the lowest display_order of the Row.Values list, and that you don't need to sort in place, and that every Row.Values will have the same lowest display_order value or it is okay to sort against the lowest available for each Row, you can use LINQ:

var ans = rs.OrderBy(r => r.Values.OrderBy(rv => rv.key.ColumnValidation.display_order)
                                  .First()
                                  .value)
            .ToList();

Upvotes: 1

J. G&#243;mez
J. G&#243;mez

Reputation: 96

As I understand it, you have a list of rows, each of which contains a list of columns with values. These columns have an inherent sort key based on their display orders.

Then, for a given result set, you want to find the column present with the lowest sort order, then using that, sort all the row objects by that column.

I put together some code. I put it in a .NET Fiddle so you can experiment with it. Try changing the sort order keys and see if it matches your request.

Here is the key part of the code:

        // find column with lowest sort value that is actually present
                              //   get flat list of all values across rows
        var firstColumnLabel = rows.SelectMany(l => l.Values)
                              // get validation column
                              .Select(v => v.key.ColumnValidation)
                              // get label of first column
                              .OrderBy(v => v.display_order)
                              .Select(v => v.column_label)
                              .FirstOrDefault();
        
        // sort rows by this column 
        // if a row lacks the column, then it gets bumped to the end
        var sortedRows = rows
                            .Select(r => new { r, key = GetSortKey(r, firstColumnLabel) })
                            // push rows with missing columns to end
                            .OrderBy(rr => rr.key == null ? 1 : 0)
                            // now do a secondary sort by the actual key
                            .ThenBy(rr => rr.key)
                            // recover original row object without sort key
                            .Select(rr => rr.r);

    // ...

    static string GetSortKey(Row r, string column_label) {
        var columns = r.Values;
        var colForLabel = columns.FirstOrDefault(c => c.key.ColumnValidation.column_label == column_label);
        if(colForLabel != null) {
            return colForLabel.value;
        }
        
        return null;
    }

Here is the full code:

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        // some sample data with row values and column validation orders
        var sampleRow1 = new (string value, int column_order, string column_label)[] {
          ("Val 1", 2, "Column B"), 
          ("Val 2", 1, "Column A"),
          ("Val 30", 5, "Column E")
        };
        
        var sampleRow2 = new (string value, int column_order, string column_label)[] {
          ("Val 1", 2, "Column B"), 
          ("Val 2", 5, "Column E")
        };
        
        var sampleRows = new[] { sampleRow1, sampleRow2 };
        
        // build up the row data
        var rows = new List<Row>();
        foreach(var sampleRow in sampleRows) {
            var row = new Row();
            rows.Add(row);
            row.Values = new List<RowValue>();
            foreach(var sampleColumn in sampleRow) {
                row.Values.Add(new RowValue {
                  value = sampleColumn.value,
                  key = new Column {
                      ColumnValidation = new ColumnValidation
                      {
                        display_order = sampleColumn.column_order,
                        column_label = sampleColumn.column_label
                      }
                  }
                });
            } // END: build columns in Row
        } // END: build list of Row objects
        
        // find column with lowest sort value that is actually present
                              //   get flat list of all values across rows
        var firstColumnLabel = rows.SelectMany(l => l.Values)
                              // get validation column
                              .Select(v => v.key.ColumnValidation)
                              // get label of first column
                              .OrderBy(v => v.display_order)
                              .Select(v => v.column_label)
                              .FirstOrDefault();
        
        // sort rows by this column 
        // if a row lacks the column, then it gets bumped to the end
        var sortedRows = rows
                            .Select(r => new { r, key = GetSortKey(r, firstColumnLabel) })
                            // push rows with missing columns to end
                            .OrderBy(rr => rr.key == null ? 1 : 0)
                            // now do a secondary sort by the actual key
                            .ThenBy(rr => rr.key)
                            // recover original row object without sort key
                            .Select(rr => rr.r);
        
        Console.WriteLine($"We sorted by {firstColumnLabel}");
        
        foreach(var srow in sortedRows) {
            Console.WriteLine("Row");
            foreach(var col in srow.Values) {
                Console.WriteLine($"{col.key.ColumnValidation.column_label} = {col.value}");
            }
            
        }
    }
    
    static string GetSortKey(Row r, string column_label) {
        var columns = r.Values;
        var colForLabel = columns.FirstOrDefault(c => c.key.ColumnValidation.column_label == column_label);
        if(colForLabel != null) {
            return colForLabel.value;
        }
        
        return null;
    }

}


public class Row
{
    public List<RowValue> Values { get; set; }
}

public class RowValue
{
    public Column key { get; set; }
    public string value { get; set; }
}

public class Column
{
    public ColumnValidation ColumnValidation;
}

public class ColumnValidation
{
    public string column_label;
    public int display_order;
    // ...
}

Upvotes: 0

Related Questions