简体   繁体   中英

How to sort a JavaScript array by more nested objects in arrays and grab the top ###?

Here is a dummy example. I have an array of objects:

var cars = [
  { 
    name: "Hyundai",
    plans: [
      {
        name: "Something",
        add-ons: [
            {
               cost: 100
            },
            {
               cost: 75
            }
        ] 
      }, { ... }
    ]
  },
  { 
    name: "Jeep",
    plans: [
      {
        name: "Something",
        add-ons: [
            {
               cost: 50
            },
            {
               cost: 75
            }
        ] 
      }, { ... }
    ]
  },
  { 
    name: "Buick",
    plans: [
      {
        name: "Something",
        add-ons: [
            {
               cost: 35
            },
            {
               cost: 50
            }
        ] 
      }, {...}
    ]
  }
]

What I'm trying to do is find the top 2 cars that have the cheapest add-on and reference them via another variable.

Like this:

var top2 = findTopTwo(cars);

findTopTwo(arr) {
  return arr.sort(function(a, b) {
    // My trouble spot
  }).slice(0, 2);
}

With my simple example, the result for top2 would be:

  1. Buick ( cheapest add-on was $35, the value used to compare against )
  2. Jeep ( cheapest add-on was $50, value used to compare against )

So what I would do is feed all of them into an array and then sort it on the cost. That would be my naive approach. The more optimal solution would be to only store 2 objects at a given time instead of a list of all items.

The naive approach would be as simple as:

var items = [];
for ( var i in cars ){
  var car = cars[i];
  for (var i in car["plans"]){
    for (var j = 0; j < car["plans"][i]["add-ons"]){
      items.push({"name": car.name, "cost": car["plans"][i]["add-ons"][j]["cost"]});
    }
  }
}
return items.sort(function(a,b){ return a.cost < b.cost }).slice(0,2);

That will return a list of 2 objects, the object contains the name of the car and the cost. The more effecient thing would be to do something like this:

var biggest = function(arr){
  if (arr.length < 2 ) return -1;
  return arr[0].cost > arr[1].cost ? 0 : 1;
}
var items = [];
for ( var i in cars ){
  var car = cars[i];
  for (var i in car["plans"]){
    for (var j = 0; j < car["plans"][i]["add-ons"]){
      var obj = {"name": car.name, "cost": car["plans"][i]["add-ons"][j]["cost"]};
    }

    var index = biggest(items)
    if (index < 0){
      items.push(obj);
    }else{
      if (items[index].cost > obj.cost)
        items[index] = obj;
    }

  }
}
return items;

this more interesting design will push the first 2 into the list, but then it will find the biggest of the 2 costs and then checks to see if the new one is smaller than it. If the new one is smaller than item[index] it will be replaced.

This will never have the array larger than 2 so it takes up less memory

Another approach. By this approach your original data will not be sorted or modified.

 var cars=[{name:"Hyundai",plans:[{name:"Something","add-ons":[{cost:100},{cost:75}]}]}, {name:"Jeep",plans:[{name:"Something","add-ons":[{cost:50},{cost:75}]}]}, {name:"Buick",plans:[{name:"Something","add-ons":[{cost:35},{cost:50}]}]}]; function findTopTwo(cars) { return cars.map( car => car.plans.reduce( (prevPlan, plan) => plan['add-ons'].reduce((prevAddOn, addOn) => { if (prevAddOn.cost > addOn.cost) { prevAddOn.cost = addOn.cost; } return prevAddOn; }, prevPlan), { cost: Number.MAX_VALUE, name: car.name }) ) .sort((a, b) => a.cost - b.cost) .slice(0, 2) .map(item => item.name); } console.log(findTopTwo(cars)); 

I had to play around with the object, but here is the gist of it -

 var cars = [{ name: "Hyundai", plans: { addons: [{ cost: 100 }, { cost: 75 }] } }, { name: "Jeep", plans: { addons: [{ cost: 50 }, { cost: 75 }] } }, { name: "Buick", plans: { addons: [{ cost: 35 }, { cost: 50 }] } }]; var top2 = findTopTwo(cars); console.log(top2); function findTopTwo(arr) { return arr.sort(function (a, b) { // this map outputs array of costs: [35, 40] // and Math.min takes the lowest value of each var a_max_cost = Math.min.apply(null, a.plans.addons.map(function(i){i.cost})), b_max_cost = Math.min.apply(null, b.plans.addons.map(function(i){i.cost})); return a_max_cost - b_max_cost; }) .slice(0, 2); } 

Basically, you need to return ab in the sort function, where a and b are the lowest addon values. So I calculated the max of both cars on comparison, and used those values to decide which goes where.

Edit: I see you've updated the JS object, the answer should be similar to min, you will only need to figure out which plan to use for a and b . You can do so similar to my use of the Math.max function

One simple way of doing it is to first sort the addons by price (if you don't mind the side effect that addons then remain sorted by price).

function findTopTwo(arr) {
  arr.forEach(function (elem) {
    elem.plans.addons = elem.plans.addons.sort(function (a, b) {
      return a.cost > b.cost;
    });
  });
  return arr.sort(function(a, b) {
    return a.plans.addons[0].cost > b.plans.addons[0].cost;
  }).slice(0, 2);
}

jsbin example

Using @casraf's data:

const sortedCars = cars.map(car => {
    car.plans.addons.sort((a, b) =>  a.cost - b.cost);
    return car;
}).sort((a, b) => {
    return a.plans.addons[0].cost - b.plans.addons[0].cost;
});

Line 2 sorts each cars' addons array from low to high. Line 5 sorts the cars from low to high based on the first index of their respective addons property.

If the ES6 syntax is confusing, here's a translation to ES5

I suggest to use sorting with map , then take the top 2 entries and get the data from cars .

 var cars = [{ name: "Hyundai", plans: [{ 'add-ons': [{ cost: 100 }, { cost: 75 }] }] }, { name: "Jeep", plans: [{ 'add-ons': [{ cost: 50 }, { cost: 75 }] }] }, { name: "Buick", plans: [{ 'add-ons': [{ cost: 35 }, { cost: 50 }] }] }], cost = cars. map(function (a, i) { return { index: i, cost: a.plans.reduce(function (r, b) { return Math.min(r, b['add-ons'].reduce(function (s, c) { return Math.min(s, c.cost); }, Infinity)); }, Infinity) }; }). sort(function (a, b) { return a.cost - b.cost; }), top2 = cost.slice(0, 2).map(function (a) { return cars[a.index]; }); console.log(top2); 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM