1

I am trying to sort an array by multiple properties, but the problem is that my array is multidimensional. Currently I have built this:

// Private function to get the value of the property
var _getPropertyValue = function (object, notation) {

    // Get all the properties
    var properties = notation.split('.');

    // If we only have one property
    if (properties.length === 1) {

        // Return our value
        return object[properties];
    }

    // Loop through our properties
    for (var property in object) {

        // Make sure we are a property
        if (object.hasOwnProperty(property)) {

            // If we our property name is the same as our first property
            if (property === properties[0]) {

                // Remove the first item from our properties
                properties.splice(0, 1);

                // Create our new dot notation
                var dotNotation = properties.join('.');

                // Find the value of the new dot notation
                return _getPropertyValue(object[property], dotNotation);
            }
        }
    }
};

// Create a service
var service = {

    // Sorts our products
    sort: function (products, notation) {

        notation = notation || 'details.title';

        // Call sort
        products.sort(function (a, b) {

            // Get our values
            var aValue = _getPropertyValue(a, notation),
                bValue = _getPropertyValue(b, notation);

            console.log(bValue);

            // If our attribute name is not the same as the second attribute
            if (aValue <= bValue) {

                // Return -1
                return -1;
            }

            // Otherwise return 1
            return 1;
        });
    }
};

// Return our service
return service;

And this is one item from the array (products)

{
    "id": 1,
    "gtin": "8714574627946|4549292038446",
    "productId": "0592C022",
    "make": "Canon",
    "model": "750D + EF-S 18-55mm",
    "expert": false,
    "sponsored": false,
    "attributes": {
        "id": 1,
        "compatibleMemory": "SD, SDHC, SDXC\"",
        "whiteBalance": "ATW, Cloudy, Custom modes, Daylight, Flash, Fluorescent L, Shade, Tungsten\"",
        "sceneModes": "Food, Landscape, Sports\"",
        "shootingModes": "",
        "photoEffects": "",
        "cameraPlayback": "Movie, Single image, Slide show\"",
        "tripod": false,
        "directPrinting": false,
        "colour": "Black",
        "picture": {
            "id": 1,
            "megapixel": "24.2 MP",
            "type": "SLR Camera Kit",
            "sensorType": "CMOS",
            "maxResolution": "6000 x 4000 pixels",
            "resolutions": "3984x2656, 2976x1984, 1920x1280, 720x480, 5328x4000, 3552x2664, 2656x1992, 1696x1280, 640x480, 6000x3368, 3984x2240, 2976x1680, 1920x1080, 720x480, 4000x4000, 2656x2656, 1984x1984, 1280x1280, 480x480\"",
            "stablizer": true,
            "location": "Lens",
            "supportedAspectRatios": "2.9 cm",
            "totalMegapixels": "24.7 MP",
            "formats": "JPG"
        },
        "video": {
            "id": 1,
            "maxResolution": "1920 x 1080 pixels",
            "resolutions": "640 x 480, 1280 x 720, 1920 x 1080 pixels\"",
            "captureResolution": "",
            "frameRate": "",
            "fullHD": true,
            "supportedFormats": null
        },
        "audio": {
            "id": 1,
            "supportedFormats": ""
        },
        "battery": {
            "id": 1,
            "powerSource": "Battery",
            "technology": "Lithium-Ion (Li-Ion)",
            "life": "",
            "type": "LP-E17"
        },
        "dimensions": {
            "id": 1,
            "width": "",
            "depth": "7.78 cm",
            "height": "10.1 cm",
            "weight": "",
            "weightIncludingBattery": "555 g"
        },
        "display": {
            "id": 1,
            "type": "LCD",
            "diagonal": "7.62 cm (3\"\")\"",
            "resolution": "1040000 pixels"
        },
        "exposure": {
            "id": 1,
            "isoSensitivity": "100, 6400, 12800, Auto\"",
            "mode": "Auto, Manual\"",
            "correction": "�5EV (1/2; 1/3 EV step)",
            "metering": "Centre-weighted, Evaluative (Multi-pattern), Partial, Spot\"",
            "minimum": 100,
            "maxiumum": 12800
        },
        "flash": {
            "id": 1,
            "modes": "Hi-speed sync, Red-eye reduction\"",
            "exposureLock": true,
            "rangeWide": "",
            "rangeTelephoto": "",
            "rechargeTime": "",
            "speed": "1/200"
        },
        "focusing": {
            "id": 1,
            "focus": "TTL-CT-SIR",
            "adjustment": "",
            "autoFocusModes": "",
            "closestDistance": "0.25 m",
            "normalRange": "",
            "macroRangeTelephoto": "",
            "macroRangeWide": "",
            "autoModeTelephoto": "",
            "autoModeWide": ""
        },
        "interface": {
            "id": 1,
            "pictBridge": true,
            "usbVersion": "2.0",
            "usbType": "",
            "hdmi": true,
            "hdmiType": "Mini"
        },
        "lens": {
            "id": 1,
            "focalLength": "18 - 55 mm",
            "minimumFocalLength": "2.9 cm",
            "maximumFocalLength": "8.8 cm",
            "minimumAperture": "3.5",
            "maximumAperture": "38",
            "lensStructure": "13/11",
            "zoom": {
                "id": 1,
                "optical": "",
                "digital": "",
                "extraSmart": "",
                "combined": ""
            }
        },
        "network": {
            "id": 1,
            "wiFi": false,
            "wiFiStandards": "",
            "nfc": false
        },
        "shutter": {
            "id": 1,
            "fastestSpeed": "1/4000 s",
            "slowestSpeed": "30 s"
        }
    },
    "details": {
        "id": 1,
        "title": "Canon EOS 750D + EF-S 18-55mm",
        "description": "\"<b>Take your pictures to the next level with EOS 750D</b>\\n- Effortlessly take your pictures to the next level with the latest DSLR technology and Scene Intelligent Auto mode.\\n- Effortlessly capture stunning detail in any situation\\n- Record cinematic movies as easily as you shoot stills\\n- Easily connect and share your images with the world\\n\\n<b>Take your pictures to the next level with EOS 750D</b>\\n<b>Range of shooting modes</b>\\nEffortlessly capture stunning images using the latest DSLR technology with Basic and Creative modes, which allow you to take as much or as little control as you like.\\n\\n<b>Moveable screen for creative framing</b>\\nExplore creative shooting angles and enjoy simple and intuitive access to controls using the 3.0\"\" (7.7cm) Vari Angle LCD touch screen\\n\\n<b>Intelligent Viewfinder</b>\\nEOS 750D features an Intelligent Viewfinder which gives a much enhanced shooting experience. As you look through the viewfinder you can more easily see the focus point and any active AF areas, also the shooting information is clearly displayed.\\n\\n<b>Effortlessly capture stunning detail in any situation</b>\\nCapture vivid, detailed, high-resolution images with better dynamic range, lower noise and excellent control over depth of field thanks to a 24.2 Megapixel APS-C sensor.\\n\\n<b>19 all cross-type AF points for accurate subject tracking</b>\\nKeep track of fast moving action thanks to a fast and accurate autofocus system comprising 19 cross-type AF points.\\n\\n<b>Fast processor for action</b>\\nA powerful DIGIC 6 processor delivers full resolution shooting at 5 fps � so you�ll never miss that decisive moment.\\n\\n<b>Great low light shots</b>\\nTake memorable low light pictures without using flash thanks to a large ISO sensitivity range of ISO 100-12800 (extendable to ISO 25600)\\n\\n<b>Record cinematic Full HD movies as easily as you shoot stills</b>\\nShoot superbly detailed Full HD movies with a cinematic feel thanks to DSLR control over depth of field. Record your movies in MP4 format for quicker online sharing and easier transfer to other devices.\\n\\n<b>Smoother results</b>\\nEasily shoot cinematic Full HD movies with Hybrid CMOS AF III to track movement and focus smoothly between subjects.\\n\\n<b>Empower your creativity with easy shooting modes</b>\\nLet the camera do the work for you and capture creative photos with ease using a range of Scene Modes\\n\\n<b>Creative movie modes</b>\\nExpand the range of shooting possibilities in movies with features like Miniature Effect in movie.\"",
        "shortDescription": "\"22.3 x 14.9mm CMOS, 24.2 megapixels, 3:2, DIGIC 6, LCD, ISO 12800, Full HD Movie, USB, HDMI mini, SD/SDHC/SDXC, Black\"",
        "summary": "\"Canon 750D + EF-S 18-55mm, EOS. Megapixel: 24.2 MP, Camera type: SLR Camera Kit, Sensor type: CMOS. Focal length range (f-f): 18 - 55 mm, Minimum focal length (35mm film equiv): 2.9 cm, Maximum focal length (35mm film equiv): 8.8 cm. Focus: TTL-CT-SIR, Closest focusing distance: 0.25 m. ISO sensitivity: 100, 6400, 12800, Auto, Light exposure modes: Auto, Manual, Light exposure control: Program AE. Fastest camera shutter speed: 1/4000 s, Slowest camera shutter speed: 30 s, Camera shutter type: Electronic\"",
        "shortSummary": "\"Canon EOS 750D + EF-S 18-55mm, ATW, Cloudy, Custom modes, Daylight, Flash, Fluorescent L, Shade, Tungsten, Food, Landscape, Sports, Movie, Single image, Slide show, Battery, SLR Camera Kit, TTL-CT-SIR\""
    },
    "category": null,
    "preview": {
        "id": 1,
        "highRes": "http://images.icecat.biz/img/norm/high/26171112-1991.jpg",
        "lowRes": "http://images.icecat.biz/img/norm/low/26171112-1991.jpg",
        "manual": ""
    }
}

This works for 1 property. Does anyone know how I can efficiently rehash this to work with multiple properties?


I have tried to do this:

// Create a service
var service = {

    // Sorts our products
    sort: function (products, notations) {

        // Call sort
        products.sort(function (a, b) {

            // For each notation
            for (var i = 0; i < notations.length; i++) {

                // Get our notation
                var notation = notations[i];

                // Get our values
                var aValue = _getPropertyValue(a, notation),
                    bValue = _getPropertyValue(b, notation);

                console.log(bValue);

                // If our attribute name is not the same as the second attribute
                if (aValue <= bValue) {

                    // Return -1
                    return -1;
                }

                // Otherwise return 1
                return 1;
            }
        });
    }
};

and invoked it like this:

handler.sort(self.products, ['attributes.dimensions.weightIncludingBattery', 'attributes.network.wiFi']);

but this only seems to sort by the first property and not the second.

5
  • please add an example of the data, you like to sort. Commented Apr 26, 2016 at 10:58
  • Could you please show the object structure? Commented Apr 26, 2016 at 11:05
  • 1
    Ok, I have added one item from the products array Commented Apr 26, 2016 at 11:07
  • and what would you like to sort? Commented Apr 26, 2016 at 11:09
  • well if this was in an array, I would like to sort by user defined "properties". So you can pass in an array of dot notations and it will sort by the first item in the array first, then by the second and then by the third and so on. Commented Apr 26, 2016 at 11:12

3 Answers 3

1

With the link that @Nina Scholz posted I managed to create a set of functions that seem to work fast. The set of functions look like this:

// Private function to get the value of the property
var _getPropertyValue = function (object, notation) {

    // Get all the properties
    var properties = notation.split('.');

    // If we only have one property
    if (properties.length === 1) {

        // Return our value
        return object[properties];
    }

    // Loop through our properties
    for (var property in object) {

        // Make sure we are a property
        if (object.hasOwnProperty(property)) {

            // If we our property name is the same as our first property
            if (property === properties[0]) {

                // Remove the first item from our properties
                properties.splice(0, 1);

                // Create our new dot notation
                var dotNotation = properties.join('.');

                // Find the value of the new dot notation
                return _getPropertyValue(object[property], dotNotation);
            }
        }
    }
};

// Get our fields
var _getFields = function (notations) {

    // Create our array
    var fields = [];

    // For each notation
    angular.forEach(notations, function (notation) {

        // Get our field
        var names = notation.split('.'),
            len = names.length,
            name = names[len - 1];

        // Push our name into our array
        fields.push({ name: name, notation: notation });
    });

    // Return our fields
    return fields;
};

// Create a mapped array
var _createMapped = function (array, notations) {

    // Get our fields
    var fields = _getFields(notations);

    // Create our mapped array
    var mapped = array.map(function (a, i) {

        // Create our object
        var obj = {
            index: i
        };

        // For each of our fields
        angular.forEach(fields, function (field) {

            // Map our field
            obj[field.name] = _getPropertyValue(a, field.notation);
        });

        // Return our object
        return obj;
    });

    // Return our mapped array
    return mapped;
};

// Create a service
var service = {

    // Sorts our products
    sort: function (products, notations) {

        // Get our fields
        var mapped = _createMapped(products, notations);

        // Sort our mapped array
        mapped.sort(function (a, b) {

            // Loop through our properties
            for (var i = 0; i < notations.length; i++) {

                // Get our value (skip the first)
                var o1 = a[i + 1];
                var o2 = b[i + 1];

                // Compare the values
                if (o1 < o2) return -1;
                if (o1 > o2) return 1;
            }

            // Default return 
            return 0;
        });

        // Get our result
        var result = mapped.map(function (item) {
            return products[item.index];
        });

        // Return our result
        return result;
    }
};

// Return our service
return service;
Sign up to request clarification or add additional context in comments.

Comments

0

Basically you need something like that:

For the access to a property's value an iteration through the object

function getValue(string, object) {
    return string.split('.').reduce(function (r, a) {
        return r[a];
    }, object);
}

And for the sort mechanism the iteration over the wanted sort parameters. Actually I assume, that all values are strings.

// handler.sort
function sort(array, order) {
    array.sort(function (a, b) {
        var r = 0;
        order.some(function (s) {
            r = getValue(s, a).localeCompare(getValue(s, b));
            return r;
        });
        return r;
    });
}

The drawback of this is a very slow sorting, because of the lookup mechanism of a specific value.

A faster way would be sorting with map, where the map contains only the wanted values from the getValue

2 Comments

your getValue function, is that the same as my _getPropertyValue?
I really like that getValue function, I didn't even know you could do that.
0

Sorting over multiple properties can be done as in the following example

var data = [
	        {
	          a : 10,
	          b : 24
            },
	        {
	          a : 11,
	          b : 20
            },
	        {
	          a : 12,
	          b : 21
            },
	        {
	          a : 12,
	          b : 10
            },
	        {
	          a : 10,
	          b : 12
            },
	        {
	          a : 15,
	          b : 7
            },
	        {
	          a : 10,
	          b : 18
            }
           ]
var sortData = (arr, prop1, prop2) => arr.sort((p,c) => p[prop1] < c[prop1] ? -1: p[prop1] == c[prop1] ? p[prop2] <= c[prop2] ? -1 : 1: 1);
      sorted = sortData(data,"a","b");

document.write("<pre>" + JSON.stringify(sorted,null,2) + "</pre>");

2 Comments

I have added an update to my question as I don't think your solution is what I need.
@ r3plica I have made some corrections. See if it's giving an idea.

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.