Porównaj tablicę obiektów JavaScript, aby uzyskać min./maks

99

Mam tablicę obiektów i chcę porównać te obiekty na określonej właściwości obiektu. Oto moja tablica:

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

Chciałbym skupić się konkretnie na „koszcie” i uzyskać minimalną i maksymalną wartość. Zdaję sobie sprawę, że mogę po prostu pobrać wartości kosztów i przenieść je do tablicy javascript, a następnie uruchomić Fast JavaScript Max / Min .

Czy jest jednak prostszy sposób na to, pomijając krok tablicy w środku i bezpośrednio wyłączając właściwości obiektów (w tym przypadku „Koszt”)?

firedrawndagger
źródło

Odpowiedzi:

55

W tym przypadku najszybszym sposobem jest zapętlenie wszystkich elementów i porównanie ich z jak dotąd najwyższą / najniższą wartością.

(Tworzenie tablicy, wywoływanie metod tablicowych jest przesadą w przypadku tej prostej operacji).

 // 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);
Rob W
źródło
Ma to sens, utknąłem z myśleniem o porównywaniu danych wewnątrz tablicy ze sobą zamiast zewnętrznej wysokiej / niskiej liczby.
firedrawndagger
1
Jedyne, co bym zmienił, to ustawienie najniższego i najwyższego, jest trochę zbędne. Wolałbym zapętlić o jeden raz mniej i ustawić, lowest=highest=myArray[0]a następnie rozpocząć pętlę od 1.
J. Holmes
1
@ 32bitkid Słuszna uwaga. powinno być myArray[0].Cost. Ale jeśli nie ma pierwszego elementu, zostanie zgłoszony błąd. Potrzebna jest więc dodatkowa kontrola, prawdopodobnie cofająca niewielki wzrost wydajności.
Rob W,
1
@Wilt Tak, utrzymanie innej zmiennej, która zostanie zaktualizowany, gdy znalazłem najniższą wartość, IE var lowestObject; for (...)iif (tmp < lowest) { lowestObject = myArray[i]; lowest = tmp; }
Rob W
1
Ta odpowiedź jest bardzo stara, zanim wyszedł ECMAScript 2015 (ES6). Miałem wtedy rację, ale teraz ta odpowiedź jest lepszym rozwiązaniem.
Erick Petrucelli
171

Redukcja jest dobra w przypadku takich rzeczy: do wykonywania zagregowanych operacji (takich jak min, max, avg itp.) Na tablicy obiektów i zwracania pojedynczego wyniku:

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

... lub możesz zdefiniować tę funkcję wewnętrzną za pomocą składni funkcji ES6:

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

Jeśli chcesz być uroczy, możesz dołączyć to do tablicy:

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

Teraz możesz po prostu powiedzieć:

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

Pamiętaj, że jeśli zamierzasz tego użyć, nie ma pełnej kontroli dla każdej sytuacji. Jeśli przekażesz tablicę typów pierwotnych, zakończy się niepowodzeniem. Jeśli sprawdzisz właściwość, która nie istnieje, lub jeśli nie wszystkie obiekty zawierają tę właściwość, otrzymasz ostatni element. Ta wersja jest trochę bardziej nieporęczna, ale ma następujące kontrole:

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;
 }
Tristan Reid
źródło
16
Moim zdaniem najlepsza odpowiedź. To nie zmienia tablicę i jej znacznie bardziej zwięzły niż odpowiedzi, który mówi „Tworzenie tablicy, wywoływanie metod tablicy jest przesadą dla tej prostej operacji”
Julian Mann
Jest to absolutnie najlepsza odpowiedź pod względem wydajności dużych zestawów danych (ponad 30 kolumn / 100 tys. Wierszy).
cerd
2
Zastanawiasz się tylko, czy kiedy redukuje sprawdzenia, pierwszy element tablicy nie prev.Costbędzie niezdefiniowany? Czy inicjuje jako 0?
GrayedFox
1
Słuszna uwaga @Saheb. Właśnie edytowałem, więc w takim przypadku zwróci wartość null.
Tristan Reid
1
Dodałem również kilka sprawdzeń pod kątem innych danych wejściowych, które mogą powodować problemy, takie jak obiekty niebędące obiektami lub brak tej właściwości w niektórych obiektach. Wydaje mi się, że ostatecznie w większości przypadków robi się ciężko.
Tristan Reid
27

Użyj sort, jeśli nie obchodzi Cię modyfikacja tablicy.

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

var min = myArray[0],
    max = myArray[myArray.length - 1]
katspaugh
źródło
3
pełne sortowanie nie jest najszybszym sposobem na znalezienie min / maks, ale myślę, że zadziała.
J. Holmes,
4
Pamiętaj tylko, że to się zmieni myArray, czego nie można się spodziewać.
ziesemer
Sortowanie tablicy jest wolniejsze niż przechodzenie przez nią. Sortuj złożoność O(nlog(n))O(n)
:,
20

Użyj Mathfunkcji i wyodrębnij żądane wartości map.

Oto 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

AKTUALIZACJA:

Jeśli chcesz używać ES6:

var min = Math.min.apply(null, myArray.map(item => item.Cost)),
    max = Math.max.apply(null, myArray.map(item => item.Cost));
Rupert
źródło
17
W ES6 korzystamy z operatora Spread, już nie potrzebujemy apply. Po prostu powiedz - Math.min(...myArray.map(o => o.Cost))aby znaleźć minimum i Math.max(...myArray.map(o => o.Cost))znaleźć maksimum.
Nitin
13

Myślę, że odpowiedź Rob W za to bardzo słuszna (+1), ale tylko dla zabawy: jeśli chcesz być „mądry”, to mógłby zrobić coś takiego:

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"));

lub jeśli masz głęboko zagnieżdżoną strukturę, możesz uzyskać trochę bardziej funkcjonalną i wykonać następujące czynności:

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

Można je łatwo curry w bardziej przydatne / specyficzne formy.

J. Holmes
źródło
4
Przeprowadziłem testy porównawcze naszych rozwiązań: jsperf.com/comparison-of-numbers . Po optymalizacji kodu (patrz test porównawczy) wydajność obu metod jest podobna. Bez optymalizacji moja metoda jest 14x szybsza.
Rob W,
2
@RoBW Och, całkowicie spodziewałbym się, że twoja wersja będzie znacznie szybsza, właśnie dostarczałem alternatywną implementację architektoniczną. :)
J. Holmes
@ 32bitkid Spodziewałem się tego samego, ale co zaskakujące, metoda jest prawie tak szybka (po optymalizacji), jak widać w przypadku testowym 3 testu porównawczego.
Rob W,
1
@RobW Zgadzam się, nie spodziewałbym się tego. Jestem zaintrygowany. :) Ma na celu pokazanie, że zawsze powinieneś raczej testować niż zakładać.
J. Holmes,
@RobW Żeby było jasne, myślę, że przy większej liczbie wyników przeglądarki Twoja implementacja konsekwentnie pokonałaby niezoptymalizowane i zoptymalizowane wersje.
J. Holmes,
6

dla Max

Math.max.apply(Math, myArray.map(a => a.Cost));

na min

Math.min.apply(Math, myArray.map(a => a.Cost));
Issa Lafi
źródło
5

Try ( ato tablica, fto pole do porównania)

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

Kamil Kiełczewski
źródło
2
Uwielbiam tę odpowiedź, tak kompaktową i łatwą w użyciu.
Dziekan
4

Korzystając z Array.prototype.reduce () , możesz podłączyć funkcje komparatora w celu określenia wartości min, max itd. W tablicy.

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;

Panie Polywhirl
źródło
4

Korzystanie Math.mini 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);

JuZDePeche
źródło
2

To lepsze rozwiązanie

    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);
Deepak Sisodiya
źródło
2

Dodając do odpowiedzi Tristana Reida (+ używając es6), możesz stworzyć funkcję akceptującą wywołanie zwrotne, która będzie zawierała operator, który chcesz zastosować do previ 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

Następnie możesz po prostu zadzwonić za pomocą:

const costMin = compare(myArray, 'Cost', (a, b) => a < b);
const costMax = compare(myArray, 'Cost', (a, b) => a > b);
James Moran
źródło
2

Można to osiągnąć za pomocą lodash minByi maxByfunkcji.

Lodash minByi maxBydokumentacja

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

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

Te metody akceptują iterację, która jest wywoływana dla każdego elementu tablicy w celu wygenerowania kryterium, według którego wartość jest uszeregowana. Iteracja jest wywoływana z jednym argumentem: (wartość).

Rozwiązanie

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>

Trent
źródło
0

możemy rozwiązać problem dwoma podejściami. Obie metody zostały już wyjaśnione powyżej, ale brakowało testu wydajności, więc go ukończył

1, natywny sposób skryptu java
2, najpierw sortuj obiekt, a następnie łatwo uzyskasz min max z posortowanego obiektu

Testuję również wydajność obu metod holowania

możesz także uruchomić i przetestować wydajność ... Miłego kodowania (:

//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)

Jadli
źródło
0

Aby uzyskać zwięzłe, nowoczesne rozwiązanie, można wykonać reduceoperację na tablicy, śledząc bieżące wartości minimalne i maksymalne, więc tablica jest iterowana tylko raz (co jest optymalne).

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

Próbny:

odrobina
źródło
-2

Kolejny, podobny do odpowiedzi Kennebeca, ale wszystko w jednym wierszu:

maxsort = myArray.slice(0).sort(function (a, b) { return b.ID - a.ID })[0].ID; 
Ben
źródło
-2

Możesz użyć wbudowanego obiektu Array, aby zamiast tego użyć 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
kod źródłowy
źródło