JavaScript'te bir boole koşuluyla eşleşen dizinin ilk öğesini nasıl bulabilirim?


220

Belirli bir koşulla eşleşen bir JS dizisinin ilk öğesini bulmak için bilinen, yerleşik / zarif bir yol olup olmadığını merak ediyorum. AC # eşdeğeri List.Find olur .

Şimdiye kadar böyle iki işlevli bir kombo kullanıyorum:

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

Ve sonra kullanabilirim:

var result = someArray.findFirst(isNotNullNorUndefined);

Ancak ECMAScript'te çok fazla işlevsel tarzda dizi yöntemi olduğundan , belki de zaten böyle bir şey var mı? Bir çok insanın sürekli böyle şeyler uygulaması gerektiğini hayal ediyorum ...



Underscore.js gerçekten çok güzel görünüyor! Ve bulduğu (). Teşekkürler!
Jakub P.

1
Bildiğiniz gibi, bunu azaltabilirsiniz: buna return (typeof (o) !== 'undefined' && o !== null);kadar return o != null;. Tam olarak eşdeğerdirler.
delilik uçurumları

1
Bunu bildiğim iyi oldu. Ama biliyorsunuz, gibi zorlayan operatörlere güvensizim! = Veya ==. Kolayca test bile edemezdim, çünkü bu şekilde null'a zorlanan başka bir değer olmadığını bir şekilde kontrol etmem gerekecekti ... :) Yani izin verilen bir kütüphaneye sahip olduğum için ne kadar şanslıyım Bu işlevi tamamen kaldırmam için bana ... :)
Jakub P.

Dürüst olmak gerekirse, bunu oldukça zarif bir çözüm olarak söylemek zorundayım. Bulabildiğim en yakın şey Array.prototype.some, bazı öğelerin kendisine verilen bir işlevi yerine getirip getirmediğini bulmaya çalışır. Ne yazık ki, bu dizin veya öğe yerine bir boole döndürür. Kütüphanelerin çok daha büyük olmaları ve kullanamayacağınız şeyleri içermesi nedeniyle çözümünüzü bir kütüphane kullanma konusunda tavsiye ederim ve işleri hafif tutmayı tercih ederim (çünkü sadece bir işlevi sağladığı paketin dışında kullanabilirsiniz).
Graham Robertson

Yanıtlar:


220

ES6'dan beri diziler için yerel findyöntem vardır ; bu, ilk eşleşmeyi bulup değeri döndürdüğünde diziyi numaralandırmayı durdurur.

const result = someArray.find(isNotNullNorUndefined);

Eski cevap:

Bu filterönerileri durdurmak için bir cevap göndermek zorunda :-)

ECMAScript'te çok fazla işlevsel tarzda dizi yöntemi olduğundan, belki de zaten böyle bir şey var mı?

Bir koşul karşılanana (ve sonra durdurulana kadar) diziyi yinelemek için someArray yöntemini kullanabilirsiniz . Maalesef, koşulun yalnızca bir kez karşılanıp karşılanmadığını döndürür, hangi öğeyle (veya hangi dizinde) karşılandığını değil. Bu yüzden biraz değiştirmeliyiz:

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
Filter () 'e yönlendirilmiş tüm hoşnutsuzluğu tamamen anladığımı söyleyemem. Daha yavaş olabilir, ama gerçekte; bunun büyük olasılıkla kullanıldığı çoğu durumda, başlamak için küçük bir listedir ve JavaScript uygulamalarının çoğu bu düzeyde verimlilik konusunda gerçekten endişelenecek kadar karmaşık değildir. [] .filter (test) .pop () veya [] .filter (test) [0] basit, yerel ve okunabilir. Tabii ki iş uygulamaları veya oyunlar gibi yoğun olmayan uygulamalar hakkında konuşuyorum.
Josh Mc

11
Filtre çözümleri tüm dizi / koleksiyonları geçiyor mu? Öyleyse, filtreleme çok verimsizdir, çünkü bulunan değer koleksiyondaki ilk değer olsa bile tüm dizi üzerinde çalışır. some()Öte yandan, hemen geri döner, bu da neredeyse tüm durumlarda filtreleme çözümlerinden çok daha hızlıdır.
AlikElzin-kilaka

@ AlikElzin-kilaka: Evet, kesinlikle.
Bergi

15
@JoshMc elbette, ancak Stack Overflow gibi basit bir soruna bir çözüm gönderirken gereksiz yere verimsiz olmamak mantıklı. Birçok kişi kodu buradan kopyalayıp bir yardımcı program işlevine yapıştıracak ve bazıları bir noktada bu yardımcı işlevi, performansın uygulamayı düşünmeden önemli olduğu bir bağlamda kullanacaktır. Onlara başlamak için verimli bir uygulaması olan bir şey verdiyseniz, aksi takdirde sahip olmayacakları bir performans sorununu çözdünüz veya bunları teşhis etmek için bir sürü geliştirici zaman kazandınız.
Mark Amery

1
@SuperUberDuper: Hayır. Aşağıdaki Mark Amery'nin cevabına bakınız.
Bergi

104

ECMAScript 6'dan itibaren Array.prototype.findbunun için kullanabilirsiniz . Bu, Firefox (25.0), Chrome (45.0), Edge (12) ve Safari'de (7.1) uygulanır ve çalışır, ancak Internet Explorer'da veya bir dizi eski veya nadir platformda uygulanmaz .

Örneğin, aşağıdaki ifade olarak değerlendirilir 106.

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

Bunu şu anda kullanmak istiyorsanız, ancak IE veya diğer destekleyici tarayıcılar için desteğe ihtiyacınız varsa, bir şim kullanabilirsiniz. Es6-shim'i tavsiye ederim . MDN ayrıca, herhangi bir nedenden ötürü tüm es6-shim'i projenize koymak istemiyorsanız bir şim sunar . Maksimum uyumluluk için es6-shim'i istersiniz, çünkü MDN sürümünden farklı olarak, buggy yerel uygulamalarını algılar findve bunların üzerine yazar ( "Array # find ve Array # findIndex'te hatalar etrafında çalışma" ve hemen ardından gelen satırlara bakın) .


finddaha iyi olduğunu filterçünkü findbu ise bir element, koşul ile eşleşen bulduğunda hemen durur filtertüm unsurları ile ilmeklerin Tüm eşleşen elemanları elde edildi.
Anh Tran

59

Filtre kullanma ve sonuç dizisinden ilk dizini alma hakkında ne dersiniz ?

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

6
Es5 yöntemlerini kullanmaya devam edin. var sonuç = someArray.filter (isNotNullNorUndefined) .shift ();
16:21 de someyoungideas

Ben kendimi @Bergi yukarıda cevap bulmak için oy-oy rağmen, ben ES6 yıkım ile biraz yukarıda artırabilir düşünüyorum: var [sonuç] = someArray.filter (isNotNullNorUndefined);
Nakul Manchanda

@someyoungideas .shiftBurada kullanmanın yararlarını açıklayabilir misiniz ?
jakubiszon

3
@jakubiszon Kullanmanın yararı shift"akıllı görünüyor" ama aslında daha kafa karıştırıcı olmasıdır. Kim shift()hiç argüman olmadan çağrı yapmanın ilk unsuru ele almakla aynı olacağını düşünürdü? Belirsiz IMO. Dizi erişimi zaten daha hızlı: jsperf.com/array-access-vs-shift
Josh M.

Ayrıca, köşeli ayraç notasyonu ES5 afaik hem nesneler ve diziler için, bir tercih görmemiştim kullanılabilir .shift()üzerinde [0]böyle ifade açıkça. Buna rağmen, kullanmayı seçip seçemeyeceğiniz bir alternatif, buna rağmen [0]yapışırdım.
SidOfc

15

JavaScript'in böyle bir çözüm sunmadığı açıktır; İşte en faydalı iki türev, en faydalı ilk:

  1. Array.prototype.some(fn)bir koşul karşılandığında istenen durdurma davranışını sunar, ancak yalnızca bir öğenin mevcut olup olmadığını döndürür; Bergi'nin cevabının sunduğu çözüm gibi bazı hile yapmak zor değil .

  2. Array.prototype.filter(fn)[0]tek bir astar sağlar, ancak en az verimlidir, çünkü N - 1sadece ihtiyacınız olanı elde etmek için öğeleri fırlatırsınız .

JavaScript'teki geleneksel arama yöntemleri, öğenin kendisi veya -1 yerine bulunan öğenin dizinini döndürerek karakterize edilir. Bu, olası tüm türlerin etki alanından bir dönüş değeri seçmek zorunda kalmaz; bir dizin yalnızca bir sayı olabilir ve negatif değerler geçersizdir.

Yukarıdaki her iki çözüm de ofset aramayı desteklemiyor, bu yüzden bunu yazmaya karar verdim:

(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

En kapsamlı cevap gibi görünüyor. Cevabınıza üçüncü bir yaklaşım ekleyebilir misiniz?
Mrusful

13

Özet:

  • Bir dizideki boole koşuluyla eşleşen ilk öğeyi bulmak için ES6 find()
  • find()üzerinde bulunan Array.prototypeher dizide kullanılabilir böylece.
  • find()bir booleankoşulun test edildiği bir geri arama alır . İşlev değeri döndürür (dizin değil!)

Misal:

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

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

console.log(found);   //  56



4

ES 2015 itibariyle, Array.prototype.find()bu tam işlevselliği sağlar.

Bu özelliği desteklemeyen tarayıcılar için Mozilla Geliştirici Ağı bir çoklu dolgu sağlamıştır (aşağıda yapıştırılmıştır):

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
Koşul koşulu ve bazı açıklayıcı kelimeler içeren gerçek hayattan bir örnek, cevabınızı daha değerli ve anlaşılır hale getirecektir.
SaschaM78

0

Aşağıdaki çözümü elde etmek için internetteki birden fazla kaynaktan ilham aldım. Hem varsayılan bazı değerleri hesaba katmak hem de her girişi bu çözümü çözdüğü genel bir yaklaşımla karşılaştırmak için bir yol sağlamak istedik.

Kullanım: (değer "İkinci" vererek)

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

Uygulama:

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

Bu aramayı gerçekleştirmek için Javascript'te yerleşik bir işlev yoktur.

JQuery kullanıyorsanız bir jQuery.inArray(element,array).


Muhtemelen Underscore ile gidecek olsam da, bu da işe yarar :)
Jakub P.

3
Bu, askerin ihtiyaç duyduğu şeyin çıktısını karşılamıyor (boole değil, bir indekste elemente ihtiyaç duyuyor).
Graham Robertson

@GrahamRobertson $.inArraybir boole döndürmez, (şaşırtıcı bir şekilde!) İlk eşleşen öğenin dizinini döndürür. Yine de OP'nin istediği şeyi yapmıyor.
Mark Amery

-2

throwTüm doğru hata mesajlarını (tabanlı Array.prototype.filter) ancak ilk sonuçta yinelemeyi durduracak daha az zarif bir yol

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

Sonra örnekler

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

filterKullanarak biterek çalışır throw.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.