Як знайти перший елемент масиву, що відповідає булевій умові в 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
Існує не вбудований метод, але є
утилітні

Underscore.js насправді виглядає дуже приємно! І це має знайти (). Дякую!
Якуб П.

1
Тільки щоб ви знали, ви можете звести це: return (typeof (o) !== 'undefined' && o !== null);до цього return o != null;. Вони абсолютно рівноцінні.
скелі божевілля

1
Добре знати. Але ви знаєте, я недовіряю операторам примусу, як! = Або ==. Я б навіть не міг легко це перевірити, оскільки мені потрібно було б якось перевірити, чи немає іншого значення, яке примушується до цього нулю ... :) Так як мені пощастило мати бібліотеку, дозволену мені взагалі зняти цю функцію ... :)
Якуб П.

Я б чесно мав сказати це досить елегантне рішення. Найближче, що я можу знайти, це Array.prototype.some, який намагається знайти, чи задовольняє якийсь елемент заданій умові, ви передаєте йому у вигляді функції. На жаль, це повертає булеве значення замість індексу чи елемента. Я рекомендую ваше рішення щодо використання бібліотеки, оскільки бібліотеки, як правило, набагато більше і містять речі, які ви не будете використовувати, і я вважаю за краще зберігати речі легкими (оскільки ви можете використовувати лише одну функцію поза набором, який вона надає).
Грем Робертсон

Відповіді:


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
Не можу сказати, що я повністю розумію всю неприязнь, спрямовану на filter (). Це може бути повільніше, але насправді; Більшість випадків, коли це, ймовірно, використовується, для початку це невеликий список, і більшість додатків JavaScript не є досить складними, щоб справді турбуватися про ефективність на цьому рівні. [] .filter (test) .pop () або [] .filter (test) [0] є простими, власними та читабельними. Звичайно, я кажу про бізнес-додатки або веб-сайти, не такі інтенсивні програми, як ігри.
Джош Мак

11
Чи обробляють фільтри рішення всіма масивами / колекціями? Якщо так, фільтрація дуже неефективна, оскільки вона працює над усім масивом, навіть якщо знайдене значення є першим у колекції. some()з іншого боку, повертається негайно, що набагато швидше майже у всіх випадках, ніж фільтрування розчинів.
AlikElzin-kilaka

@ AlikElzin-kilaka: Так, саме так.
Бергі

15
@JoshMc впевнений, але має сенс не бути бездоганно неефективним, коли публікуєш вирішення такої простої проблеми десь, як переповнення стека. Багато людей скопіюють і вставлять код звідси у функцію утиліти, а дехто з них у якийсь момент перетворить цю функцію на користь у контексті, коли продуктивність має значення, не замислюючись про реалізацію. Якщо ви дали їм щось, що має ефективну реалізацію для початку, ви або вирішили проблему з продуктивністю, якої б у них інакше не було, або зберегли їх купу часу для діагностики.
Марк Амері

1
@SuperUberDuper: Ні. Дивіться відповідь Марка Амерді нижче.
Бергі

104

Станом на ECMAScript 6, ви можете використовувати Array.prototype.findдля цього. Це реалізовано та працює в Firefox (25.0), Chrome (45.0), Edge (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-shim . MDN також пропонує прошивку, якщо з якихось причин ви не хочете вставляти весь проект es6-shim у свій проект. Для максимальної сумісності вам потрібно es6-shim, тому що на відміну від версії MDN він виявляє вбудовані реальні помилки findта перезаписує їх (див. Коментар, який починається "Обхід помилок у Array # find and Array # findIndex" та рядки, що безпосередньо слідують за ним) .


findкраще, ніж filterоскільки findзупиняється негайно, коли він знаходить елемент, який відповідає умові, тоді як filterперебирає всі елементи, щоб надати всі відповідні елементи.
Ань Тран

59

Що з використанням фільтра та отримання першого індексу з отриманого масиву?

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

6
Продовжуйте використовувати методи es5. var результат = someArray.filter (isNotNullNorUndefined) .shift ();
someyoungideas

Хоча я сам голосував за пошук відповіді вище @Bergi, я думаю, що за допомогою деструктуризації ES6 ми можемо трохи покращитись: var [результат] = someArray.filter (isNotNullNorUndefined);
Накуль Манчанда

@someyoungideas ви могли б пояснити перевагу використання .shiftтут?
jakubiszon

3
@jakubiszon Перевага використання shiftполягає в тому, що він "виглядає розумним", але насправді є більш заплутаним. Хто б міг подумати, що дзвінок shift()без аргументів буде таким самим, як прийняття першого елемента? Незрозуміло ІМО. Доступ до масиву все одно швидший: jsperf.com/array-access-vs-shift
Josh M.

Також позначення квадратних дужок доступні як для об’єктів, так і для масивів в ES5 AFAIK, ніколи не бачивши перевагу .shift()над [0]явно зазначеним таким. Незважаючи на це, це альтернатива, яку ви можете вибрати, чи не використовувати, я б дотримувався цього [0].
SidOfc

15

На сьогодні вже повинно бути зрозуміло, що JavaScript не пропонує такого рішення спочатку; Ось найближчі два похідні, найкорисніший перший:

  1. Array.prototype.some(fn)пропонує бажану поведінку зупинки, коли виконується умова, але повертає лише те, чи присутній елемент; не важко застосувати якусь хитрість, наприклад, рішення, запропоноване відповіддю Бергі .

  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

Схоже на найбільш вичерпну відповідь. Чи можете ви додати третій підхід у своїй відповіді?
Грізний

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

Документація:


Використовуйте варіант підкреслення лише за потреби, тому що завантажувати бібліотеку саме для цього просто не варто
DannyFeliz

4

Станом на ES 2015, Array.prototype.find()передбачає саме таку функціональність.

Для веб-переглядачів, які не підтримують цю функцію, Мережа розробників Mozilla надала поліфайл (вставлений нижче):

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
Це не задовольняє висновку того, що вимагає запитувач (потрібен елемент у якомусь індексі, а не булевому).
Грем Робертсон

@GrahamRobertson $.inArrayне повертає булевого, він (на диво!) Повертає індекс першого відповідного елемента. Він все ще не робить те, про що просив ОП.
Марк Амері

-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

Він працює, закінчуючи filterза допомогою throw.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.