วิธีการค้นหาองค์ประกอบแรกของอาร์เรย์ที่ตรงกับเงื่อนไขบูลีนใน JavaScript?


220

ฉันสงสัยว่ามีวิธีที่รู้จักในตัว / หรูหราเพื่อค้นหาองค์ประกอบแรกของอาร์เรย์ JS ที่ตรงกับเงื่อนไขที่กำหนดหรือไม่ AC # เทียบเท่าจะList.Find

จนถึงตอนนี้ฉันได้ใช้คอมโบสองฟังก์ชันดังนี้:

// Returns the first element of an array that satisfies given predicate
Array.prototype.findFirst = function (predicateCallback) {
    if (typeof predicateCallback !== 'function') {
        return undefined;
    }

    for (var i = 0; i < arr.length; i++) {
        if (i in this && predicateCallback(this[i])) return this[i];
    }

    return undefined;
};

// Check if element is not undefined && not null
isNotNullNorUndefined = function (o) {
    return (typeof (o) !== 'undefined' && o !== null);
};

จากนั้นฉันก็สามารถใช้:

var result = someArray.findFirst(isNotNullNorUndefined);

แต่เนื่องจากมีวิธีอาเรย์สไตล์การทำงานจำนวนมากใน ECMAScriptอาจมีบางอย่างที่มีอยู่แล้วเช่นนี้? ฉันคิดว่าผู้คนจำนวนมากต้องใช้สิ่งนี้ตลอดเวลา ...


6
ไม่มีวิธีการในตัว แต่มียูทิลิตี้ไลบรารีที่ประมาณฟังก์ชั่นนี้เช่นdocumentcloud.github.com/underscore
kinakuta

Underscore.js ดูดีมาก ๆ ! และมันก็มี find () ขอบคุณ!
จาคุบพี

1
เพียงเพื่อให้คุณรู้ว่าคุณสามารถลดนี้: ลงไปนี้return (typeof (o) !== 'undefined' && o !== null); return o != null;พวกมันเทียบเท่ากันหมด
หน้าผาแห่งความวิกลจริต

1
ดีแล้วที่รู้. แต่คุณรู้ไหมฉันไม่ไว้ใจผู้คุมที่ชอบ! = หรือ == ฉันจะไม่สามารถทดสอบได้อย่างง่ายดายเพราะฉันต้องตรวจสอบว่าไม่มีค่าอื่นใดที่ถูกบังคับให้เป็นโมฆะในแบบนั้น ... :) ดังนั้นฉันจึงโชคดีที่มีห้องสมุดที่ได้รับอนุญาต ฉันจะลบฟังก์ชั่นนั้นไปเลย ... :)
Jakub P.

ฉันต้องบอกว่านี่เป็นทางออกที่สง่างามพอสมควร สิ่งที่ใกล้เคียงที่สุดที่ฉันสามารถหาได้คือ Array.prototype.some ซึ่งพยายามค้นหาว่าองค์ประกอบบางอย่างเป็นไปตามเงื่อนไขที่คุณกำหนดให้ผ่านไปในรูปแบบของฟังก์ชันหรือไม่ น่าเสียดายที่สิ่งนี้คืนค่าบูลีนแทนที่จะเป็นดัชนีหรือองค์ประกอบ ฉันอยากจะแนะนำวิธีแก้ปัญหาของคุณให้ใช้ห้องสมุดเพราะห้องสมุดมักจะมีขนาดใหญ่กว่าและมีสิ่งที่คุณไม่ได้ใช้และฉันชอบที่จะรักษาสิ่งต่าง ๆ ให้สว่างขึ้น
Graham Robertson

คำตอบ:


219

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

const result = someArray.find(isNotNullNorUndefined);

คำตอบเก่า:

ฉันต้องโพสต์คำตอบเพื่อหยุดfilterคำแนะนำเหล่านี้:-)

เนื่องจากมีวิธีอาเรย์สไตล์การทำงานมากมายใน ECMAScript บางทีอาจมีบางอย่างที่เป็นเช่นนี้อยู่แล้ว?

คุณสามารถใช้someวิธี Arrayเพื่อย้ำอาร์เรย์จนเป็นไปตามเงื่อนไข (และหยุด) น่าเสียดายที่มันจะกลับมาเพียงว่าเงื่อนไขถูกพบเพียงครั้งเดียวไม่ใช่โดยองค์ประกอบใด (หรือที่ดัชนีใด) ดังนั้นเราต้องแก้ไขมันเล็กน้อย:

function find(arr, test, ctx) {
    var result = null;
    arr.some(function(el, i) {
        return test.call(ctx, el, i, arr) ? ((result = el), true) : false;
    });
    return result;
}

var result = find(someArray, isNotNullNorUndefined);

28
ไม่สามารถพูดได้ว่าฉันเข้าใจสิ่งที่ไม่ชอบทั้งหมดโดยตรงที่ตัวกรอง () มันอาจจะช้ากว่า แต่ในความเป็นจริง กรณีส่วนใหญ่ที่อาจใช้เป็นรายการเริ่มต้นเล็ก ๆ และแอปพลิเคชัน JavaScript ส่วนใหญ่ไม่ซับซ้อนพอที่จะกังวลเกี่ยวกับประสิทธิภาพในระดับนี้ [] .filter (ทดสอบ) .pop () หรือ [] .filter (ทดสอบ) [0] นั้นเรียบง่ายเป็นภาษาพื้นเมืองและสามารถอ่านได้ แน่นอนว่าฉันกำลังพูดถึงแอพธุรกิจหรือเว็บไซต์ที่ไม่ได้ใช้แอพอย่างเข้มข้นเช่นเกม
Josh Mc

11
โซลูชันการกรองข้ามอาร์เรย์ / ชุดรวมทั้งหมดหรือไม่ ถ้าเป็นเช่นนั้นการกรองจะไม่มีประสิทธิภาพมากเพราะจะทำงานในอาร์เรย์ทั้งหมดแม้ว่าค่าที่พบเป็นค่าแรกในคอลเลกชัน some()ในทางกลับกันให้คืนทันทีซึ่งเกือบจะเร็วกว่าในเกือบทุกกรณีกว่าโซลูชันการกรอง
AlikElzin-kilaka

@ AlikElzin-kilaka: ใช่แน่นอน
Bergi

15
@JoshMc แน่นอน แต่มันก็สมเหตุสมผลที่จะไม่มีประสิทธิภาพเมื่อทำการโพสต์วิธีแก้ไขปัญหาง่ายๆที่อื่นเช่น Stack Overflow ผู้คนจำนวนมากจะคัดลอกและวางรหัสจากที่นี่ในฟังก์ชั่นยูทิลิตี้และบางคนจะลงเอยด้วยการใช้ฟังก์ชั่นยูทิลิตี้นั้นในบริบทที่ประสิทธิภาพมีความสำคัญโดยไม่ต้องคิดถึงการใช้งาน หากคุณได้ให้สิ่งที่มีการใช้งานอย่างมีประสิทธิภาพให้กับพวกเขาคุณจะได้แก้ไขปัญหาด้านประสิทธิภาพที่พวกเขาไม่มีหรือช่วยให้พวกเขาประหยัดเวลาในการวินิจฉัยปัญหา
Mark Amery

1
@SuperUberDuper: ไม่ดูคำตอบของ Mark Amery ด้านล่าง
Bergi

104

ในฐานะของ ECMAScript 6 คุณสามารถใช้Array.prototype.findสำหรับสิ่งนี้ นี้จะดำเนินการและการทำงานใน Firefox (25.0) Chrome (45.0), ขอบ (12) และ Safari (7.1) แต่ไม่ได้อยู่ใน Internet Explorer หรือพวงของแพลตฟอร์มเก่าหรือผิดปกติอื่น

106ยกตัวอย่างเช่นการแสดงออกด้านล่างประเมินเพื่อ

[100,101,102,103,104,105,106,107,108,109].find(function (el) {
    return el > 105;
});

หากคุณต้องการใช้งานในตอนนี้ แต่ต้องการการสนับสนุนสำหรับ IE หรือเบราว์เซอร์ที่ไม่รองรับอื่น ๆ คุณสามารถใช้ shim ได้ ผมขอแนะนำให้ES6-ชิม MDN ยังเสนอshimด้วยเหตุผลบางประการที่คุณไม่ต้องการใส่ทั้ง es6-shim ในโครงการของคุณ เข้ากันได้สูงสุดที่คุณต้องการ ES6-ชิมเพราะแตกต่างจากรุ่น MDN ตรวจพบการใช้งานพื้นเมืองรถของfindและเขียนทับพวกเขา (ดูความคิดเห็นที่เริ่มต้น"การทำงานรอบข้อบกพร่องในอาร์เรย์ # หาและอาร์เรย์ # findIndex"และสายทันทีต่อไปนี้มัน) .


findดีกว่าfilterตั้งแต่findหยุดทันทีเมื่อพบองค์ประกอบที่ตรงกับเงื่อนไขในขณะที่filterวนรอบองค์ประกอบทั้งหมดเพื่อให้องค์ประกอบที่ตรงกันทั้งหมด
Anh Tran

59

สิ่งที่เกี่ยวกับการใช้ตัวกรองและการรับดัชนีแรกจากอาร์เรย์ผลลัพธ์

var result = someArray.filter(isNotNullNorUndefined)[0];

6
ใช้วิธี ES5 ต่อไป var result = someArray.filter (isNotNullNorUndefined) .shift ();
someyoungideas

แม้ว่าฉันจะได้รับการโหวตเพื่อค้นหาคำตอบเหนือ @Bergi แต่ฉันคิดว่าการทำลายล้าง ES6 นั้นเราสามารถปรับปรุงได้เหนือกว่าเล็กน้อย: var [result] = someArray.filter (isNotNullNorUndefined);
Nakul Manchanda

@someyoungideas คุณสามารถอธิบายถึงประโยชน์ของการใช้.shiftที่นี่ได้หรือไม่
jakubiszon

3
@jakubiszon ประโยชน์ของการใช้shiftคือมัน "ดูฉลาด" แต่จริง ๆ แล้วสับสนมากกว่า ใครจะคิดว่าการโทรshift()โดยไม่มีข้อโต้แย้งจะเหมือนกับการรับองค์ประกอบแรก IMO ไม่ชัดเจน การเข้าถึงอาเรย์นั้นเร็วกว่า: jsperf.com/array-access-vs-shift
Josh M.

นอกจากนี้เครื่องหมายรูปสี่เหลี่ยมจัตุรัสจะมีให้สำหรับทั้งวัตถุและอาร์เรย์ใน ES5 AFAIK ไม่เคยเห็นการตั้งค่า.shift()มากกว่าที่[0]ระบุไว้อย่างชัดเจนเช่นนี้ แม้ว่ามันจะเป็นทางเลือกที่คุณสามารถเลือกใช้หรือไม่[0]ก็ตาม
SidOfc

15

ตอนนี้มันควรจะชัดเจนแล้วว่า JavaScript ไม่มีวิธีแก้ปัญหาเช่นนั้น นี่คืออนุพันธ์ที่ใกล้เคียงที่สุดซึ่งมีประโยชน์มากที่สุดอันดับแรก:

  1. Array.prototype.some(fn)นำเสนอพฤติกรรมที่ต้องการหยุดเมื่อเงื่อนไขตรง แต่กลับเฉพาะว่าองค์ประกอบมีอยู่; ก็ไม่ยากที่จะใช้กลอุบายบางอย่างเช่นการแก้ปัญหาที่นำเสนอโดยคำตอบของ Bergi

  2. Array.prototype.filter(fn)[0]ทำให้เป็นหนึ่งซับที่ดี แต่มีประสิทธิภาพน้อยที่สุดเพราะคุณทิ้งN - 1องค์ประกอบเพียงเพื่อให้ได้สิ่งที่คุณต้องการ

วิธีการค้นหาแบบดั้งเดิมใน JavaScript นั้นมีลักษณะโดยการส่งคืนดัชนีขององค์ประกอบที่พบแทนที่จะเป็นองค์ประกอบเองหรือ -1 หลีกเลี่ยงการเลือกค่าตอบแทนจากโดเมนทุกประเภทที่เป็นไปได้ ดัชนีสามารถเป็นตัวเลขและค่าลบไม่ถูกต้องเท่านั้น

โซลูชันทั้งสองข้างต้นไม่รองรับการค้นหาออฟเซ็ตดังนั้นฉันจึงตัดสินใจเขียนสิ่งนี้:

(function(ns) {
  ns.search = function(array, callback, offset) {
    var size = array.length;

    offset = offset || 0;
    if (offset >= size || offset <= -size) {
      return -1;
    } else if (offset < 0) {
      offset = size - offset;
    }

    while (offset < size) {
      if (callback(array[offset], offset, array)) {
        return offset;
      }
      ++offset;
    }
    return -1;
  };
}(this));

search([1, 2, NaN, 4], Number.isNaN); // 2
search([1, 2, 3, 4], Number.isNaN); // -1
search([1, NaN, 3, NaN], Number.isNaN, 2); // 3

ดูเหมือนคำตอบที่ครอบคลุมที่สุด คุณสามารถเพิ่มวิธีที่สามในคำตอบของคุณได้ไหม?
Mrusful

13

สรุป:

  • สำหรับการค้นหาองค์ประกอบแรกในอาร์เรย์ที่ตรงกับเงื่อนไขบูลีนเราสามารถใช้ ES6 find()
  • find()ตั้งอยู่บนArray.prototypeเพื่อให้สามารถใช้กับทุกอาร์เรย์
  • find()รับการติดต่อกลับที่มีbooleanการทดสอบเงื่อนไข ฟังก์ชันส่งคืนค่า (ไม่ใช่ดัชนี!)

ตัวอย่าง:

const array = [4, 33, 8, 56, 23];

const found = array.find((element) => {
  return element > 50;
});

console.log(found);   //  56


8

หากคุณใช้underscore.jsคุณสามารถใช้findและindexOfฟังก์ชั่นเพื่อให้ได้สิ่งที่ต้องการ:

var index = _.indexOf(your_array, _.find(your_array, function (d) {
    return d === true;
}));

เอกสารอ้างอิง:


ใช้ตัวเลือกขีดล่างหากจำเป็นเพราะโหลด
ไลบรารี่

4

ในฐานะของ ES 2015 Array.prototype.find()ให้การทำงานที่แน่นอนนี้

สำหรับเบราว์เซอร์ที่ไม่รองรับคุณสมบัตินี้ Mozilla Developer Network ได้จัดทำpolyfill (วางด้านล่าง):

if (!Array.prototype.find) {
  Array.prototype.find = function(predicate) {
    if (this === null) {
      throw new TypeError('Array.prototype.find called on null or undefined');
    }
    if (typeof predicate !== 'function') {
      throw new TypeError('predicate must be a function');
    }
    var list = Object(this);
    var length = list.length >>> 0;
    var thisArg = arguments[1];
    var value;

    for (var i = 0; i < length; i++) {
      value = list[i];
      if (predicate.call(thisArg, value, i, list)) {
        return value;
      }
    }
    return undefined;
  };
}


2
foundElement = myArray[myArray.findIndex(element => //condition here)];

3
ตัวอย่างชีวิตจริงที่มีประโยคเงื่อนไขและคำอธิบายบางคำจะทำให้คำตอบของคุณมีค่าและเข้าใจได้มากขึ้น
SaschaM78

0

ฉันได้รับแรงบันดาลใจจากหลายแหล่งบนอินเทอร์เน็ตเพื่อหาทางแก้ไขด้านล่าง ต้องการคำนึงถึงทั้งค่าเริ่มต้นและเพื่อให้วิธีการเปรียบเทียบแต่ละรายการสำหรับวิธีการทั่วไปซึ่งการแก้ปัญหานี้

การใช้งาน: (ให้ค่า "ที่สอง")

var defaultItemValue = { id: -1, name: "Undefined" };
var containers: Container[] = [{ id: 1, name: "First" }, { id: 2, name: "Second" }];
GetContainer(2).name;

การดำเนินงาน:

class Container {
    id: number;
    name: string;
}

public GetContainer(containerId: number): Container {
  var comparator = (item: Container): boolean => {
      return item.id == containerId;
    };
    return this.Get<Container>(this.containers, comparator, this.defaultItemValue);
  }

private Get<T>(array: T[], comparator: (item: T) => boolean, defaultValue: T): T {
  var found: T = null;
  array.some(function(element, index) {
    if (comparator(element)) {
      found = element;
      return true;
    }
  });

  if (!found) {
    found = defaultValue;
  }

  return found;
}

-2

ไม่มีฟังก์ชันในตัวใน Javascript ที่จะทำการค้นหานี้

หากคุณกำลังใช้ jQuery jQuery.inArray(element,array)คุณสามารถทำ


ที่จะทำงานมากเกินไปแม้ว่าฉันจะไปกับเน้นอาจ :)
ยาคุบบพี

3
สิ่งนี้ไม่ตอบสนองเอาต์พุตของสิ่งที่ผู้ถามต้องการ (ต้องการองค์ประกอบที่ดัชนีบางตัวไม่ใช่บูลีน)
Graham Robertson

@GrahamRobertson $.inArrayไม่ส่งคืนบูลีนมัน (น่าแปลกใจ!) จะส่งคืนดัชนีขององค์ประกอบที่ตรงกันแรก มันยังคงไม่ทำตามที่ OP ร้องขอ
Mark Amery

-2

วิธีที่สวยงามน้อยกว่าที่จะthrowทำให้ข้อความแสดงข้อผิดพลาดถูกต้อง (ตามArray.prototype.filter) แต่จะหยุดการวนซ้ำในผลลัพธ์แรกคือ

function findFirst(arr, test, context) {
    var Result = function (v, i) {this.value = v; this.index = i;};
    try {
        Array.prototype.filter.call(arr, function (v, i, a) {
            if (test(v, i, a)) throw new Result(v, i);
        }, context);
    } catch (e) {
        if (e instanceof Result) return e;
        throw e;
    }
}

จากนั้นเป็นตัวอย่าง

findFirst([-2, -1, 0, 1, 2, 3], function (e) {return e > 1 && e % 2;});
// Result {value: 3, index: 5}
findFirst([0, 1, 2, 3], 0);               // bad function param
// TypeError: number is not a function
findFirst(0, function () {return true;}); // bad arr param
// undefined
findFirst([1], function (e) {return 0;}); // no match
// undefined

มันทำงานโดยการสิ้นสุดโดยใช้filterthrow

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