Làm cách nào để tìm phần tử đầu tiên của mảng khớp với điều kiện boolean trong JavaScript?


218

Tôi đang tự hỏi liệu có một cách được biết đến, tích hợp / thanh lịch để tìm phần tử đầu tiên của mảng JS phù hợp với một điều kiện nhất định không. Tương đương AC # sẽ là List.Find .

Cho đến nay tôi đã sử dụng kết hợp hai chức năng như thế này:

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

Và sau đó tôi có thể sử dụng:

var result = someArray.findFirst(isNotNullNorUndefined);

Nhưng vì có rất nhiều phương thức mảng kiểu chức năng trong ECMAScript , có lẽ có một cái gì đó ngoài kia đã như thế này? Tôi tưởng tượng rất nhiều người phải thực hiện những thứ như thế này mọi lúc ...


6
Không có phương thức tích hợp, nhưng có các thư viện tiện ích gần đúng chức năng này, chẳng hạn như documentcloud.github.com/underscore
kinakuta

Underscore.js trông rất đẹp thực sự! Và nó đã tìm thấy (). Cảm ơn!
Jakub P.

1
Chỉ để bạn biết, bạn có thể giảm điều này: return (typeof (o) !== 'undefined' && o !== null);xuống cái này return o != null;. Chúng chính xác tương đương.
vách đá điên loạn

1
Tốt để biết. Nhưng bạn biết đấy, tôi không tin các toán tử ép buộc như! = Hoặc ==. Tôi thậm chí sẽ không thể dễ dàng kiểm tra nó, vì tôi cần phải kiểm tra bằng cách nào đó rằng không có giá trị nào khác bị ép buộc thành null trong thời trang đó ... :) Thật may mắn làm sao tôi có một thư viện cho phép để tôi loại bỏ chức năng đó hoàn toàn ... :)
Jakub P.

Thành thật mà nói tôi phải nói đây là một giải pháp khá tao nhã. Thứ gần nhất tôi có thể tìm thấy là Array.prototype.some cố gắng tìm xem một phần tử nào đó có thỏa mãn một điều kiện nhất định mà bạn truyền cho nó dưới dạng hàm không. Thật không may, điều này trả về một boolean thay vì chỉ mục hoặc phần tử. Tôi muốn giới thiệu giải pháp của bạn cho việc sử dụng thư viện vì các thư viện có xu hướng lớn hơn nhiều và chứa những thứ bạn sẽ không sử dụng và tôi thích giữ mọi thứ nhẹ nhàng hơn (vì bạn chỉ có thể sử dụng một chức năng trong bộ phần mềm mà nó cung cấp).
Graham Robertson

Câu trả lời:


217

Vì ES6 có findphương thức riêng cho mảng; điều này dừng liệt kê mảng một khi nó tìm thấy kết quả khớp đầu tiên và trả về giá trị.

const result = someArray.find(isNotNullNorUndefined);

Câu trả lời cũ:

Tôi phải đăng câu trả lời để dừng những filterđề xuất này :-)

vì có rất nhiều phương thức mảng kiểu chức năng trong ECMAScript, có lẽ có một cái gì đó ngoài kia đã như thế này?

Bạn có thể sử dụng somephương thức Array để lặp lại mảng cho đến khi một điều kiện được đáp ứng (và sau đó dừng lại). Thật không may, nó sẽ chỉ trả về cho dù điều kiện được đáp ứng một lần, không phải bởi yếu tố nào (hoặc ở chỉ số nào) mà nó được đáp ứng. Vì vậy, chúng tôi phải sửa đổi nó một chút:

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
Không thể nói tôi hoàn toàn hiểu tất cả sự không thích hướng vào bộ lọc (). Nó có thể chậm hơn, nhưng trong thực tế; hầu hết các trường hợp có thể được sử dụng, đây là một danh sách nhỏ để bắt đầu và phần lớn các ứng dụng JavaScript không đủ phức tạp để thực sự lo lắng về hiệu quả ở cấp độ này. [] .filter (test) .pop () hoặc [] .filter (test) [0] là đơn giản, nguyên gốc và có thể đọc được. Tất nhiên tôi đang nói về các ứng dụng kinh doanh hoặc các trang web không phải là các ứng dụng chuyên sâu như trò chơi.
Josh Mc

11
Liệu các giải pháp lọc đi qua tất cả các mảng / bộ sưu tập? Nếu vậy, lọc rất không hiệu quả, bởi vì nó chạy trên tất cả các mảng ngay cả khi giá trị tìm thấy là giá trị đầu tiên trong bộ sưu tập. some()mặt khác, trả về ngay lập tức, nhanh hơn nhiều trong hầu hết các trường hợp so với các giải pháp lọc.
AlikElzin-kilaka

@ AlikElzin-kilaka: Vâng, chính xác.
Bergi

15
@JoshMc chắc chắn, nhưng thật vô nghĩa khi không hiệu quả một cách vô cớ khi đăng một giải pháp cho một vấn đề đơn giản ở đâu đó như Stack Overflow. Nhiều người sẽ sao chép và dán mã từ đây vào một chức năng tiện ích và đôi khi một số trong số họ sẽ kết thúc việc sử dụng chức năng tiện ích đó trong bối cảnh hiệu suất không thành vấn đề mà không nghĩ đến việc triển khai. Nếu bạn đã cho họ một cái gì đó có triển khai hiệu quả để bắt đầu, bạn đã giải quyết vấn đề về hiệu suất mà họ không có hoặc đã lưu cho họ một loạt thời gian để chẩn đoán nó.
Đánh dấu Amery

1
@SuperUberDuper: Không. Xem câu trả lời của Mark Amery bên dưới.
Bergi

104

Kể từ ECMAScript 6, bạn có thể sử dụng Array.prototype.findcho việc này. Điều này được triển khai và hoạt động trong Firefox (25.0), Chrome (45.0), Edge (12) và Safari (7.1), nhưng không có trong Internet Explorer hoặc một loạt các nền tảng cũ hoặc không phổ biến khác .

Ví dụ, biểu thức dưới đây ước tính 106.

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

Nếu bạn muốn sử dụng ngay bây giờ nhưng cần hỗ trợ cho IE hoặc các trình duyệt không hỗ trợ khác, bạn có thể sử dụng shim. Tôi đề nghị es6-shim . MDN cũng cung cấp một shim nếu vì lý do nào đó bạn không muốn đưa toàn bộ es6-shim vào dự án của mình. Để có khả năng tương thích tối đa, bạn muốn es6-shim, vì không giống như phiên bản MDN, nó phát hiện các triển khai gốc lỗi findvà ghi đè lên chúng (xem nhận xét bắt đầu "Làm việc xung quanh các lỗi trong Array # find và Array # find Index" và các dòng ngay sau nó) .


findlà tốt hơn so với filtertừ finddừng lại ngay lập tức khi nó tìm thấy một yếu tố phù hợp với điều kiện, trong khi filtervòng qua tất cả các yếu tố này để cung cấp cho tất cả các yếu tố phù hợp.
Anh Trần

59

Điều gì về việc sử dụng bộ lọc và nhận chỉ mục đầu tiên từ mảng kết quả?

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

6
Tiếp tục sử dụng các phương pháp es5. var result = someArray.filter (isNotNullNorUnd xác định) .shift ();
someyoungideas

Mặc dù bản thân tôi đã bỏ phiếu để tìm câu trả lời ở trên @Bergi, tôi nghĩ với việc phá hủy ES6, chúng tôi có thể cải thiện hơn một chút: var [result] = someArray.filter (isNotNullNorUnd xác định);
Nakul Manchanda

@someyoungideas bạn có thể giải thích lợi ích của việc sử dụng .shiftở đây không?
jakubiszon

3
@jakubiszon Lợi ích của việc sử dụng shiftlà nó "có vẻ thông minh" nhưng thực sự khó hiểu hơn. Ai có thể nghĩ rằng gọi shift()mà không có đối số sẽ giống như lấy phần tử đầu tiên? IMO không rõ ràng. Truy cập mảng nhanh hơn dù sao: jsperf.com/array-access-vs-shift
Josh M.

Ngoài ra, ký hiệu khung vuông có sẵn cho cả các đối tượng và mảng trong ES5 AFAIK, chưa bao giờ thấy một sở thích của .shift()trên [0]quy định rõ ràng như thế này. Mặc dù vậy, đó là một lựa chọn thay thế mà bạn có thể chọn sử dụng hoặc không, [0]mặc dù vậy tôi vẫn kiên trì .
SidOfc

15

Bây giờ, rõ ràng là JavaScript không cung cấp giải pháp nào như vậy; Đây là hai dẫn xuất gần nhất, đầu tiên hữu ích nhất:

  1. Array.prototype.some(fn)đưa ra hành vi mong muốn dừng lại khi một điều kiện được đáp ứng, nhưng chỉ trả về cho dù một phần tử có mặt hay không; không khó để áp dụng một số mánh khóe, chẳng hạn như giải pháp được đưa ra bởi câu trả lời của Berg .

  2. Array.prototype.filter(fn)[0]làm cho một lớp lót tuyệt vời nhưng kém hiệu quả nhất, bởi vì bạn vứt bỏ N - 1các yếu tố chỉ để có được những gì bạn cần.

Các phương thức tìm kiếm truyền thống trong JavaScript được đặc trưng bằng cách trả về chỉ mục của phần tử được tìm thấy thay vì chính phần tử hoặc -1. Điều này tránh phải chọn giá trị trả về từ miền của tất cả các loại có thể; một chỉ mục chỉ có thể là một số và các giá trị âm không hợp lệ.

Cả hai giải pháp trên đều không hỗ trợ tìm kiếm bù, vì vậy tôi đã quyết định viết bài này:

(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

Hình như câu trả lời toàn diện nhất. Bạn có thể thêm cách tiếp cận thứ ba trong câu trả lời của bạn?
Mrusful

13

Tóm lược:

  • Để tìm phần tử đầu tiên trong một mảng phù hợp với điều kiện boolean, chúng ta có thể sử dụng ES6 find()
  • find()được đặt trên Array.prototypeđể nó có thể được sử dụng trên mọi mảng.
  • find()thực hiện một cuộc gọi lại trong đó một booleanđiều kiện được kiểm tra. Hàm trả về giá trị (không phải chỉ mục!)

Thí dụ:

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

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

console.log(found);   //  56


8

Nếu bạn đang sử dụng, underscore.jsbạn có thể sử dụng chức năng findindexOfchức năng của nó để có được chính xác những gì bạn muốn:

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

Tài liệu:


Sử dụng tùy chọn underscorejs chỉ khi cần thiết vì tải một thư viện chỉ vì điều này không đáng
DanielFeliz

4

Kể từ ES 2015, Array.prototype.find()cung cấp chức năng chính xác này.

Đối với các trình duyệt không hỗ trợ tính năng này, Mozilla Developer Network đã cung cấp một polyfill (được dán bên dưới):

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
Một ví dụ thực tế với một mệnh đề điều kiện và một số từ giải thích sẽ làm cho câu trả lời của bạn có giá trị và dễ hiểu hơn.
SaschaM78

0

Tôi đã lấy cảm hứng từ nhiều nguồn trên internet để tìm ra giải pháp dưới đây. Muốn tính đến cả một số giá trị mặc định và cung cấp một cách để so sánh từng mục cho một cách tiếp cận chung mà điều này giải quyết.

Cách sử dụng: (đưa ra giá trị "Thứ hai")

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

Thực hiện:

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

Không có chức năng tích hợp trong Javascript để thực hiện tìm kiếm này.

Nếu bạn đang sử dụng jQuery, bạn có thể làm một jQuery.inArray(element,array).


Điều đó cũng sẽ hiệu quả, mặc dù tôi sẽ đi với Underscore :)
Jakub P.

3
Điều này không thỏa mãn đầu ra của những gì người hỏi yêu cầu (cần phần tử ở một số chỉ mục, không phải là boolean).
Graham Robertson

@GrahamRobertson $.inArraykhông trả về boolean, nó (đáng ngạc nhiên!) Trả về chỉ mục của phần tử khớp đầu tiên. Mặc dù vậy, nó vẫn không làm những gì OP yêu cầu.
Đánh dấu Amery

-2

Một cách ít thanh lịch hơn sẽ có throwtất cả các thông báo lỗi đúng (dựa trên Array.prototype.filter) nhưng sẽ dừng lặp lại trên kết quả đầu tiên là

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

Sau đó, ví dụ là

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

Nó hoạt động bằng cách kết thúc filterbằng cách sử dụng throw.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.