Сравните массив объектов JavaScript, чтобы получить минимум / максимум


99

У меня есть массив объектов, и я хочу сравнить эти объекты по определенному свойству объекта. Вот мой массив:

var myArray = [
    {"ID": 1, "Cost": 200},
    {"ID": 2, "Cost": 1000},
    {"ID": 3, "Cost": 50},
    {"ID": 4, "Cost": 500}
]

Я хотел бы сосредоточиться на «стоимости» конкретно и получить минимальное и максимальное значение. Я понимаю, что могу просто получить значения стоимости и поместить их в массив javascript, а затем запустить Fast JavaScript Max / Min .

Однако есть ли более простой способ сделать это, пропустив шаг массива посередине и напрямую отключив свойства объекта (в данном случае «Стоимость»)?

Ответы:


55

Самый быстрый способ в этом случае - это перебрать все элементы и сравнить его с самым высоким / самым низким значением на данный момент.

(Создание массива и вызов методов массива для этой простой операции излишни).

 // There's no real number bigger than plus Infinity
var lowest = Number.POSITIVE_INFINITY;
var highest = Number.NEGATIVE_INFINITY;
var tmp;
for (var i=myArray.length-1; i>=0; i--) {
    tmp = myArray[i].Cost;
    if (tmp < lowest) lowest = tmp;
    if (tmp > highest) highest = tmp;
}
console.log(highest, lowest);

Это имеет смысл, я застрял в мысли о сравнении данных внутри массива друг с другом вместо внешнего высокого / низкого числа.
firedrawndagger

1
Единственное, что я бы изменил, это установка самого низкого и максимального значения, немного избыточно. Я бы предпочел зациклить на один раз меньше и установить, lowest=highest=myArray[0]а затем начать цикл с 1.
Дж. Холмс

1
@ 32bitkid Хороший момент. должно быть myArray[0].Cost, хотя. Но, если первого элемента нет, будет выдана ошибка. Итак, необходима дополнительная проверка, которая, возможно, отменит небольшой прирост производительности.
Rob W

1
@Wilt Да, поддерживайте другую переменную, которая будет обновляться, когда вы найдете наименьшее значение, то есть var lowestObject; for (...)иif (tmp < lowest) { lowestObject = myArray[i]; lowest = tmp; }
Роб В.

1
Этот ответ очень старый, до выхода ECMAScript 2015 (ES6). Это было правильно в то время, но сейчас этот ответ - лучший вариант.
Эрик Петручелли,

171

Уменьшение хорошо подходит для таких вещей: выполнять агрегированные операции (например, min, max, avg и т. Д.) Над массивом объектов и возвращать один результат:

myArray.reduce(function(prev, curr) {
    return prev.Cost < curr.Cost ? prev : curr;
});

... или вы можете определить эту внутреннюю функцию с синтаксисом функции ES6:

(prev, curr) => prev.Cost < curr.Cost ? prev : curr

Если вы хотите быть милым, вы можете прикрепить это к массиву:

Array.prototype.hasMin = function(attrib) {
    return (this.length && this.reduce(function(prev, curr){ 
        return prev[attrib] < curr[attrib] ? prev : curr; 
    })) || null;
 }

Теперь вы можете просто сказать:

myArray.hasMin('ID')  // result:  {"ID": 1, "Cost": 200}
myArray.hasMin('Cost')    // result: {"ID": 3, "Cost": 50}
myEmptyArray.hasMin('ID')   // result: null

Обратите внимание, что если вы собираетесь использовать это, он не имеет полной проверки для каждой ситуации. Если вы передадите массив примитивных типов, это не удастся. Если вы проверите свойство, которое не существует, или если не все объекты содержат это свойство, вы получите последний элемент. Эта версия немного более громоздкая, но имеет следующие проверки:

Array.prototype.hasMin = function(attrib) {
    const checker = (o, i) => typeof(o) === 'object' && o[i]
    return (this.length && this.reduce(function(prev, curr){
        const prevOk = checker(prev, attrib);
        const currOk = checker(curr, attrib);
        if (!prevOk && !currOk) return {};
        if (!prevOk) return curr;
        if (!currOk) return prev;
        return prev[attrib] < curr[attrib] ? prev : curr; 
    })) || null;
 }

16
На мой взгляд, лучший ответ. Он не модифицирует массив и его гораздо короче, чем ответ, который гласит: «Создание массива, вызов методов массива - излишек для этой простой операции»
Джулиан Манн

Это лучший ответ для производительности больших наборов данных (30+ столбцов / 100 тыс. Строк).
cerd

2
Просто интересно, когда сокращение проверяет, что первый элемент массива не prev.Costбудет неопределенным? Или он запускается как 0?
GrayedFox

1
Хороший вопрос @Saheb. Я только что отредактировал, поэтому в этом случае он вернет null.
Тристан Рид

1
Я также добавил несколько проверок для других входных данных, которые могут вызвать проблемы, например, не объекты, или если это свойство отсутствует у некоторых объектов. В конечном итоге, я думаю, в большинстве случаев это становится немного жестким.
Тристан Рид

27

Используйте sort, если вас не волнует модифицируемый массив.

myArray.sort(function (a, b) {
    return a.Cost - b.Cost
})

var min = myArray[0],
    max = myArray[myArray.length - 1]

3
полная сортировка - не самый быстрый способ найти минимум / максимум, но я думаю, это сработает.
Дж. Холмс

4
Просто имейте в виду, что это изменится myArray, чего нельзя ожидать.
ziesemer

Сортировка массива происходит медленнее, чем его обход. Сложность сортировки:, O(nlog(n))обход массива:O(n)
Mourad Qqch

20

Используйте Mathфункции и выбирайте нужные вам значения map.

Вот jsbin:

https://jsbin.com/necosu/1/edit?js,console

var myArray = [{
    "ID": 1,
    "Cost": 200
  }, {
    "ID": 2,
    "Cost": 1000
  }, {
    "ID": 3,
    "Cost": 50
  }, {
    "ID": 4,
    "Cost": 500
  }],

  min = Math.min.apply(null, myArray.map(function(item) {
    return item.Cost;
  })),
  max = Math.max.apply(null, myArray.map(function(item) {
    return item.Cost;
  }));

console.log('min', min);//50
console.log('max', max);//1000

ОБНОВИТЬ:

Если вы хотите использовать ES6:

var min = Math.min.apply(null, myArray.map(item => item.Cost)),
    max = Math.max.apply(null, myArray.map(item => item.Cost));

18
В ES6 с использованием Spread Operator нам больше не требуется apply. Проще говоря - Math.min(...myArray.map(o => o.Cost))найти минимум и Math.max(...myArray.map(o => o.Cost))найти максимум.
Нитин

13

Я думаю, что ответ Роба В. действительно правильный (+1), но просто для удовольствия: если вы хотите быть «умным», вы можете сделать что-то вроде этого:

var myArray = 
[
    {"ID": 1, "Cost": 200},
    {"ID": 2, "Cost": 1000},
    {"ID": 3, "Cost": 50},
    {"ID": 4, "Cost": 500}
]

function finder(cmp, arr, attr) {
    var val = arr[0][attr];
    for(var i=1;i<arr.length;i++) {
        val = cmp(val, arr[i][attr])
    }
    return val;
}

alert(finder(Math.max, myArray, "Cost"));
alert(finder(Math.min, myArray, "Cost"));

или если у вас есть глубоко вложенная структура, вы могли бы стать немного более функциональным и сделать следующее:

var myArray = 
[
    {"ID": 1, "Cost": { "Wholesale":200, Retail: 250 }},
    {"ID": 2, "Cost": { "Wholesale":1000, Retail: 1010 }},
    {"ID": 3, "Cost": { "Wholesale":50, Retail: 300 }},
    {"ID": 4, "Cost": { "Wholesale":500, Retail: 1050 }}
]

function finder(cmp, arr, getter) {
    var val = getter(arr[0]);
    for(var i=1;i<arr.length;i++) {
        val = cmp(val, getter(arr[i]))
    }
    return val;
}

alert(finder(Math.max, myArray, function(x) { return x.Cost.Wholesale; }));
alert(finder(Math.min, myArray, function(x) { return x.Cost.Retail; }));

Их можно легко преобразовать в более полезные / конкретные формы.


4
Я протестировал наши решения: jsperf.com/comparison-of-numbers . После оптимизации кода (см. Тест) оба метода работают примерно одинаково. Без оптимизации мой метод в 14 раз быстрее.
Rob W

2
@RoBW о, я бы полностью ожидал, что ваша версия будет намного быстрее, я просто предлагал альтернативную архитектурную реализацию. :)
Дж. Холмс

@ 32bitkid Я ожидал того же, но, как ни странно, метод почти такой же быстрый (после оптимизации), как видно из тестового примера 3 теста.
Rob W

1
@RobW Согласен, я этого не ожидал. Я заинтригован. :) Это показывает, что вы всегда должны оценивать, а не предполагать.
Дж. Холмс,

@RobW Чтобы внести ясность, я думаю, что с большим количеством результатов в браузере ваша реализация будет постоянно превосходить неоптимизированные и оптимизированные версии.
Дж. Холмс,


5

Попробуйте ( aэто массив, fэто поле для сравнения)

let max= (a,f)=> a.reduce((m,x)=> m[f]>x[f] ? m:x);
let min= (a,f)=> a.reduce((m,x)=> m[f]<x[f] ? m:x);


2
Мне нравится этот ответ, такой компактный и простой в использовании.
Дин

4

Используя Array.prototype.reduce () , вы можете подключить функции компаратора для определения минимального, максимального и т. Д. Элемента в массиве.

var items = [
  { name : 'Apple',  count : 3  },
  { name : 'Banana', count : 10 },
  { name : 'Orange', count : 2  },
  { name : 'Mango',  count : 8  }
];

function findBy(arr, key, comparatorFn) {
  return arr.reduce(function(prev, curr, index, arr) { 
    return comparatorFn.call(arr, prev[key], curr[key]) ? prev : curr; 
  });
}

function minComp(prev, curr) {
  return prev < curr;
}

function maxComp(prev, curr) {
  return prev > curr;
}

document.body.innerHTML  = 'Min: ' + findBy(items, 'count', minComp).name + '<br />';
document.body.innerHTML += 'Max: ' + findBy(items, 'count', maxComp).name;


4

Использование Math.minи Math.max:

var myArray = [
    { id: 1, cost: 200},
    { id: 2, cost: 1000},
    { id: 3, cost: 50},
    { id: 4, cost: 500}
]


var min = Math.min(...myArray.map(item => item.cost));
var max = Math.max(...myArray.map(item => item.cost));

console.log("min: " + min);
console.log("max: " + max);


2

Это более лучшее решение

    var myArray = [
    {"ID": 1, "Cost": 200},
    {"ID": 2, "Cost": 1000},
    {"ID": 3, "Cost": 50},
    {"ID": 4, "Cost": 500}
    ]
    var lowestNumber = myArray[0].Cost;
    var highestNumber = myArray[0].Cost;

    myArray.forEach(function (keyValue, index, myArray) {
      if(index > 0) {
        if(keyValue.Cost < lowestNumber){
          lowestNumber = keyValue.Cost;
        }
        if(keyValue.Cost > highestNumber) {
          highestNumber = keyValue.Cost;
        }
      }
    });
    console.log('lowest number' , lowestNumber);
    console.log('highest Number' , highestNumber);

2

Добавив к ответу Тристана Рейда (+ используя es6), вы можете создать функцию, которая принимает обратный вызов, который будет содержать оператор, который вы хотите применить к prevи curr:

const compare = (arr, key, callback) => arr.reduce((prev, curr) =>
    (callback(prev[key], curr[key]) ? prev : curr), {})[key];

    // remove `[key]` to return the whole object

Тогда вы могли бы просто вызвать это, используя:

const costMin = compare(myArray, 'Cost', (a, b) => a < b);
const costMax = compare(myArray, 'Cost', (a, b) => a > b);

2

Этого можно добиться с помощью lodash minByи maxByfunctions.

Lodash minByи maxByдокументация

_.minBy(array, [iteratee=_.identity])

_.maxBy(array, [iteratee=_.identity])

Эти методы принимают итерацию, которая вызывается для каждого элемента в массиве, чтобы сгенерировать критерий, по которому оценивается значение. Итерация вызывается с одним аргументом: (значение).

Решение

var myArray = [
    {"ID": 1, "Cost": 200},
    {"ID": 2, "Cost": 1000},
    {"ID": 3, "Cost": 50},
    {"ID": 4, "Cost": 500}
]

const minimumCostItem = _.minBy(myArray, "Cost");

console.log("Minimum cost item: ", minimumCostItem);

// Getting the maximum using a functional iteratee
const maximumCostItem = _.maxBy(myArray, function(entry) {
  return entry["Cost"];
});

console.log("Maximum cost item: ", maximumCostItem);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>


0

мы можем решить проблему двумя способами, оба метода уже объяснены выше, но тест производительности отсутствовал, поэтому выполнение этого

1, собственный способ java-скрипта
2, сначала объект сортировки, затем легко получить минимальный максимум из отсортированного объекта

Я также тестирую производительность обоих подходов к буксировке

вы также можете запустить и проверить производительность ... Удачного кодирования (:

//first approach 

var myArray = [
    {"ID": 1, "Cost": 200},
    {"ID": 2, "Cost": 1000},
    {"ID": 3, "Cost": 50},
    {"ID": 4, "Cost": 500}
]

var t1 = performance.now();;

let max=Math.max.apply(Math, myArray.map(i=>i.Cost))

let min=Math.min.apply(Math, myArray.map(i=>i.Cost))

var t2   = performance.now();;

console.log("native fuction took " + (t2 - t1) + " milliseconds.");

console.log("max Val:"+max)
console.log("min Val:"+min)

//  Second approach:


function sortFunc (a, b) {
    return a.Cost - b.Cost
} 

var s1 = performance.now();;
sortedArray=myArray.sort(sortFunc)


var minBySortArray = sortedArray[0],
    maxBySortArray = sortedArray[myArray.length - 1]
    
var s2   = performance.now();;
 console.log("sort funciton took  " + (s2 - s1) + " milliseconds.");  
console.log("max ValBySortArray :"+max)
console.log("min Val BySortArray:"+min)


0

Для краткого современного решения можно выполнить reduceоперацию над массивом, отслеживая текущие минимальные и максимальные значения, поэтому массив повторяется только один раз (что оптимально).

let [min, max] = myArray.reduce(([prevMin,prevMax], {Cost})=>
   [Math.min(prevMin, Cost), Math.max(prevMax, Cost)], [Infinity, -Infinity]);

Демо:


-2

Еще один, похожий на ответ Кеннебека, но все в одной строке:

maxsort = myArray.slice(0).sort(function (a, b) { return b.ID - a.ID })[0].ID; 

-2

Вместо этого вы можете использовать встроенный объект Array, чтобы использовать Math.max / Math.min:

var arr = [1,4,2,6,88,22,344];

var max = Math.max.apply(Math, arr);// return 344
var min = Math.min.apply(Math, arr);// return 1
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.