Ánh xạ và lọc một mảng cùng một lúc


155

Tôi có một mảng các đối tượng mà tôi muốn lặp đi lặp lại để tạo ra một mảng được lọc mới. Nhưng ngoài ra, tôi cần lọc ra một số đối tượng từ mảng mới tùy thuộc vào một tham số. Tôi đang thử cái này:

function renderOptions(options) {
    return options.map(function (option) {
        if (!option.assigned) {
            return (someNewObject);
        }
    });   
}

Đó có phải là một cách tiếp cận tốt? Có một phương pháp tốt hơn? Tôi đang mở để sử dụng bất kỳ thư viện như lodash.


Điều gì về một cách tiếp cận "object.keys"? developer.mozilla.org/fr/docs/Web/JavaScript/Reference/,
Julo0sS


3
.reduce()chắc chắn là nhanh hơn so với việc làm .filter(...).map(...)mà tôi thấy đề xuất ở nơi khác. Tôi đã thiết lập Thử nghiệm JSPerf để chứng minh stackoverflow.com/a/47877054/2379922
Justin L.

Câu trả lời:


218

Bạn nên sử dụng Array.reducecho việc này.

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

var reduced = options.reduce(function(filtered, option) {
  if (option.assigned) {
     var someNewValue = { name: option.name, newProperty: 'Foo' }
     filtered.push(someNewValue);
  }
  return filtered;
}, []);

document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>


Ngoài ra, bộ giảm tốc có thể là một chức năng thuần túy, như thế này

var reduced = options.reduce(function(result, option) {
  if (option.assigned) {
    return result.concat({
      name: option.name,
      newProperty: 'Foo'
    });
  }
  return result;
}, []);

2
Đối với tôi, đối số đầu tiên, filteredlà một đối tượng. vì vậy filtered.pushlà không xác định cho tôi.
Rahmathullah M

4
Tôi cũng có một vấn đề với filteredviệc là một đối tượng. Điều này là do tôi đã không chuyển vào "giá trị ban đầu" - mảng trống ( []) sau hàm giảm. ví dụ không var reduced = options.reduce(function(filtered, option) { ... }); chính xác var reduced = options.reduce(function(filtered, option) { ... }, []);
Jon Higgins

Không có lý do cho điều này reduceở đây, bạn cũng có thể sử dụng forEachvì trong mỗi lần lặp bạn thay đổi mảng filteredvà do đó nó không phải là chức năng thuần túy. Nó sẽ là sự kiện dễ đọc và nhỏ gọn hơn.
Marko

1
@Marko Tôi cũng giới thiệu một bộ giảm tốc tinh khiết. PTAL.
thefourtheye

Yap đó là tinh khiết bây giờ. +1 cho nỗ lực của bạn. Không phải tôi là người ủng hộ lập trình chức năng thuần túy, từ đó đến giờ, tôi chỉ không thể nhìn thấy điểm đó :) Nhưng thực sự tôi đã sử dụng mánh khóe của bạn sau khi nhìn thấy nó, bởi vì nó rất tiện trong tình huống đã cho. Nhưng đối với nhiệm vụ này, bạn có thể muốn xem chức năng FlatMap, tôi nghĩ rằng nó đã trở thành tiêu chuẩn sau khi bạn đưa ra câu trả lời của mình (cũng vì lý do đó có thể không được một số trình duyệt hỗ trợ). Nó sẽ được thực hiện nhiều hơn kể từ khi ghép các mảng như thế này làm cho nhiệm vụ O (n ^ 2) ra khỏi nhiệm vụ O (n).
Marko

43

Sử dụng giảm, Luke!

function renderOptions(options) {
    return options.reduce(function (res, option) {
        if (!option.assigned) {
            res.push(someNewObject);
        }
        return res;
    }, []);   
}

30

Kể từ năm 2019, Array.prototype.flatMap là lựa chọn tốt.

options.flatMap(o => o.assigned ? [o.name] : []);

Từ trang MDN được liên kết ở trên:

flatMapcó thể được sử dụng như một cách để thêm và xóa các mục (sửa đổi số lượng mục) trong bản đồ. Nói cách khác, nó cho phép bạn ánh xạ nhiều mục thành nhiều mục (bằng cách xử lý riêng từng mục đầu vào), thay vì luôn luôn một đối một. Theo nghĩa này, nó hoạt động giống như đối diện của bộ lọc. Chỉ cần trả về mảng 1 phần tử để giữ mục, mảng nhiều phần tử để thêm mục hoặc mảng 0 phần tử để xóa mục.


5
Giải pháp tuyệt vời! Nhưng, độc giả hãy nhớ rằng flatMapgần đây đã được giới thiệu và hỗ trợ trình duyệt của nó bị hạn chế . Bạn có thể muốn sử dụng polyfill / shims chính thức: FlatMapphẳng .
Arel

24

Với ES6 bạn có thể làm điều đó rất ngắn:

options.filter(opt => !opt.assigned).map(opt => someNewObject)


25
Điều này sẽ thực hiện hai vòng lặp, tùy thuộc vào những gì bộ lọc trả về vẫn có thể ăn hết bộ nhớ.
hogan

1
@oosean nhưng đây là câu trả lời đúng (có thể không phải là giải pháp tối ưu) cho truy vấn ban đầu
vikramvi

1
@vikramvi cảm ơn bạn đã lưu ý. Vấn đề là, chúng ta có thể đạt được những điều tương tự với hàng tấn cách và tôi rất thích cách tốt nhất.
hogan

10

Một dòng reducevới cú pháp lây lan ưa thích ES6 ở đây!

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

const filtered = options
  .reduce((result, {name, assigned}) => [...result, ...assigned ? [name] : []], []);

console.log(filtered);


1
Thực sự tốt đẹp @Maxim! Tôi upvote này! Nhưng ... trên mỗi phần tử được thêm vào, nó phải trải đều tất cả các phần tử trong result... giống như filter(assigned).map(name)giải pháp
0zkr PM

Đẹp một. Phải mất một giây tôi mới nhận ra chuyện gì đang xảy ra ...assigned ? [name] : []- có thể dễ đọc hơn...(assigned ? [name] : [])
johansenja

5

Tôi sẽ bình luận, nhưng tôi không có danh tiếng cần thiết. Một cải tiến nhỏ cho câu trả lời rất hay của Maxim Kuzmin để làm cho nó hiệu quả hơn:

const options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

const filtered = options
  .reduce((result, { name, assigned }) => assigned ? result.concat(name) : result, []);

console.log(filtered);

Giải trình

Thay vì trải rộng toàn bộ kết quả nhiều lần cho mỗi lần lặp, chúng tôi chỉ nối vào mảng và chỉ khi thực sự có giá trị để chèn.


3

Sử dụng Array.prototy.filter bản thân

function renderOptions(options) {
    return options.filter(function(option){
        return !option.assigned;
    }).map(function (option) {
        return (someNewObject);
    });   
}

2

Tại một số điểm, không dễ dàng hơn (hoặc dễ dàng như vậy) để sử dụng một forEach

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

var reduced = []
options.forEach(function(option) {
  if (option.assigned) {
     var someNewValue = { name: option.name, newProperty: 'Foo' }
     reduced.push(someNewValue);
  }
});

document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>

Tuy nhiên, sẽ rất tuyệt nếu có một malter()hoặc fap()chức năng kết hợp giữa các chức năng mapfilter. Nó sẽ hoạt động như một bộ lọc, ngoại trừ thay vì trả về true hoặc false, nó sẽ trả về bất kỳ đối tượng nào hoặc null / không xác định.


Bạn có thể muốn kiểm tra memes của mình ... ;-)
Arel

2

Tôi đã tối ưu hóa các câu trả lời với các điểm sau:

  1. Viết lại if (cond) { stmt; }nhưcond && stmt;
  2. Sử dụng các hàm mũi tên ES6

Tôi sẽ giới thiệu hai giải pháp, một sử dụng foreach , người kia sử dụng giảm :

Giải pháp 1: Sử dụng forEach

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];
var reduced = []
options.forEach(o => {
  o.assigned && reduced.push( { name: o.name, newProperty: 'Foo' } );
} );
console.log(reduced);

Giải pháp 2: Sử dụng giảm

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];
var reduced = options.reduce((a, o) => {
  o.assigned && a.push( { name: o.name, newProperty: 'Foo' } );
  return a;
}, [ ] );
console.log(reduced);

Tôi để nó cho bạn quyết định giải pháp nào để đi.


1
Tại sao trên trái đất bạn sẽ sử dụng cond && stmt;? Điều này khó đọc hơn nhiều và không mang lại lợi ích nào cả.
jlh

1

Sử dụng less, bạn có thể thực hiện điều này trong một hàm Array.prototype. Điều này sẽ lấy tất cả các số chẵn từ một mảng.

var arr = [1,2,3,4,5,6,7,8];

var brr = arr.reduce((c, n) => {
  if (n % 2 !== 0) {
    return c;
  }
  c.push(n);
  return c;
}, []);

document.getElementById('mypre').innerHTML = brr.toString();
<h1>Get all even numbers</h1>
<pre id="mypre"> </pre>

Bạn có thể sử dụng cùng một phương thức và khái quát nó cho các đối tượng của bạn, như thế này.

var arr = options.reduce(function(c,n){
  if(somecondition) {return c;}
  c.push(n);
  return c;
}, []);

arr bây giờ sẽ chứa các đối tượng được lọc.


0

Việc sử dụng trực tiếp .reducecó thể khó đọc, vì vậy tôi khuyên bạn nên tạo một hàm tạo bộ giảm tốc cho bạn:

function mapfilter(mapper) {
  return (acc, val) => {
    const mapped = mapper(val);
    if (mapped !== false)
      acc.push(mapped);
    return acc;
  };
}

Sử dụng nó như vậy:

const words = "Map and filter an array #javascript #arrays";
const tags = words.split(' ')
  .reduce(mapfilter(word => word.startsWith('#') && word.slice(1)), []);
console.log(tags);  // ['javascript', 'arrays'];
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.