1

Bear with me, I will try to not make this too complicated. I have a list of products. A product looks like this:

{
    "id": 3797,
    "title": "Canon EOS 100D Digital SLR Camera with 18-55 IS STM Lens, HD 1080p, 18MP, 3\" LCD Touch Screen",
    "shortTitle": "Canon EOS 100D Black",
    "brand": "Canon",
    "model": "EOS 100D",
    "colour": "Black",
    "gtin": "8714574602721",
    "image": "http://piiick.blob.core.windows.net/images/Canon-EOS-100D-18-55-Black-8714574602721.png",
    "type": "Digital SLR",
    "lensFocalLength": "18-55",
    "lensType": "IS STM",
    "lensMount": "EF/EF-S",
    "maxAperture": "999",
    "connectivity": "",
    "shootingModes": "Scene Intelligent Auto (Stills and Movie), No Flash, Creative Auto, Portrait, Landscape, Close-up, Sports, SCN(Kids, Food, Candlelight, Night Portrait, Handheld Night Scene, HDR Backlight Control), Program AE , Shutter priority AE, Aperture priority AE, Manual (Stills and Movie)",
    "weight": 410.0,
    "width": 116.8,
    "height": 90.7,
    "depth": 69.4,
    "digitalZoom": "N/A",
    "opticalZoom": "N/A",
    "waterproof": false,
    "maxVideoResolution": "1920 x 1080",
    "sensorType": "CMOS",
    "sensorSize": "22.3 x 14.9 mm",
    "continuousShootingSpeed": "4",
    "iso": "1600",
    "style": "traditional",
    "designer": "",
    "dateAnnounced": "10/06/2008",
    "focusPoints": 7
}

But to help explain this I will simplify it. I am trying to create a sort method that will allow sorting on multiple columns. For example, I have this method:

/// <summary>
/// Performs an advanced sort on products using sortations
/// </summary>
/// <param name="products">The products to sort</param>
/// <param name="sortations">The sortation list</param>
public void AdvancedSort(List<JObject> products, List<Sortation> sortations)
{

    // Get our value types
    GetTypesFromOperator(sortations);

    // Sort our products by the sortations
    products.Sort((a, b) =>
    {
        foreach (var sortation in sortations)
        {

            // Get our values
            var fieldName = sortation.Field;
            var x = a.SelectToken(fieldName).ToString();
            var y = b.SelectToken(fieldName).ToString();
            var type = sortation.Type;

            // If both values are the same, skip the rest of this iteration
            if (x.Equals(y)) return 0;

            // If we have an expression
            if (!string.IsNullOrEmpty(sortation.Expression))
            {

                // If we are checking for between
                if (sortation.Operator == "><")
                {

                    // Get our values
                    var values = sortation.Expression.Split(',');

                    // If we have 2 values
                    if (values.Length == 2)
                        return CompareBetween(values, x, y);
                }

                // If we are checking booleans
                if (sortation.Operator == "===")
                    return CompareBoolean(sortation.Expression);

                // If we are checking equals
                if (sortation.Operator == "=")
                    return y.Equals(sortation.Expression) ? 1 : -1;

                // If we are checking like
                if (sortation.Operator == "%")
                    return y.Equals(sortation.Expression, StringComparison.OrdinalIgnoreCase) ? 1 : -1;
            }

            // If we get this far, do a switch on sortation types
            switch (sortation.Type)
            {
                case SortationValueType.Boolean:
                    return Boolean.Parse(x) ? -1 : 1;
                case SortationValueType.Number:
                    return CompareNumeric(x, y, sortation.Direction);
                default:
                    return string.CompareOrdinal(x, y);
            }
        }

        // If nothing matches, return 0
        return 0;
    });
}

It is trying to sort based on property value types and what I call sortations. A sortation is just a class that stipulates the operator, expression and field to query. This is an example of a sortation:

// Add importance, expert and sponsored
sortations.Add(new Sortation
{
    Field = "importance",
    Operator = "<"
});

Now, we can have an number of different sortations which are dynamically added. I need to be able to sort the products by these sortations. I have created a simple test:

[Test]
public void SortBySilverColourSilverShouldBeTop()
{

    // Assemble
    var json = "[{ colour: 'Black', importance: '150' }, { colour: 'Black', importance: '150' }, { colour: 'Black', importance: '150' }, { colour: 'Silver', importance: '150' }, { colour: 'Black', importance: '149' }, { colour: 'Black', importance: '149' }, { colour: 'Black', importance: '149' }, { colour: 'Silver', importance: '149' }]";
    var products = JsonConvert.DeserializeObject<List<JObject>>(json);
    var sortations = new List<Sortation>() { new Sortation() { Field = "colour", Operator = "=", Expression = "Silver" }, new Sortation() { Field = "importance", Operator = "<" } };

    // Act
    _sortProvider.AdvancedSort(products, sortations);
    var result = products[0].SelectToken("colour").ToString();

    // Assert
    Assert.That(result, Is.EqualTo("Silver"));
}   

I would expect with that, that I would get this output:

[  
   { colour:'Silver', Importance:'150'},
   { colour:'Black', Importance:'150' },
   { colour:'Black', Importance:'150' },
   { colour:'Black', Importance:'150' },
   { colour:'Silver', Importance:'149' },
   { colour:'Black', Importance:'149' },
   { colour:'Black', Importance:'149' },
   { colour:'Black', Importance:'149' }
]

but instead I get this output:

[  
   { colour:'Silver', Importance:'150' },
   { colour:'Silver', Importance:'149' },
   { colour:'Black', Importance:'150' },
   { colour:'Black', Importance:'150' },
   { colour:'Black', Importance:'150' },
   { colour:'Black', Importance:'149' },
   { colour:'Black', Importance:'149' },
   { colour:'Black', Importance:'149' }
]

Can someone help me fix my sort method?

2
  • This sort of stuff is really easy when the data is in some sort of database, and you'll get better performance that way than manually sorting after the fact. If this data originally comes from a database, you'd be much better off converting your sortations into part of a query. If it doesn't come from a database you might consider putting it into a database. Commented Jun 2, 2017 at 13:51
  • It comes from an API hence why it's all in json :D Commented Jun 2, 2017 at 14:16

2 Answers 2

2

In my mind your basic problem is that as soon as the first sortation hits a match it returns the sort order value to the sort function and the rest of the sortations don't matter at all. I believe what you need to do is swap the foreach and the products.sort call, like so:

// Get our value types
GetTypesFromOperator(sortations);

foreach (var sortation in sortations)
{
    // Sort our products by the sortations
    products.Sort((a, b) =>
    {
        // Get our values
        var fieldName = sortation.Field;
        var x = a.SelectToken(fieldName).ToString();
        var y = b.SelectToken(fieldName).ToString();
        var type = sortation.Type;
        ....
        ....
    }
}

This will take longer, but each sortation will be considered. Keep in mind that the last sortation in the list will be the most significant.

Sign up to request clarification or add additional context in comments.

2 Comments

that works, how much slower are we talking do you think? I will never have more than 200 products and probably no more than 3 sortations
At those sizes, not slow enough to bother you. The extra sorts cause the most trouble, as even the most efficient sorting algorithms take a lot computation, but with at most 3 of them you can expect it to take only 3 times longer than your original.
1

It looks like you've got them backwards - it puts all the "Silver"s first, then ordering by importance second.

Try swapping the order of your sortations:

var sortations = new List<Sortation>() {
    new Sortation() { Field = "importance", Operator = "<" },
    new Sortation() { Field = "colour", Operator = "=", Expression = "Silver" }
};

2 Comments

If I do that, the "Silver" products end up at the bottom of each importance block
in fact, it never gets to the second sortation

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.