Bagaimana menemukan elemen pertama array yang cocok dengan kondisi boolean di JavaScript?

218

Saya bertanya-tanya apakah ada cara yang dikenal, bawaan / elegan untuk menemukan elemen pertama dari array JS yang cocok dengan kondisi tertentu. Setara dengan AC # adalah List.Find .

Sejauh ini saya telah menggunakan kombo dua fungsi seperti ini:

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

Dan kemudian saya bisa menggunakan:

var result = someArray.findFirst(isNotNullNorUndefined);

Tetapi karena ada begitu banyak metode array gaya fungsional dalam ECMAScript , mungkin sudah ada sesuatu di luar sana seperti ini? Saya membayangkan banyak orang harus menerapkan hal-hal seperti ini setiap saat ...

Jakub P.
sumber
6
Tidak ada metode
bawaan
Underscore.js memang terlihat sangat bagus! Dan itu telah menemukan (). Terima kasih!
Jakub P.
1
Asal tahu saja, Anda bisa mengurangi ini: return (typeof (o) !== 'undefined' && o !== null);turun ke ini return o != null;. Mereka persis setara.
Tebing kegilaan
1
Senang mendengarnya. Tapi tahukah Anda, saya tidak percaya pada operator pemaksaan seperti! = Atau ==. Saya bahkan tidak akan dapat dengan mudah mengujinya, karena saya harus memeriksa entah bagaimana bahwa tidak ada nilai lain yang dipaksa untuk dibatalkan dalam mode itu ... :) Jadi, betapa beruntungnya saya memiliki perpustakaan yang memungkinkan saya untuk menghapus fungsi itu sama sekali ... :)
Jakub P.
Sejujurnya saya harus mengatakan ini solusi yang cukup elegan. Hal terdekat yang dapat saya temukan adalah Array.prototype.some yang mencoba menemukan jika beberapa elemen memenuhi kondisi tertentu yang Anda berikan dalam bentuk fungsi. Sayangnya, ini mengembalikan boolean, bukan indeks atau elemen. Saya akan merekomendasikan solusi Anda menggunakan perpustakaan karena perpustakaan cenderung jauh lebih besar dan berisi hal-hal yang tidak akan Anda gunakan dan saya lebih suka menjaga hal-hal yang ringan (karena Anda mungkin hanya menggunakan satu fungsi dari suite yang disediakannya).
Graham Robertson

Jawaban:

217

Karena ES6 ada findmetode asli untuk array; ini berhenti menghitung array setelah menemukan kecocokan pertama dan mengembalikan nilai.

const result = someArray.find(isNotNullNorUndefined);

Jawaban lama:

Saya harus mengirim jawaban untuk menghentikan filtersaran ini :-)

karena ada begitu banyak metode array gaya-fungsional di ECMAScript, mungkin sudah ada sesuatu di luar sana seperti ini?

Anda dapat menggunakan somemetode Array untuk mengulangi array sampai suatu kondisi terpenuhi (dan kemudian berhenti). Sayangnya itu hanya akan mengembalikan apakah kondisi dipenuhi sekali, bukan oleh elemen mana (atau pada indeks apa) itu dipenuhi. Jadi kita harus sedikit mengubahnya:

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);
Bergi
sumber
28
Tidak bisa mengatakan saya sepenuhnya memahami semua ketidaksukaan yang diarahkan pada filter (). Mungkin lebih lambat, tetapi dalam kenyataannya; kebanyakan kasus di mana ini mungkin digunakan, ini adalah daftar kecil untuk memulai, dan sebagian besar aplikasi JavaScript tidak cukup rumit untuk benar-benar khawatir tentang efisiensi pada level ini. [] .filter (test) .pop () atau [] .filter (test) [0] sederhana, asli dan mudah dibaca. Tentu saja saya berbicara tentang aplikasi bisnis atau situs web bukan aplikasi intensif seperti permainan.
Josh Mc
11
Apakah solusi filter melintasi semua array / koleksi? Jika demikian, penyaringan sangat tidak efisien, karena berjalan di atas semua array bahkan jika nilai yang ditemukan adalah yang pertama dalam koleksi. some()di sisi lain, segera kembali, yang jauh lebih cepat di hampir semua kasus daripada solusi penyaringan.
AlikElzin-kilaka
@ AlikElzin-kilaka: Ya, persis.
Bergi
15
@JoshMc yakin, tetapi masuk akal untuk tidak menjadi tidak efisien dengan sangat tidak wajar ketika memposting solusi untuk masalah sederhana di suatu tempat seperti Stack Overflow. Banyak orang akan menyalin dan menempelkan kode dari sini ke fungsi utilitas, dan beberapa dari mereka, pada titik tertentu, akhirnya menggunakan fungsi utilitas itu dalam konteks di mana kinerja penting tanpa memikirkan implementasi. Jika Anda telah memberi mereka sesuatu yang memiliki implementasi yang efisien untuk memulai, Anda telah memecahkan masalah kinerja yang seharusnya tidak mereka miliki, atau menyelamatkan mereka banyak waktu untuk mendiagnosisnya.
Mark Amery
1
@SuperUberDuper: Tidak. Lihat jawaban Mark Amery di bawah ini.
Bergi
104

Pada ECMAScript 6, Anda dapat menggunakannya Array.prototype.finduntuk ini. Ini diimplementasikan dan bekerja di Firefox (25.0), Chrome (45.0), Edge (12), dan Safari (7.1), tetapi tidak di Internet Explorer atau sekelompok platform lama atau tidak umum lainnya .

Misalnya, ekspresi di bawah ini dievaluasi menjadi 106.

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

Jika Anda ingin menggunakan ini sekarang tetapi membutuhkan dukungan untuk IE atau browser yang tidak didukung lainnya, Anda dapat menggunakan shim. Saya merekomendasikan es6-shim . MDN juga menawarkan shim jika karena alasan tertentu Anda tidak ingin memasukkan seluruh es6-shim ke dalam proyek Anda. Untuk kompatibilitas maksimum, Anda menginginkan es6-shim, karena tidak seperti versi MDN, ia mendeteksi implementasi asli buggy dari finddan menimpa mereka (lihat komentar yang dimulai "Kerjakan bug di Array # find dan Array # findIndex" dan baris yang segera mengikutinya) .

Mark Amery
sumber
findlebih baik dari filtersejak findberhenti segera ketika menemukan elemen cocok kondisi, sementara filterloop melalui semua elemen untuk memberikan semua elemen cocok.
Anh Tran
59

Bagaimana dengan menggunakan filter dan mendapatkan indeks pertama dari array yang dihasilkan?

var result = someArray.filter(isNotNullNorUndefined)[0];
Phil Mander
sumber
6
Tetap menggunakan metode es5. var result = someArray.filter (isNotNullNorUndefined) .shift ();
someyoungideas
Meskipun saya sendiri terpilih untuk menemukan jawaban di atas @Bergi, saya pikir dengan ES6 perusakan kita dapat sedikit memperbaiki di atas: var [hasil] = someArray.filter (isNotNullNorUndefined);
Nakul Manchanda
@someyoungideas dapatkah Anda menjelaskan manfaat menggunakan .shiftdi sini?
jakubiszon
3
@jakubiszon Manfaat menggunakan shiftadalah "terlihat pintar" tetapi sebenarnya lebih membingungkan. Siapa yang akan berpikir bahwa menelepon shift()tanpa argumen sama dengan mengambil elemen pertama? Tidak jelas IMO. Akses array lebih cepat: jsperf.com/array-access-vs-shift
Josh M.
Juga, notasi braket persegi tersedia untuk kedua objek dan array di ES5 AFAIK, tidak pernah melihat preferensi .shift()atas [0]secara eksplisit dinyatakan seperti ini. Meskipun begitu, ini adalah alternatif yang bisa Anda pilih untuk digunakan atau tidak, saya tetap menggunakannya [0].
SidOfc
15

Seharusnya jelas sekarang bahwa JavaScript tidak menawarkan solusi semacam itu secara asli; di sini adalah dua turunan terdekat, yang paling bermanfaat pertama:

  1. Array.prototype.some(fn)menawarkan perilaku berhenti yang diinginkan ketika suatu kondisi terpenuhi, tetapi hanya mengembalikan apakah ada elemen; tidak sulit untuk menerapkan beberapa tipu daya, seperti solusi yang ditawarkan oleh jawaban Bergi .

  2. Array.prototype.filter(fn)[0]membuat one-liner yang hebat tetapi paling tidak efisien, karena Anda membuang N - 1elemen hanya untuk mendapatkan yang Anda butuhkan.

Metode pencarian tradisional dalam JavaScript ditandai dengan mengembalikan indeks elemen yang ditemukan alih-alih elemen itu sendiri atau -1. Ini menghindari harus memilih nilai kembali dari domain semua jenis yang mungkin; indeks hanya bisa berupa angka dan nilai negatif tidak valid.

Kedua solusi di atas tidak mendukung pencarian offset, jadi saya memutuskan untuk menulis ini:

(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
Mendongkrak
sumber
Sepertinya jawaban yang paling komprehensif. Bisakah Anda menambahkan pendekatan ketiga dalam jawaban Anda?
Mrusful
13

Ringkasan:

  • Untuk menemukan elemen pertama dalam array yang cocok dengan kondisi boolean kita bisa menggunakan ES6 find()
  • find()terletak di Array.prototypesehingga dapat digunakan pada setiap array.
  • find()mengambil panggilan balik di mana suatu booleankondisi diuji. Fungsi mengembalikan nilai (bukan indeks!)

Contoh:

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

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

console.log(found);   //  56

Willem van der Veen
sumber
8

Jika Anda menggunakan, underscore.jsAnda dapat menggunakan finddan indexOffungsinya untuk mendapatkan apa yang Anda inginkan:

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

Dokumentasi:

Matt Woelk
sumber
Gunakan opsi underscorejs hanya jika perlu karena memuat perpustakaan hanya untuk ini hanya tidak layak
DannyFeliz
4

Pada ES 2015, Array.prototype.find()menyediakan fungsionalitas yang tepat ini.

Untuk browser yang tidak mendukung fitur ini, Mozilla Developer Network telah menyediakan polyfill (disisipkan di bawah):

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;
  };
}
Kevin Lee Garner
sumber
2
foundElement = myArray[myArray.findIndex(element => //condition here)];
dotista2008
sumber
3
Contoh kehidupan nyata dengan klausa kondisi dan beberapa kata penjelasan akan membuat jawaban Anda lebih berharga dan mudah dipahami.
SaschaM78
0

Saya mendapatkan inspirasi dari berbagai sumber di internet untuk mendapatkan solusi di bawah ini. Ingin memperhitungkan beberapa nilai default dan menyediakan cara untuk membandingkan setiap entri untuk pendekatan umum yang dipecahkan ini.

Penggunaan: (memberi nilai "Kedua")

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

Penerapan:

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;
}
Henrik
sumber
-2

Tidak ada fungsi bawaan di Javascript untuk melakukan pencarian ini.

Jika Anda menggunakan jQuery, Anda dapat melakukan a jQuery.inArray(element,array).

PedroSena
sumber
Itu akan berhasil juga, meskipun saya mungkin akan menggunakan Underscore :)
Jakub P.
3
Ini tidak memenuhi output dari apa yang diminta penanya (membutuhkan elemen pada indeks tertentu, bukan boolean).
Graham Robertson
@GrahamRobertson $.inArraytidak mengembalikan boolean, itu (mengejutkan!) Mengembalikan indeks elemen pencocokan pertama. Namun, masih tidak melakukan apa yang diminta OP.
Mark Amery
-2

Cara yang kurang elegan yaitu throwpesan kesalahan yang benar (berdasarkan Array.prototype.filter) tetapi akan berhenti diulang pada hasil pertama

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

Maka contohnya adalah

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

Ini bekerja dengan mengakhiri filterdengan menggunakan throw.

Paul S.
sumber