Chia mảng theo chức năng lọc


92

Tôi có một mảng Javascript mà tôi muốn chia thành hai dựa trên việc một hàm được gọi trên mỗi phần tử trả về truehoặc false. Về cơ bản, đây là một array.filter, nhưng tôi cũng muốn có trong tay các yếu tố đã được lọc ra .

Hiện tại, kế hoạch của tôi là sử dụng array.forEachvà gọi hàm vị từ trên mỗi phần tử. Tùy thuộc vào việc điều này đúng hay sai, tôi sẽ đẩy phần tử hiện tại lên một trong hai mảng mới. Có cách nào thanh lịch hơn hoặc cách khác tốt hơn để làm điều này không? Ví dụ: một array.filternơi sẽ đẩy phần tử lên một mảng khác trước khi nó trả về false?


Nếu bạn có thể đăng một số mã mẫu, sẽ giúp cung cấp cho bạn câu trả lời tốt hơn!
Mark Pieszak - Trilon.io

Bất kể bạn sử dụng cách triển khai nào, Javascript sẽ luôn phải: lặp qua các mục, chạy hàm, đẩy mục vào một mảng. Tôi không nghĩ rằng có một cách để làm cho điều đó hiệu quả hơn.
TheZ

3
Bạn có thể làm bất cứ điều gì bạn muốn trong lệnh gọi lại được chuyển đến .filternhưng các tác dụng phụ như vậy rất khó theo dõi và hiểu. Chỉ cần lặp qua mảng và đẩy sang mảng này hoặc mảng khác.
Felix Kling,

Câu trả lời:


75

Với ES6, bạn có thể sử dụng cú pháp spread với giảm:

function partition(array, isValid) {
  return array.reduce(([pass, fail], elem) => {
    return isValid(elem) ? [[...pass, elem], fail] : [pass, [...fail, elem]];
  }, [[], []]);
}

const [pass, fail] = partition(myArray, (e) => e > 5);

Hoặc trên một dòng:

const [pass, fail] = a.reduce(([p, f], e) => (e > 5 ? [[...p, e], f] : [p, [...f, e]]), [[], []]);

8
Đối với tôi, phân vùng lodash hoặc chỉ foreach sẽ dễ dàng hơn để hiểu, nhưng ngay cả như vậy, công sức đẹp
Toni Leigh

15
Thao tác này sẽ tạo hai mảng mới cho mọi phần tử trong bản gốc. Trong khi một mảng sẽ chỉ có hai phần tử, thì mảng kia lớn dần theo kích thước của mảng. Do đó, điều này sẽ siêu chậm và lãng phí nhiều bộ nhớ. (Bạn có thể làm tương tự với một sự thúc đẩy và nó sẽ hiệu quả hơn.)
Stuart Schechter

Cảm ơn, đã tiết kiệm thời gian của tôi!
7urkm3n

Điều này thực sự hữu ích, braza. Lưu ý cho những người khác: có một số phiên bản dễ đọc hơn bên dưới.
Raffi

38

Bạn có thể sử dụng lodash.partition

var users = [
  { 'user': 'barney',  'age': 36, 'active': false },
  { 'user': 'fred',    'age': 40, 'active': true },
  { 'user': 'pebbles', 'age': 1,  'active': false }
];

_.partition(users, function(o) { return o.active; });
// → objects for [['fred'], ['barney', 'pebbles']]

// The `_.matches` iteratee shorthand.
_.partition(users, { 'age': 1, 'active': false });
// → objects for [['pebbles'], ['barney', 'fred']]

// The `_.matchesProperty` iteratee shorthand.
_.partition(users, ['active', false]);
// → objects for [['barney', 'pebbles'], ['fred']]

// The `_.property` iteratee shorthand.
_.partition(users, 'active');
// → objects for [['fred'], ['barney', 'pebbles']]

hoặc ramda.partition

R.partition(R.contains('s'), ['sss', 'ttt', 'foo', 'bars']);
// => [ [ 'sss', 'bars' ],  [ 'ttt', 'foo' ] ]

R.partition(R.contains('s'), { a: 'sss', b: 'ttt', foo: 'bars' });
// => [ { a: 'sss', foo: 'bars' }, { b: 'ttt' }  ]

16

Bạn có thể sử dụng giảm cho nó:

function partition(array, callback){
  return array.reduce(function(result, element, i) {
    callback(element, i, array) 
      ? result[0].push(element) 
      : result[1].push(element);

        return result;
      }, [[],[]]
    );
 };

Cập nhật. Sử dụng cú pháp ES6, bạn cũng có thể làm điều đó bằng cách sử dụng đệ quy:

function partition([current, ...tail], f, [left, right] = [[], []]) {
    if(current === undefined) {
        return [left, right];
    }
    if(f(current)) {
        return partition(tail, f, [[...left, current], right]);
    }
    return partition(tail, f, [left, [...right, current]]);
}

3
IMHO, giải pháp đầu tiên (đẩy) là hiệu suất tốt hơn khi kích thước mảng tăng lên.
ToolmakerSteve

@ToolmakerSteve bạn có thể giải thích thêm tại sao không? Tôi cũng đã đọc bình luận về câu trả lời hàng đầu nhưng vẫn bối rối tại sao
buncis

2
@buncis. Cách tiếp cận đầu tiên kiểm tra từng phần tử một lần, chỉ cần đẩy phần tử đó vào mảng thích hợp. Cách tiếp cận thứ hai xây dựng [...left, current]hoặc [...right, current]- cho mỗi phần tử. Tôi không biết chính xác bên trong, nhưng tôi chắc chắn rằng việc xây dựng sẽ tốn kém hơn so với việc chỉ đơn giản là đẩy một phần tử vào một mảng. Ngoài ra, theo nguyên tắc chung, đệ quy đắt hơn lặp lại , vì nó liên quan đến việc tạo một "khung ngăn xếp" mỗi lần.
ToolmakerSteve

14

Điều này nghe rất giống với phương pháp của RubyEnumerable#partition .

Nếu hàm không thể có hiệu ứng phụ (tức là nó không thể thay đổi mảng ban đầu), thì không có cách nào hiệu quả hơn để phân vùng mảng hơn là lặp lại từng phần tử và đẩy phần tử vào một trong hai mảng của bạn.

Điều đó được cho là "thanh lịch" hơn để tạo ra một phương pháp Arrayđể thực hiện chức năng này. Trong ví dụ này, hàm bộ lọc được thực thi trong ngữ cảnh của mảng ban đầu (tức là thissẽ là mảng ban đầu), và nó nhận phần tử và chỉ mục của phần tử dưới dạng đối số (tương tự như phương thức của jQueryeach ):

Array.prototype.partition = function (f){
  var matched = [],
      unmatched = [],
      i = 0,
      j = this.length;

  for (; i < j; i++){
    (f.call(this, this[i], i) ? matched : unmatched).push(this[i]);
  }

  return [matched, unmatched];
};

console.log([1, 2, 3, 4, 5].partition(function (n, i){
  return n % 2 == 0;
}));

//=> [ [ 2, 4 ], [ 1, 3, 5 ] ]

11
Đối với trình đọc hiện đại, VUI LÒNG không thêm phương thức vào các đối tượng thư viện tiêu chuẩn chung. Nó nguy hiểm và có khả năng bị ghi đè, dẫn đến hành vi bí ẩn và bị hỏng. Một hàm cũ đơn giản, có phạm vi phù hợp, an toàn hơn nhiều và việc gọi myFunc (mảng) không kém phần "thanh lịch" so với array.myFunc ().
Emmett R.

14

Tôi đến với anh chàng nhỏ bé này. Nó sử dụng cho từng và tất cả những thứ như bạn đã mô tả, nhưng theo ý kiến ​​của tôi thì nó trông sạch sẽ và ngắn gọn.

//Partition function
function partition(array, filter) {
  let pass = [], fail = [];
  array.forEach((e, idx, arr) => (filter(e, idx, arr) ? pass : fail).push(e));
  return [pass, fail];
}

//Run it with some dummy data and filter
const [lessThan5, greaterThanEqual5] = partition([0,1,4,3,5,7,9,2,4,6,8,9,0,1,2,4,6], e => e < 5);

//Output
console.log(lessThan5);
console.log(greaterThanEqual5);


1
Giải pháp này tốt hơn nhiều so với một số giải pháp có nhiều lượt ủng hộ hơn, IMHO. Dễ đọc, thực hiện một lần chuyển qua mảng và không phân bổ và phân bổ lại các mảng kết quả phân vùng. Tôi cũng thích rằng nó hiển thị tất cả ba giá trị bộ lọc chung cho chức năng bộ lọc (giá trị, chỉ mục và toàn bộ mảng). Điều này sẽ làm cho chức năng này được tái sử dụng nhiều hơn.
speckledcarp

Tôi cũng rất thích cái này vì sự đơn giản và sang trọng của nó. Phải nói rằng, tôi thấy nó chậm hơn đáng kể so với một forvòng lặp đơn giản như trong câu trả lời cũ của @qwertymk. Ví dụ: đối với một mảng chứa 100.000 phần tử, nó chậm gấp đôi trên hệ thống của tôi.
tromgy

9

Trong hàm bộ lọc, bạn có thể đẩy các mục sai của mình vào một biến khác bên ngoài hàm:

var bad = [], good = [1,2,3,4,5];
good = good.filter(function (value) { if (value === false) { bad.push(value) } else { return true});

Tất nhiên value === falsecần phải so sánh thực sự;)

Nhưng nó gần như hoạt động tương tự như vậy forEach. Tôi nghĩ bạn nên sử dụng forEachđể dễ đọc mã hơn.


Đồng ý với người đăng ở trên ... đặt loại logic này vào một chức năng bộ lọc cảm thấy hơi cồng kềnh và khó quản lý.
theUtherSide

Tôi nghĩ rằng bộ lọc có ý nghĩa, bạn muốn loại bỏ chúng khỏi mảng ban đầu để nó cho nó một bộ lọc
Mojimi

5

Dễ dàng để đọc một.

const partition = (arr, condition) => {
    const trues = arr.filter(el => condition(el));
    const falses = arr.filter(el => !condition(el));
    return [trues, falses];
};

// sample usage
const nums = [1,2,3,4,5,6,7]
const [evens, odds] = partition(nums, (el) => el%2 == 0)

6
Nhược điểm là bạn thực hiện 2 vòng thay vì chỉ một
Laurent

ủng hộ tính dễ đọc, đối với mảng nhỏ, cái này là bằng chứng rõ ràng hơn trong tương lai
allan.simon

4

Thử đi:

function filter(a, fun) {
    var ret = { good: [], bad: [] };
    for (var i = 0; i < a.length; i++)
        if (fun(a[i])
            ret.good.push(a[i]);
        else
            ret.bad.push(a[i]);
    return ret;
}

BẢN GIỚI THIỆU


Chỉ ra rằng chức năng lọc của mảng là không thực sự hỗ trợ trong tất cả các trình duyệt (Tôi đang nhìn bạn IES TRỞ LÊN)
TheZ

4

Cái này thì sao?

[1,4,3,5,3,2].reduce( (s, x) => { s[ x > 3 ].push(x); return s;} , {true: [], false:[]} )

Có lẽ điều này hiệu quả hơn sau đó toán tử spread

Hoặc ngắn hơn một chút, nhưng xấu hơn

[1,4,3,5,3,2].reduce( (s, x) => s[ x > 3 ].push(x)?s:s , {true: [], false:[]} )


2

Rất nhiều câu trả lời ở đây sử dụng Array.prototype.reduceđể xây dựng một bộ tích lũy có thể thay đổi và chỉ ra một cách chính xác rằng đối với các mảng lớn, điều này hiệu quả hơn, giả sử, sử dụng toán tử spread để sao chép một mảng mới mỗi lần lặp lại. Nhược điểm là nó không đẹp như một biểu thức "thuần túy" sử dụng cú pháp lambda ngắn.

Nhưng một cách giải quyết đó là sử dụng toán tử dấu phẩy. Trong các ngôn ngữ giống C, dấu phẩy là một toán tử luôn trả về toán hạng bên phải. Bạn có thể sử dụng nó để tạo một biểu thức gọi một hàm void và trả về một giá trị.

function partition(array, predicate) {
    return array.reduce((acc, item) => predicate(item)
        ? (acc[0].push(item), acc)
        : (acc[1].push(item), acc), [[], []]);
}

Nếu bạn tận dụng thực tế là biểu thức boolean chuyển ngầm thành một số là 0 và 1, và bạn có thể làm cho nó ngắn gọn hơn nữa, mặc dù tôi không nghĩ rằng nó có thể đọc được:

function partition(array, predicate) {
    return array.reduce((acc, item) => (acc[+!predicate(item)].push(item), acc), [[], []]);
}

Sử dụng:

const [trues, falses] = partition(['aardvark', 'cat', 'apple'], i => i.startsWith('a'));
console.log(trues); // ['aardvark', 'apple']
console.log(falses); // ['cat']

0

Phân vùng MỘT LINER

const partition = (a,f)=>a.reduce((p,q)=>(p[+!f(q)].push(q),p),[[],[]]);

BẢN GIỚI THIỆU

// to make it consistent to filter pass index and array as arguments
const partition = (a, f) =>
    a.reduce((p, q, i, ar) => (p[+!f(q, i, ar)].push(q), p), [[], []]);

console.log(partition([1, 2, 3, 4, 5], x => x % 2 === 0));
console.log(partition([..."ABCD"], (x, i) => i % 2 === 0));

Đối với chữ viết

const partition = <T>(
  a: T[],
  f: (v: T, i?: number, ar?: T[]) => boolean
): [T[], T[]] =>
  a.reduce((p, q, i, ar) => (p[+!f(q, i, ar)].push(q), p), [[], []]);

-1

Tôi đã kết thúc việc này vì nó dễ hiểu (và được đánh máy đầy đủ bằng bản đánh máy).

const partition = <T>(array: T[], isValid: (element: T) => boolean): [T[], T[]] => {
  const pass: T[] = []
  const fail: T[] = []
  array.forEach(element => {
    if (isValid(element)) {
      pass.push(element)
    } else {
      fail.push(element)
    }
  })
  return [pass, fail]
}

// usage
const [pass, fail] = partition([1, 2, 3, 4, 5], (element: number) => element > 3)
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.