เปรียบเทียบ JavaScript Array of Objects เพื่อรับ Min / Max


102

ฉันมีอาร์เรย์ของอ็อบเจ็กต์และฉันต้องการเปรียบเทียบอ็อบเจ็กต์เหล่านั้นกับคุณสมบัติอ็อบเจ็กต์เฉพาะ นี่คืออาร์เรย์ของฉัน:

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

ฉันต้องการให้ "ต้นทุน" เป็นศูนย์โดยเฉพาะและรับค่าต่ำสุดและสูงสุด ฉันรู้ฉันเพียงแค่สามารถคว้าค่าค่าใช้จ่ายและผลักดันพวกเขาออกไปในอาร์เรย์ JavaScript และเรียกใช้ด่วน JavaScript สูงสุด

อย่างไรก็ตามมีวิธีที่ง่ายกว่านี้โดยการข้ามขั้นตอนอาร์เรย์ตรงกลางและปิดคุณสมบัติของวัตถุ (ในกรณีนี้คือ "ต้นทุน") โดยตรงหรือไม่?

คำตอบ:


56

วิธีหนึ่งคือการวนซ้ำองค์ประกอบทั้งหมดและเปรียบเทียบกับค่าสูงสุด / ต่ำสุด

(การสร้างอาร์เรย์การเรียกใช้เมธอดอาร์เรย์นั้นเกินความจำเป็นสำหรับการดำเนินการง่ายๆนี้)

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

นี่เป็นเหตุผลที่ฉันคิดเกี่ยวกับการเปรียบเทียบข้อมูลในอาร์เรย์ซึ่งกันและกันแทนที่จะเป็นตัวเลขสูง / ต่ำภายนอก
firerawndagger

1
สิ่งเดียวที่ฉันจะเปลี่ยนคือการตั้งค่าต่ำสุดและสูงสุดนั้นซ้ำซ้อนเล็กน้อย ฉันค่อนข้างจะวนซ้ำน้อยลงและตั้งค่าlowest=highest=myArray[0]จากนั้นเริ่มลูปที่ 1
J. Holmes

1
@ 32bitkid จุดดี. ควรจะเป็นmyArray[0].Costแม้ว่า แต่หากไม่มีองค์ประกอบแรกข้อผิดพลาดจะเกิดขึ้น ดังนั้นจึงจำเป็นต้องมีการตรวจสอบเพิ่มเติมซึ่งอาจเป็นการยกเลิกการเพิ่มประสิทธิภาพเล็กน้อย
Rob W

1
@ Wilt ใช่รักษาตัวแปรอื่นที่ได้รับการอัปเดตเมื่อคุณพบค่าต่ำสุดนั่นคือvar lowestObject; for (...)และif (tmp < lowest) { lowestObject = myArray[i]; lowest = tmp; }
Rob W

1
คำตอบนี้เก่ามากก่อนที่ ECMAScript 2015 (ES6) จะออกไป ถูกในเวลานั้น แต่ตอนนี้คำตอบนั้นเป็นตัวเลือกที่ดีกว่า
Erick Petrucelli

176

การลดเป็นสิ่งที่ดีสำหรับสิ่งนี้: เพื่อดำเนินการรวม (เช่น 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;
 }

17
คำตอบที่ดีที่สุดในความคิดของฉัน มันไม่ได้แก้ไขอาร์เรย์และมีความกระชับมากกว่าคำตอบที่ระบุว่า "การสร้างอาร์เรย์การเรียกใช้เมธอดอาร์เรย์นั้นเกินความจำเป็นสำหรับการดำเนินการง่ายๆนี้"
Julian Mann

นี่คือคำตอบที่ดีที่สุดสำหรับประสิทธิภาพของชุดข้อมูลขนาดใหญ่ (30+ คอลัมน์ / 100,000 แถว)
cerd

2
แค่สงสัยว่าเมื่อลดการตรวจสอบองค์ประกอบแรกของอาร์เรย์จะไม่prev.Costถูกกำหนด? หรือเริ่มต้นเป็น 0?
GrayedFox

1
จุดดี @Saheb. ฉันเพิ่งแก้ไขดังนั้นมันจะคืนค่าว่างในกรณีนั้น
Tristan Reid

1
ฉันยังเพิ่มการตรวจสอบอินพุตอื่น ๆ ที่อาจทำให้เกิดปัญหาเช่นไม่ใช่อ็อบเจ็กต์หรือหากคุณสมบัตินั้นหายไปจากอ็อบเจ็กต์บางอย่าง ในที่สุดฉันคิดว่ามันค่อนข้างหนักไปหน่อยสำหรับกรณีส่วนใหญ่
Tristan Reid

28

ใช้sortถ้าคุณไม่สนใจว่าอาร์เรย์จะถูกแก้ไข

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

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

3
การจัดเรียงแบบเต็มไม่ใช่วิธีที่เร็วที่สุดในการค้นหา min / max แต่ฉันเดาว่ามันจะได้ผล
J. Holmes

4
โปรดทราบว่าสิ่งนี้จะปรับเปลี่ยนmyArrayซึ่งอาจไม่คาดคิด
ziesemer

การเรียงลำดับอาร์เรย์จะช้ากว่าการข้ามผ่าน เรียงลำดับความซับซ้อน: O(nlog(n))ข้ามอาร์เรย์:O(n)
Mourad Qqch

20

ใช้ฟังก์ชั่นและถอนออกค่าที่คุณต้องการด้วยMathmap

นี่คือ 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));

19
ใน ES6 applyใช้ประกอบการแพร่กระจายเราไม่จำเป็นต้อง พูดง่ายๆว่า - Math.min(...myArray.map(o => o.Cost))สำหรับการหาค่าต่ำสุดและ Math.max(...myArray.map(o => o.Cost))หาค่าสูงสุด
Nitin

13

ฉันคิดว่าคำตอบของ Rob Wนั้นถูกต้องจริงๆ (+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
ฉันได้ benchmarked โซลูชั่นของเรา: jsperf.com/comparison-of-numbers หลังจากปรับแต่งโค้ดของคุณให้เหมาะสมแล้ว (ดูเกณฑ์มาตรฐาน) ประสิทธิภาพของทั้งสองวิธีจะใกล้เคียงกัน หากไม่มีการปรับให้เหมาะสมวิธีการของฉันเร็วขึ้น 14 เท่า
Rob W

2
@RoBW โอ้ฉันทั้งหมดคาดว่าจะได้รุ่นของคุณจะเป็นวิธีที่เร็วขึ้นผมก็แค่ให้การดำเนินงานสถาปัตยกรรมอื่น :)
J.Holmes

@ 32bitkid ฉันคาดหวังไว้เหมือนกัน แต่น่าประหลาดใจที่วิธีนี้เกือบจะเร็ว (หลังจากการปรับให้เหมาะสม) ดังที่เห็นในกรณีทดสอบ 3 ของเกณฑ์มาตรฐาน
Rob W

1
@ RobW ฉันเห็นด้วยฉันไม่คาดคิดมาก่อน ฉันรู้สึกทึ่ง :) แสดงให้เห็นว่าคุณควรเปรียบเทียบเสมอมากกว่าที่จะคิด
J. Holmes

@RobW เพื่อความชัดเจนฉันคิดว่าด้วยผลลัพธ์ของเบราว์เซอร์ที่มากขึ้นการใช้งานของคุณจะเอาชนะทั้งเวอร์ชันที่ไม่ได้เพิ่มประสิทธิภาพและปรับให้เหมาะสมอย่างสม่ำเสมอ
J. Holmes

7

ลอง ( 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

เมื่อเพิ่มคำตอบของ Tristan Reid (+ โดยใช้ 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และmaxByฟังก์ชั่น

เอกสารminByและmaxByเอกสารของ Lodash

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

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

วิธีการเหล่านี้ยอมรับ iteratee ซึ่งถูกเรียกใช้สำหรับแต่ละองค์ประกอบในอาร์เรย์เพื่อสร้างเกณฑ์ที่มีการจัดอันดับค่า iteratee ถูกเรียกด้วยอาร์กิวเมนต์เดียว: (value)

วิธีการแก้

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-script ดั้งเดิม
2, จัดเรียงวัตถุก่อนจากนั้นจึงง่ายต่อการรับ min max จาก obj ที่เรียงลำดับ

ฉันยังทดสอบประสิทธิภาพของวิธีการลากจูงทั้งสอง

คุณยังสามารถเรียกใช้และทดสอบประสิทธิภาพ ... Happy coding (:

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

อีกอันหนึ่งคล้ายกับคำตอบของ Kennebec แต่ทั้งหมดอยู่ในบรรทัดเดียว:

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.