Как найти первый элемент массива, соответствующий логическому состоянию в JavaScript?

Мне интересно, есть ли известный встроенный/элегантный способ найти первый элемент массива JS, соответствующий данному условию. С# эквивалент был бы 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, возможно, там что-то уже такое? Я предполагаю, что многие люди должны постоянно реализовывать такие вещи...

Ответ 1

Начиная с 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);

Ответ 2

Как и в 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 или других неподдерживаемых браузеров, вы можете использовать прокладку. Я рекомендую es6-shim. MDN также предлагает прокладку, если по какой-то причине вы не хотите вставлять весь проект es6 в свой проект. Для максимальной совместимости вы хотите использовать es6-shim, потому что, в отличие от версии MDN, она обнаруживает багги-исходные реализации find и перезаписывает их (см. Комментарий, который начинается "Работа над ошибками в Array # find и Array # findIndex", а строки сразу после него).

Ответ 3

Как насчет использования filter и получения первого индекса из результирующего массива?

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

Ответ 4

Теперь должно быть ясно, что JavaScript не предлагает такого решения изначально; вот самые близкие две производные, наиболее полезные сначала:

  • Array.prototype.some(fn) предлагает желаемое поведение остановки при выполнении условия, но возвращает только, присутствует ли элемент; не сложно применить некоторые обманы, такие как решение, предлагаемое ответом Берги.

  • 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

Ответ 5

Если вы используете underscore.js, вы можете использовать его функции find и indexOf, чтобы получить именно то, что вы хотите:

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

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

Ответ 6

Резюме:

  • Для нахождения первого элемента в массиве, который соответствует логическому условию, мы можем использовать 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

Ответ 7

Начиная с 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;
  };
}

Ответ 9

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

Ответ 10

У меня есть вдохновение из нескольких источников в Интернете, чтобы получить решение ниже. Хотел принять во внимание как некоторые значения по умолчанию, так и предоставить способ сравнения каждой записи для общего подхода, который решается.

Использование: (давая значение "Второй")

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

Ответ 11

В Javascript нет встроенной функции для выполнения этого поиска.

Если вы используете jQuery, вы можете сделать jQuery.inArray(element,array).

Ответ 12

Менее элегантным способом, который будет 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.