2

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"}
4
  • 2
    Since you are doing a custom sort, IMO, I think what you have is fine. You could break down both ways of sorting into functions and then it would be re-usable, which may be a way to improve on your design. But the sorting itself, needs to still implement the logic you have. Commented May 1, 2018 at 18:00
  • do you want alphabetically ordered at bottom or at top? Commented May 1, 2018 at 18:02
  • It doesn't necessarily need to be ordered by class. The labels themselves need to be ordered though (for search-filter-type) it should follow car_types_order. Whereas for search-filter-company it should be alphabetical. Commented May 1, 2018 at 18:04
  • Don't look for "clever" hacks to shorten code, especially when they involve unintuitive operations requiring unusual, implicit type coercions. Shorter does not mean clearer. You code should describe clearly what you're intending. Commented May 1, 2018 at 19:48

2 Answers 2

1

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; }

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

9 Comments

There's a potential flaw in the logic. It will work reliably only as long as objects that do not have the "search-filter-type" class are also guaranteed to not have non-matching .labels in that array. If they do, then they'll be sorted by that array order instead of lexically.
...There's also a negative performance implication to performing all those unnecessary .indexOf() operations.
@CrazyTrain, the data structure is given as is. the indexOf operation could be replaced by an object with order types as keys and indices as values, as here mentioned, or here.
"the data structure is given as is" How do you know? Did you post this question? Is that a strawman account?
@CrazyTrain, every object contains the same properties in the question. i do not assume that there are some properties in some objects missing. this is not part of the question and should be mentioned, if so.
|
1

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; }

2 Comments

you might review this part: car_types_order.get(a.label) || 0 - car_types_order.get(b.label) || 0, which is basically car_types_order.get(a.label) || (0 - car_types_order.get(b.label)) || 0. anyway, be nice.
@NinaScholz: Yep, you're right. I didn't consider precedence properly. Thanks for pointing out the error in my code. I updated the answer to fix the problem.

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.