Michael
Michael

Reputation: 443

How to use JS Sort to sort Multiple Elements

I have an array of objects that I need sorted. If the object has class "search-filter-type", it needs to be sorted by specific order (see: car_types_order). For all other objects in the array, it should be sorted alphabetically by label. What I have below works, I'm wondering if there should be two separate sort functions or if there is a cleaner way of achieving this? Thanks!

Code:

    var car_types_order = ['Small Cars', 'Medium Cars', 'Large Cars', 'SUVs & Crossovers', 'Vans', 'Luxury',
        'Convertibles', 'Sports', 'Wagons', 'Hybrids', 'Electric', 'Pickup Trucks', 'Off-Road Vehicles', 'RVs',
        'Commercial', 'Specialty'];

    filters.sort(function(a, b){
        if (a.class == "search-filter-type" || b.class == "search-filter-type")
            return car_types_order.indexOf(a.label) - car_types_order.indexOf(b.label);
        else
        {
            if (a.label < b.label)
                return -1;
            if (a.label > b.label)
                return 1;
        }
        return 0;
    });

Example Objects:

{title: "Rental Company", class: "search-filter-company", label: "Hertz", id: "filter-companies-HZ", value: "HZ"}
{title: "Rental Company", class: "search-filter-company", label: "Silvercar", id: "filter-companies-SC", value: "SC"}
{title: "Rental Company", class: "search-filter-company", label: "NextCar", id: "filter-companies-NC", value: "NC"}
{title: "Rental Company", class: "search-filter-company", label: "National", id: "filter-companies-NA", value: "NA"}
{title: "Rental Company", class: "search-filter-company", label: "Payless", id: "filter-companies-ZA", value: "ZA"}
{title: "Rental Company", class: "search-filter-company", label: "Sixt", id: "filter-companies-SX", value: "SX"}
{title: "Rental Company", class: "search-filter-company", label: "Thrifty", id: "filter-companies-ZT", value: "ZT"}
{title: "Car Type", class: "search-filter-type", label: "Commercial", id: "filter-types-commercial", value: "SKAR"}
{title: "Rental Company", class: "search-filter-company", label: "Routes", id: "filter-companies-RO", value: "RO"}
{title: "Car Type", class: "search-filter-type", label: "Electric", id: "filter-types-electric", value: "ICAE"}
{title: "Car Type", class: "search-filter-type", label: "Medium Cars", id: "filter-types-medium", value: "FCAR"}
{title: "Car Type", class: "search-filter-type", label: "Small Cars", id: "filter-types-small", value: "CCAR"}

Upvotes: 2

Views: 116

Answers (2)

user2437417
user2437417

Reputation:

Don't be allured by "clever" hacks that provide short code that becomes very difficult to interpret, and therefore likely to obscure nasty bugs.

Write clear, descriptive code so that others (and even yourself at a later date) can quickly comprehend its intent. Here's an example:

var data = [{ title: "Rental Company", class: "search-filter-company", label: "Hertz", id: "filter-companies-HZ", value: "HZ" }, { title: "Rental Company", class: "search-filter-company", label: "National", id: "filter-companies-NA", value: "NA" }, { title: "Rental Company", class: "search-filter-company", label: "NextCar", id: "filter-companies-NC", value: "NC" }, { title: "Rental Company", class: "search-filter-company", label: "Payless", id: "filter-companies-ZA", value: "ZA" }, { title: "Rental Company", class: "search-filter-company", label: "Silvercar", id: "filter-companies-SC", value: "SC" }, { title: "Rental Company", class: "search-filter-company", label: "Sixt", id: "filter-companies-SX", value: "SX" }, { title: "Rental Company", class: "search-filter-company", label: "Thrifty", id: "filter-companies-ZT", value: "ZT" }, { title: "Car Type", class: "search-filter-type", label: "Commercial", id: "filter-types-commercial", value: "SKAR" }, { title: "Rental Company", class: "search-filter-company", label: "Routes", id: "filter-companies-RO", value: "RO" }, { title: "Car Type", class: "search-filter-type", label: "Electric", id: "filter-types-electric", value: "ICAE" }, { title: "Car Type", class: "search-filter-type", label: "Medium Cars", id: "filter-types-medium", value: "FCAR" }, { title: "Car Type", class: "search-filter-type", label: "Small Cars", id: "filter-types-small", value: "CCAR" }],
    car_types_order = ['Small Cars', 'Medium Cars', 'Large Cars', 'SUVs & Crossovers', 'Vans', 'Luxury', 'Convertibles', 'Sports', 'Wagons', 'Hybrids', 'Electric', 'Pickup Trucks', 'Off-Road Vehicles', 'RVs', 'Commercial', 'Specialty'];

data.sort((a, b) => {
  const aSearchArray = a.class === "search-filter-type";
  const bSearchArray = b.class === "search-filter-type";

  if (aSearchArray !== bSearchArray) { // Group by ordering method
     return aSearchArray ? 1 : -1;
  }

  if (aSearchArray) { // They both need to search the array
     return car_types_order.indexOf(a.label) - car_types_order.indexOf(b.label);
  }

  return a.label.localeCompare(b.label); // Order them lexically
});

console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

This is guaranteed to never search the array unless both objects have the "search-filter-type" class. Nor will it perform a lexical comparison of .label if neither have that class.


As a potential performance enhancement, use a Map of strings to order number instead of an Array for the car_types_order.

This demo starts with an Array but uses it only to build the Map.

var data = [{ title: "Rental Company", class: "search-filter-company", label: "Hertz", id: "filter-companies-HZ", value: "HZ" }, { title: "Rental Company", class: "search-filter-company", label: "National", id: "filter-companies-NA", value: "NA" }, { title: "Rental Company", class: "search-filter-company", label: "NextCar", id: "filter-companies-NC", value: "NC" }, { title: "Rental Company", class: "search-filter-company", label: "Payless", id: "filter-companies-ZA", value: "ZA" }, { title: "Rental Company", class: "search-filter-company", label: "Silvercar", id: "filter-companies-SC", value: "SC" }, { title: "Rental Company", class: "search-filter-company", label: "Sixt", id: "filter-companies-SX", value: "SX" }, { title: "Rental Company", class: "search-filter-company", label: "Thrifty", id: "filter-companies-ZT", value: "ZT" }, { title: "Car Type", class: "search-filter-type", label: "Commercial", id: "filter-types-commercial", value: "SKAR" }, { title: "Rental Company", class: "search-filter-company", label: "Routes", id: "filter-companies-RO", value: "RO" }, { title: "Car Type", class: "search-filter-type", label: "Electric", id: "filter-types-electric", value: "ICAE" }, { title: "Car Type", class: "search-filter-type", label: "Medium Cars", id: "filter-types-medium", value: "FCAR" }, { title: "Car Type", class: "search-filter-type", label: "Small Cars", id: "filter-types-small", value: "CCAR" }],
    car_types_order = new Map(['Small Cars', 'Medium Cars', 'Large Cars', 'SUVs & Crossovers', 'Vans', 'Luxury', 'Convertibles', 'Sports', 'Wagons', 'Hybrids', 'Electric', 'Pickup Trucks', 'Off-Road Vehicles', 'RVs', 'Commercial', 'Specialty'].map((s, i) => [s, i+1]));

data.sort((a, b) => {
  const aSearchMap = a.class === "search-filter-type";
  const bSearchMap = b.class === "search-filter-type";

  if (aSearchMap !== bSearchMap) { // Group by ordering method
     return aSearchMap ? 1 : -1;
  }

  if (aSearchMap) { // They both need to search the map
     return (car_types_order.get(a.label) || 0) - (car_types_order.get(b.label) || 0);
  }

  return a.label.localeCompare(b.label); // Order them lexically
});

console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 1

Nina Scholz
Nina Scholz

Reputation: 386604

You could first check if 'search-filter-type' is given and sort this items to botton, if both given, the sort by index and if same, then sort by alphabet.

For sorting 'search-filter-type' to top, you could swap a and b in this line:

(a.class === "search-filter-type") - (b.class === "search-filter-type")

var data = [{ title: "Rental Company", class: "search-filter-company", label: "Hertz", id: "filter-companies-HZ", value: "HZ" }, { title: "Rental Company", class: "search-filter-company", label: "National", id: "filter-companies-NA", value: "NA" }, { title: "Rental Company", class: "search-filter-company", label: "NextCar", id: "filter-companies-NC", value: "NC" }, { title: "Rental Company", class: "search-filter-company", label: "Payless", id: "filter-companies-ZA", value: "ZA" }, { title: "Rental Company", class: "search-filter-company", label: "Silvercar", id: "filter-companies-SC", value: "SC" }, { title: "Rental Company", class: "search-filter-company", label: "Sixt", id: "filter-companies-SX", value: "SX" }, { title: "Rental Company", class: "search-filter-company", label: "Thrifty", id: "filter-companies-ZT", value: "ZT" }, { title: "Car Type", class: "search-filter-type", label: "Commercial", id: "filter-types-commercial", value: "SKAR" }, { title: "Rental Company", class: "search-filter-company", label: "Routes", id: "filter-companies-RO", value: "RO" }, { title: "Car Type", class: "search-filter-type", label: "Electric", id: "filter-types-electric", value: "ICAE" }, { title: "Car Type", class: "search-filter-type", label: "Medium Cars", id: "filter-types-medium", value: "FCAR" }, { title: "Car Type", class: "search-filter-type", label: "Small Cars", id: "filter-types-small", value: "CCAR" }],
    car_types_order = ['Small Cars', 'Medium Cars', 'Large Cars', 'SUVs & Crossovers', 'Vans', 'Luxury', 'Convertibles', 'Sports', 'Wagons', 'Hybrids', 'Electric', 'Pickup Trucks', 'Off-Road Vehicles', 'RVs', 'Commercial', 'Specialty'];

data.sort((a, b) =>
    (a.class === "search-filter-type") - (b.class === "search-filter-type")
        || car_types_order.indexOf(a.label) - car_types_order.indexOf(b.label)
        || a.label.localeCompare(b.label)
);

console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Some more advanced techniques:

  • Use an object for accessing the order of car types.

  • Take a default value for not found types. This requires not to use a falsy value, like zero, for known types.

var data = [{ title: "Rental Company", class: "search-filter-company", label: "Hertz", id: "filter-companies-HZ", value: "HZ" }, { title: "Rental Company", class: "search-filter-company", label: "National", id: "filter-companies-NA", value: "NA" }, { title: "Rental Company", class: "search-filter-company", label: "NextCar", id: "filter-companies-NC", value: "NC" }, { title: "Rental Company", class: "search-filter-company", label: "Payless", id: "filter-companies-ZA", value: "ZA" }, { title: "Rental Company", class: "search-filter-company", label: "Silvercar", id: "filter-companies-SC", value: "SC" }, { title: "Rental Company", class: "search-filter-company", label: "Sixt", id: "filter-companies-SX", value: "SX" }, { title: "Rental Company", class: "search-filter-company", label: "Thrifty", id: "filter-companies-ZT", value: "ZT" }, { title: "Car Type", class: "search-filter-type", label: "Commercial", id: "filter-types-commercial", value: "SKAR" }, { title: "Rental Company", class: "search-filter-company", label: "Routes", id: "filter-companies-RO", value: "RO" }, { title: "Car Type", class: "search-filter-type", label: "Electric", id: "filter-types-electric", value: "ICAE" }, { title: "Car Type", class: "search-filter-type", label: "Medium Cars", id: "filter-types-medium", value: "FCAR" }, { title: "Car Type", class: "search-filter-type", label: "Small Cars", id: "filter-types-small", value: "CCAR" }],
    car_types_order = ['Small Cars', 'Medium Cars', 'Large Cars', 'SUVs & Crossovers', 'Vans', 'Luxury', 'Convertibles', 'Sports', 'Wagons', 'Hybrids', 'Electric', 'Pickup Trucks', 'Off-Road Vehicles', 'RVs', 'Commercial', 'Specialty'],
    order = Object.assign({ default: 0 }, ...car_types_order.map((k, v) => ({ [k]: v + 1 })));

data.sort((a, b) =>
    (a.class === "search-filter-type") - (b.class === "search-filter-type")
        || (order[a.label] || order.default) - (order[b.label] || order.default)
        || a.label.localeCompare(b.label)
);

console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 1

Related Questions