2012-05-04 10 views
113

Zastanawiam się, czy istnieje znany, wbudowany/elegancki sposób na znalezienie pierwszego elementu tablicy JS pasującego do danego warunku. Odpowiednikiem C# będzie List.Find.Jak znaleźć pierwszy element tablicy odpowiadający warunkowi boolowskiemu w JavaScript?

tej pory używam dwu-funkcyjny combo tak:

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

A potem mogę użyć:

var result = someArray.findFirst(isNotNullNorUndefined); 

Ale ponieważ istnieje so many functional-style array methods in ECMAScript, może tam coś tam już to lubisz? Wyobrażam sobie, że wiele osób musi implementować takie rzeczy przez cały czas ...

+6

Nie ma wbudowany w metodzie, ale istnieją biblioteki narzędzie, które przybliżają tę funkcjonalność, takich jak http://documentcloud.github.com/underscore/ – kinakuta

+0

Underscore.js wygląda naprawdę świetnie! I ma find(). Dzięki! –

+1

Tak, że wiesz, możesz zmniejszyć to: 'return (typeof (o)! == 'undefined' && o! == null);' do tego 'return o! = Null;'. Są dokładnie równoważne. –

Odpowiedz

99

Od wersji ES6 jest rodzimy find method dla tablic.


muszę pisać odpowiedź, aby zatrzymać te filter sugestie :-)

ponieważ istnieje tak wiele metod array funkcjonalnym stylu w ECMAScript, może tam coś tam już tak?

Możesz użyć some Array method do iterowania tablicy aż do spełnienia warunku (a następnie zatrzymania). Niestety, zwróci tylko to, czy warunek został spełniony raz, a nie przez który element (lub na jakim indeksie) został spełniony.Więc musimy zmienić to trochę:

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

Nie mogę powiedzieć, że całkowicie rozumiem całą niechęć skierowaną na filter(). Może być wolniejszy, ale w rzeczywistości; w większości przypadków, w których jest to prawdopodobnie używane, jest to niewielka lista na początek, a większość aplikacji JavaScript nie jest wystarczająco skomplikowana, aby naprawdę martwić się wydajnością na tym poziomie. [] .filter (test) .pop() lub [] .filter (test) [0] są proste, natywne i czytelne. Oczywiście mówię o biznesowych aplikacjach lub stronach nie wymagających intensywnych aplikacji, takich jak gry. –

+10

Czy rozwiązania filtrów przechodzą przez wszystkie macierze/kolekcje? Jeśli tak, filtrowanie jest bardzo nieefektywne, ponieważ działa na całej tablicy, nawet jeśli znaleziona wartość jest pierwszą w kolekcji. 'some()' z drugiej strony, zwraca natychmiast, co jest znacznie szybsze w prawie wszystkich przypadkach niż rozwiązania filtrujące. –

+0

@ AlikElzin-kilaka: Tak, dokładnie. – Bergi

-1

Nie ma wbudowanej funkcji w Javascript, aby wykonać to wyszukiwanie.

Jeśli używasz jQuery, możesz wykonać jQuery.inArray(element,array).

+0

To też by zadziałało, chociaż pójdę z Podkreśleniem prawdopodobnie :) –

+3

To nie odpowiada wynikowi tego, czego żąda pytający (potrzebuje elementu na jakimś indeksie, a nie boolowskim). –

+0

@GrahamRobertson '$ .inArray' nie zwraca boolean, to (co ciekawe!) Zwraca indeks pierwszego pasującego elementu. Nadal jednak nie robi tego, o co prosił OP. –

43

A co z użyciem filter i uzyskaniem pierwszego indeksu z wynikowej tablicy?

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

Kontynuuj używanie metod es5. var result = someArray.filter (isNotNullNorUndefined) .shift(); – someyoungideas

+3

'shift()' to es3. –

-1

Mniej elegancki sposób, który będzie throw wszystkie odpowiednie komunikaty o błędach (na podstawie Array.prototype.filter), ale zatrzyma powtarzanie pierwszy wynik jest

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

Następnie przykłady

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 

Działa przez zakończenie filter przy użyciu throw.

7

Jeśli używasz underscore.js można użyć jego find i indexOf funkcje, aby uzyskać dokładnie to, co chcesz:

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

Dokumentacja:

+0

Użyj opcji underscorejs tylko jeśli jest to konieczne, ponieważ ładujesz bibliotekę tylko dlatego, że nie warto. – DannyFeliz

14

Powinno być jasne już, że JavaScript nie oferuje takiego rozwiązania natywnie; oto najbliższe dwa pochodne, najbardziej przydatny pierwszy:

  1. Array.prototype.some(fn) daje pożądanego zachowania zatrzymanie gdy warunek jest spełniony, ale zwraca tylko, czy element jest obecny; nie jest trudno zastosować pewne oszustwo, takie jak rozwiązanie oferowane przez Bergi's answer.

  2. Array.prototype.filter(fn)[0] tworzy świetny jednolinijkowy, ale jest najmniej efektywny, ponieważ wyrzucasz elementy N - 1 tylko po to, aby uzyskać to, czego potrzebujesz.

Tradycyjne metody wyszukiwania w JavaScript charakteryzują się zwracaniem indeksu znalezionego elementu zamiast samego elementu lub -1. Dzięki temu nie trzeba wybierać wartości zwracanej z domeny wszystkich możliwych typów; indeks może być tylko liczbą, a wartości ujemne są nieprawidłowe.

Oba rozwiązania powyżej nie obsługują przesunięcie szukają albo, więc zdecydowałem się napisać to:

(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 
+0

Wygląda jak najbardziej wyczerpująca odpowiedź. Czy możesz dodać trzecie podejście do swojej odpowiedzi? – Mrusful

69

Od ECMAScript 6, można użyć Array.prototype.find do tego. To jest wdrożony i działa w Firefoksie (25,0), Chrome (45,0), EDGE (12) i Safari (7.1), ale not in Internet Explorer or a bunch of other old or uncommon platforms.

Na przykład, wyrażenie poniżej ocenia się 106.

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

Jeśli chcesz skorzystać z tego prawa, ale teraz potrzebujemy wsparcia dla IE lub innych unsupporting przeglądarek, można użyć podkładkę. Polecam es6-shim. MDN oferuje również a shim jeśli z jakiegoś powodu nie chcesz umieścić cały ES6-podkładkę do projektu. Dla maksymalnej kompatybilności chcesz ES6-podkładkę, ponieważ w odróżnieniu od wersji MDN wykryje buggy rodzimych implementacje find i nadpisuje je (patrz komentarz, który zaczyna „obejścia błędów w Array # znaleźć i Array # findIndex” i natychmiast linie po nim).

3

Jak ES 2015, Array.prototype.find() przewiduje taką funkcjonalność.

Dla przeglądarek, które nie obsługują tej funkcji, Developer Network Mozilla udostępniła polyfill (wklejony poniżej):

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

Dzięki za zaktualizowane informacje. –

Powiązane problemy