4

I have a component which outputs an ICollection of the following type:

public class CoordinatedInjectableValue
{
    public int XOffset { get; set; }

    public int YOffset { get; set; }

    public object Value { get; set; }
}

This is used to describe what is basically a dataset, so for a given XOffset value, the "Value" property of all these objects will be of the same type (int, string, etc). The Value property is always a built-in type, not a custom type.

My challenge is that I would like to use this collection to build up a kind of dataset structure which allows sorting on the columns.

As an example, if I had a collection which looked like this:

new List<CoordinatedInjectableValue>
{
    new CoordinatedInjectableValue {XOffset = 0, YOffset = 0, Value = 20},
    new CoordinatedInjectableValue {XOffset = 0, YOffset = 1, Value = 10},
    new CoordinatedInjectableValue {XOffset = 0, YOffset = 2, Value = 30},
    new CoordinatedInjectableValue {XOffset = 1, YOffset = 0, Value = "CCC"},
    new CoordinatedInjectableValue {XOffset = 1, YOffset = 1, Value = "BBB"},
    new CoordinatedInjectableValue {XOffset = 1, YOffset = 2, Value = "AAA"}
};

I want some method so I can reorder based on the first "column" (ie, where XOffset = 0), so that the collection would then look like this:

new List<CoordinatedInjectableValue>
{
    // Ordered by value where XOffset = 0
    new CoordinatedInjectableValue {XOffset = 0, YOffset = 0, Value = 10}, 
    new CoordinatedInjectableValue {XOffset = 0, YOffset = 1, Value = 20},
    new CoordinatedInjectableValue {XOffset = 0, YOffset = 2, Value = 30},
    new CoordinatedInjectableValue {XOffset = 1, YOffset = 0, Value = "BBB"}, // Note this has also been re-ordered
    new CoordinatedInjectableValue {XOffset = 1, YOffset = 1, Value = "CCC"},
    new CoordinatedInjectableValue {XOffset = 1, YOffset = 2, Value = "AAA"}
}

And if I ordered by the second column (XOffset = 1) then the following would happen:

new List<CoordinatedInjectableValue>
{
    // Ordered by value where XOffset = 1
    new CoordinatedInjectableValue {XOffset = 0, YOffset = 0, Value = 30}, // Note this has also been re-ordered
    new CoordinatedInjectableValue {XOffset = 0, YOffset = 1, Value = 10},
    new CoordinatedInjectableValue {XOffset = 0, YOffset = 2, Value = 20},
    new CoordinatedInjectableValue {XOffset = 1, YOffset = 0, Value = "AAA"}, 
    new CoordinatedInjectableValue {XOffset = 1, YOffset = 1, Value = "BBB"},
    new CoordinatedInjectableValue {XOffset = 1, YOffset = 2, Value = "CCC"}
};

As you can imagine, I'm having some difficulty doing this, even for single-level sorting. I haven't even tried implementing multi level sorting (eg, sort by this, then that).

I am looking for some kind of logical approach to this. I'd rather not have to change the component which returns this data, as it's supplied by a third party, and would be costly for us to change.

closed as unclear what you're asking by durron597, GlenH7, Bart van Ingen Schenau, user40980, amon Aug 16 '15 at 13:20

Please clarify your specific problem or add additional details to highlight exactly what you need. As it's currently written, it’s hard to tell exactly what you're asking. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.

  • LINQ OrderBy ThenBy ? stackoverflow.com/questions/298725/multiple-order-by-in-linq – paparazzo Jul 23 '15 at 14:37
  • @Blam Thanks for your comment - not sure I explained correctly, but I need to re-order by Value property, within the context of a single "column" (ie a set of values with the same XOffset value), which then needs to re-order the other "columns" – tom redfern Jul 23 '15 at 14:39
  • No you did not explain it very well. How is that not OrderBy(x => x.Offset).ThenBy(y => y.Value) – paparazzo Jul 23 '15 at 14:43
  • @Blam Because I don't want to order by XOffset. I want to order by whatever the Value property is set to. I will update my question. – tom redfern Jul 23 '15 at 14:46
  • Breaks if the group size of offset is not equal. – paparazzo Jul 23 '15 at 15:10
3

This is not just a sorting problem. You are also updating the YOffset values based on the sorting. So assume the input data is valid(i.e. no missing offsets, consistent type of Value property). Here is the code:

Source data:

var givenList = new List<CoordinatedInjectableValue>
{
    new CoordinatedInjectableValue {XOffset = 0, YOffset = 0, Value = 20},
    new CoordinatedInjectableValue {XOffset = 0, YOffset = 1, Value = 10},
    new CoordinatedInjectableValue {XOffset = 0, YOffset = 2, Value = 30},
    new CoordinatedInjectableValue {XOffset = 1, YOffset = 0, Value = "CCC"},
    new CoordinatedInjectableValue {XOffset = 1, YOffset = 1, Value = "BBB"},
    new CoordinatedInjectableValue {XOffset = 1, YOffset = 2, Value = "AAA"}
};

LINQ:

var sortedByFirstColumn = givenList
.GroupBy(v => v.YOffset)
.OrderBy(grp => grp.First(x => x.XOffset == 0).Value)
.SelectMany((grp, i) =>
    grp.Select(v => new CoordinatedInjectableValue
    {
        XOffset = v.XOffset,
        YOffset = i,
        Value = v.Value
    }))

Verify the result:

sortedByFirstColumn
.OrderBy(v => v.XOffset)
.ThenBy(v => v.YOffset)
.ToList()
.ForEach(v => Console.WriteLine("{0}\t{1}\t{2}", v.XOffset, v.YOffset, v.Value));

The output:

0   0   10
0   1   20
0   2   30
1   0   BBB
1   1   CCC
1   2   AAA

Note: I kept sortedByFirstColumn as IEnumerable and unordered, but adjust it the way how your application requires.

1
    public class CIVComparer : IComparer<CoordinatedInjectableValue>
{
    private string SortBy { get; set; }
    private bool SortAscending { get; set; }

    public CIVComparer()
    {
        SortBy = "default";
        SortAscending = true;
    }

    public CIVComparer(string sortBy, bool sortAscending)
    {
        SortBy = sortBy;
        SortAscending = sortAscending;
    }

    #region IComparer<CoordinatedInjectableValue> Members

    int IComparer<CoordinatedInjectableValue>.Compare(CoordinatedInjectableValue x, CoordinatedInjectableValue y)
    {
        CoordinatedInjectableValue cx, cy;
        if (SortAscending)
        {
            cx = x;
            cy = y;
        }
        else
        {
            cx = y;
            cy = x;
        }
        int result = 0;
        var useCulture = new System.Globalization.CultureInfo("en-US");
        string xCaseNum, yCaseNum;

        switch (SortBy)
        {
            case "XOffset":
                result = new Comparer(useCulture).Compare(cx.XOffset, cy.XOffset);
                if (result == 0) { result = new Comparer(useCulture).Compare(cx.Value, cy.Value); }
                if (result == 0) { result = new Comparer(useCulture).Compare(xCaseNum, yCaseNum); }
                break;

            case "Value":
                result = new Comparer(useCulture).Compare(cx.Value, cy.Value);
                if (result == 0) { result = new Comparer(useCulture).Compare(cx.XOffset, cy.XOffset); }
                if (result == 0) { result = new Comparer(useCulture).Compare(cx.YOffset, cy.YOffset); }
                break;

            case "default":
            default:
                result = new Comparer(useCulture).Compare(cx.YOffset, cy.YOffset);
                if (result == 0) { result = new Comparer(useCulture).Compare(cx.XOffset, cy.XOffset); }
                break;
        }
        return result;
    }

    #endregion
}

Then, when you want to sort a list:

    List<CoordinatedInjectableValue> CIVs = MethodWhichReturnsList(argsForMethod);
    CIVComparer civComparer = new CIVComparer(sortBy, sortAscending);
    CIVs.Sort(civComparer);

The logic in the Comparer will obviously be different, but, as you can see, it provides a lot of flexibility.

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