Làm thế nào để bỏ qua một phần tử trong .map ()?


418

Làm thế nào tôi có thể bỏ qua một phần tử mảng trong .map?

Mã của tôi:

var sources = images.map(function (img) {
    if(img.src.split('.').pop() === "json"){ // if extension is .json
        return null; // skip
    }
    else{
        return img.src;
    }
});

Điều này sẽ trở lại:

["img.png", null, "img.png"]

19
Bạn không thể, nhưng bạn có thể lọc ra tất cả các giá trị null sau đó.
Felix Kling

1
Tại sao không? Tôi biết việc sử dụng tiếp tục không hoạt động nhưng sẽ rất tốt để biết lý do tại sao (cũng sẽ tránh lặp vòng lặp) - chỉnh sửa - đối với trường hợp của bạn, bạn không thể đảo ngược điều kiện if và chỉ quay lại img.srcnếu kết quả của phép chia tách! = = json?
GrayedFox

@GrayedFox Sau đó, ẩn undefinedsẽ được đưa vào mảng, thay vì null. Không tốt hơn ...
FZ

Câu trả lời:


639

Chỉ cần .filter()nó đầu tiên:

var sources = images.filter(function(img) {
  if (img.src.split('.').pop() === "json") {
    return false; // skip
  }
  return true;
}).map(function(img) { return img.src; });

Nếu bạn không muốn làm điều đó, điều này không hợp lý vì nó có một số chi phí, bạn có thể sử dụng tổng quát hơn .reduce(). Bạn thường có thể diễn đạt .map()theo .reduce:

someArray.map(function(element) {
  return transform(element);
});

có thể được viết như

someArray.reduce(function(result, element) {
  result.push(transform(element));
  return result;
}, []);

Vì vậy, nếu bạn cần bỏ qua các yếu tố, bạn có thể thực hiện điều đó một cách dễ dàng với .reduce():

var sources = images.reduce(function(result, img) {
  if (img.src.split('.').pop() !== "json") {
    result.push(img.src);
  }
  return result;
}, []);

Trong phiên bản đó, mã trong .filter()mẫu đầu tiên là một phần của cuộc .reduce()gọi lại. Nguồn hình ảnh chỉ được đẩy lên mảng kết quả trong trường hợp hoạt động của bộ lọc sẽ giữ nó.


21
Điều này không yêu cầu bạn lặp lại toàn bộ mảng hai lần sao? Có cách nào để tránh điều đó?
Alex McMillan

7
@AlexMcMillan bạn có thể sử dụng .reduce()và thực hiện tất cả trong một lần, mặc dù hiệu suất khôn ngoan Tôi nghi ngờ nó sẽ tạo ra sự khác biệt đáng kể.
Mũi nhọn

9
Với tất cả những tiêu cực, "trống rỗng" giá trị kiểu ( null, undefined, NaNvv) nó sẽ là tốt nếu chúng ta có thể sử dụng một bên trong một map()như một chỉ báo rằng đối tượng này bản đồ không có gì và cần được bỏ qua. Tôi thường bắt gặp các mảng tôi muốn ánh xạ 98% (ví dụ: String.split()để lại một chuỗi trống duy nhất ở cuối mà tôi không quan tâm). Cảm ơn câu trả lời của bạn :)
Alex McMillan

6
@AlexMcMillan cũng .reduce()là một loại cơ bản của chức năng "làm bất cứ điều gì bạn muốn", bởi vì bạn có toàn quyền kiểm soát giá trị trả về. Bạn có thể quan tâm đến tác phẩm xuất sắc của Rich Hickey trong Clojure liên quan đến khái niệm đầu dò .
Mũi nhọn

3
@vsync bạn không thể bỏ qua một yếu tố với .map(). Tuy nhiên, bạn có thể sử dụng .reduce()thay thế, vì vậy tôi sẽ thêm nó.
Pointy

25

Tôi nghĩ rằng cách đơn giản nhất để bỏ qua một số phần tử từ một mảng là sử dụng phương thức filter () .

Bằng cách sử dụng phương thức này ( ES5 ) và cú pháp ES6, bạn có thể viết mã của mình thành một dòng và điều này sẽ trả về những gì bạn muốn :

let images = [{src: 'img.png'}, {src: 'j1.json'}, {src: 'img.png'}, {src: 'j2.json'}];

let sources = images.filter(img => img.src.slice(-4) != 'json').map(img => img.src);

console.log(sources);


1
đó chính xác là những gì .filter()đã được thực hiện cho
avalanche1

2
Điều này có tốt hơn forEachvà hoàn thành nó trong một lần thay vì hai không?
wuliwong

1
Như bạn muốn @wuliwong. Nhưng xin vui lòng lưu ý rằng điều này sẽ vẫn còn O(n)trong bệnh sởi phức tạp và xin vui lòng xem ít nhất hai bài viết này: frontendcollisionblog.com/javascript/2015/08/15/ trêncoderwall.com/p/kvzbpa/don-t- use-Array-foreach-use-for-thay Tất cả tốt nhất!
simhumileco

1
Cảm ơn bạn @simhumileco! Chính xác là vì điều đó, tôi ở đây (và có lẽ nhiều người khác nữa). Câu hỏi có lẽ là làm thế nào để kết hợp .filter và .map chỉ bằng cách lặp một lần.
Jack Black

21

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

images.flatMap(({src}) => src.endsWith('.json') ? [] : src);

Từ MDN :

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.


1
Trả lời tốt nhất xuống tay! Thêm thông tin tại đây: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
mẹo

1
Đây là câu trả lời thực sự, đơn giản và đủ mạnh. chúng tôi tìm hiểu điều này là tốt hơn so với bộ lọc và giảm.
bảo vệ orca

19

TLDR: Trước tiên, bạn có thể lọc mảng của mình và sau đó thực hiện bản đồ của mình nhưng điều này sẽ yêu cầu hai lượt đi trên mảng (bộ lọc trả về một mảng cho bản đồ). Vì mảng này là nhỏ, nó là một chi phí hiệu suất rất nhỏ. Bạn cũng có thể làm giảm đơn giản. Tuy nhiên, nếu bạn muốn tưởng tượng lại làm thế nào điều này có thể được thực hiện với một lần vượt qua mảng (hoặc bất kỳ kiểu dữ liệu nào), bạn có thể sử dụng một ý tưởng gọi là "bộ chuyển đổi" được Rich Hickey phổ biến.

Câu trả lời:

Chúng ta không nên yêu cầu tăng chuỗi chấm và vận hành trên mảng [].map(fn1).filter(f2)...vì cách tiếp cận này tạo ra các mảng trung gian trong bộ nhớ trên mỗireducing chức năng.

Cách tiếp cận tốt nhất hoạt động trên chức năng giảm thực tế nên chỉ có một lần truyền dữ liệu và không có mảng bổ sung.

Hàm khử là hàm được truyền vào reducevà lấy một bộ tích lũy và đầu vào từ nguồn và trả về một cái gì đó trông giống như bộ tích lũy

// 1. create a concat reducing function that can be passed into `reduce`
const concat = (acc, input) => acc.concat([input])

// note that [1,2,3].reduce(concat, []) would return [1,2,3]

// transforming your reducing function by mapping
// 2. create a generic mapping function that can take a reducing function and return another reducing function
const mapping = (changeInput) => (reducing) => (acc, input) => reducing(acc, changeInput(input))

// 3. create your map function that operates on an input
const getSrc = (x) => x.src
const mappingSrc = mapping(getSrc)

// 4. now we can use our `mapSrc` function to transform our original function `concat` to get another reducing function
const inputSources = [{src:'one.html'}, {src:'two.txt'}, {src:'three.json'}]
inputSources.reduce(mappingSrc(concat), [])
// -> ['one.html', 'two.txt', 'three.json']

// remember this is really essentially just
// inputSources.reduce((acc, x) => acc.concat([x.src]), [])


// transforming your reducing function by filtering
// 5. create a generic filtering function that can take a reducing function and return another reducing function
const filtering = (predicate) => (reducing) => (acc, input) => (predicate(input) ? reducing(acc, input): acc)

// 6. create your filter function that operate on an input
const filterJsonAndLoad = (img) => {
  console.log(img)
  if(img.src.split('.').pop() === 'json') {
    // game.loadSprite(...);
    return false;
  } else {
    return true;
  }
}
const filteringJson = filtering(filterJsonAndLoad)

// 7. notice the type of input and output of these functions
// concat is a reducing function,
// mapSrc transforms and returns a reducing function
// filterJsonAndLoad transforms and returns a reducing function
// these functions that transform reducing functions are "transducers", termed by Rich Hickey
// source: http://clojure.com/blog/2012/05/15/anatomy-of-reducer.html
// we can pass this all into reduce! and without any intermediate arrays

const sources = inputSources.reduce(filteringJson(mappingSrc(concat)), []);
// [ 'one.html', 'two.txt' ]

// ==================================
// 8. BONUS: compose all the functions
// You can decide to create a composing function which takes an infinite number of transducers to
// operate on your reducing function to compose a computed accumulator without ever creating that
// intermediate array
const composeAll = (...args) => (x) => {
  const fns = args
  var i = fns.length
  while (i--) {
    x = fns[i].call(this, x);
  }
  return x
}

const doABunchOfStuff = composeAll(
    filtering((x) => x.src.split('.').pop() !== 'json'),
    mapping((x) => x.src),
    mapping((x) => x.toUpperCase()),
    mapping((x) => x + '!!!')
)

const sources2 = inputSources.reduce(doABunchOfStuff(concat), [])
// ['ONE.HTML!!!', 'TWO.TXT!!!']

Tài nguyên: bài chuyển đổi hickey phong phú


17

Đây là một giải pháp thú vị:

/**
 * Filter-map. Like map, but skips undefined values.
 *
 * @param callback
 */
function fmap(callback) {
    return this.reduce((accum, ...args) => {
        let x = callback(...args);
        if(x !== undefined) {
            accum.push(x);
        }
        return accum;
    }, []);
}

Sử dụng với toán tử liên kết :

[1,2,-1,3]::fmap(x => x > 0 ? x * 2 : undefined); // [2,4,6]

1
Phương pháp này đã cứu tôi khỏi phải sử dụng riêng biệt map, filterconcatcác cuộc gọi.
LogicalBranch

11

Trả lời sans trường hợp cạnh thừa:

const thingsWithoutNulls = things.reduce((acc, thing) => {
  if (thing !== null) {
    acc.push(thing);
  }
  return acc;
}, [])

10

Tại sao không sử dụng vòng lặp forEach?

let arr = ['a', 'b', 'c', 'd', 'e'];
let filtered = [];

arr.forEach(x => {
  if (!x.includes('b')) filtered.push(x);
});

console.log(filtered)   // filtered === ['a','c','d','e'];

Hoặc thậm chí đơn giản hơn sử dụng bộ lọc:

const arr = ['a', 'b', 'c', 'd', 'e'];
const filtered = arr.filter(x => !x.includes('b')); // ['a','c','d','e'];

1
Tốt nhất sẽ là một vòng lặp đơn giản để lọc và tạo một mảng mới, nhưng đối với bối cảnh sử dụng, maphãy giữ cho nó giống như bây giờ. (là 4 năm trước tôi đã hỏi câu hỏi này, khi tôi không biết gì về tiền mã hóa)
Ismail

Đủ công bằng, cho rằng không có cách trực tiếp nào ở trên với bản đồ và tất cả các giải pháp đã sử dụng một phương pháp thay thế tôi nghĩ tôi sẽ sứt mẻ theo cách đơn giản nhất mà tôi có thể nghĩ để làm điều tương tự.
Alex

8
var sources = images.map(function (img) {
    if(img.src.split('.').pop() === "json"){ // if extension is .json
        return null; // skip
    }
    else{
        return img.src;
    }
}).filter(Boolean);

Các .filter(Boolean)sẽ lọc ra bất kỳ giá trị falsey trong một mảng nhất định, mà trong trường hợp của bạn là null.


3

Đây là một phương thức tiện ích (tương thích ES5) chỉ ánh xạ các giá trị không null (ẩn cuộc gọi để giảm):

function mapNonNull(arr, cb) {
    return arr.reduce(function (accumulator, value, index, arr) {
        var result = cb.call(null, value, index, arr);
        if (result != null) {
            accumulator.push(result);
        }

        return accumulator;
    }, []);
}

var result = mapNonNull(["a", "b", "c"], function (value) {
    return value === "b" ? null : value; // exclude "b"
});

console.log(result); // ["a", "c"]


1

Tôi sử dụng .forEachđể lặp lại và đẩy kết quả sang resultsmảng sau đó sử dụng nó, với giải pháp này tôi sẽ không lặp qua mảng hai lần


1

Để ngoại suy nhận xét của Felix Kling , bạn có thể sử dụng .filter()như thế này:

var sources = images.map(function (img) {
  if(img.src.split('.').pop() === "json") { // if extension is .json
    return null; // skip
  } else {
    return img.src;
  }
}).filter(Boolean);

Điều đó sẽ loại bỏ các giá trị falsey khỏi mảng được trả về bởi .map()

Bạn có thể đơn giản hóa nó hơn nữa như thế này:

var sources = images.map(function (img) {
  if(img.src.split('.').pop() !== "json") { // if extension is .json
    return img.src;
  }
}).filter(Boolean);

Hoặc thậm chí là một lớp lót sử dụng chức năng mũi tên, phá hủy đối tượng và &&toán tử:

var sources = images.map(({ src }) => src.split('.').pop() !== "json" && src).filter(Boolean);

0

Đây là phiên bản cập nhật của mã được cung cấp bởi @theprtk . Nó được dọn dẹp một chút để hiển thị phiên bản tổng quát trong khi có một ví dụ.

Lưu ý: Tôi sẽ thêm nhận xét này dưới dạng nhận xét vào bài đăng của anh ấy nhưng tôi chưa có đủ danh tiếng

/**
 * @see http://clojure.com/blog/2012/05/15/anatomy-of-reducer.html
 * @description functions that transform reducing functions
 */
const transduce = {
  /** a generic map() that can take a reducing() & return another reducing() */
  map: changeInput => reducing => (acc, input) =>
    reducing(acc, changeInput(input)),
  /** a generic filter() that can take a reducing() & return */
  filter: predicate => reducing => (acc, input) =>
    predicate(input) ? reducing(acc, input) : acc,
  /**
   * a composing() that can take an infinite # transducers to operate on
   *  reducing functions to compose a computed accumulator without ever creating
   *  that intermediate array
   */
  compose: (...args) => x => {
    const fns = args;
    var i = fns.length;
    while (i--) x = fns[i].call(this, x);
    return x;
  },
};

const example = {
  data: [{ src: 'file.html' }, { src: 'file.txt' }, { src: 'file.json' }],
  /** note: `[1,2,3].reduce(concat, [])` -> `[1,2,3]` */
  concat: (acc, input) => acc.concat([input]),
  getSrc: x => x.src,
  filterJson: x => x.src.split('.').pop() !== 'json',
};

/** step 1: create a reducing() that can be passed into `reduce` */
const reduceFn = example.concat;
/** step 2: transforming your reducing function by mapping */
const mapFn = transduce.map(example.getSrc);
/** step 3: create your filter() that operates on an input */
const filterFn = transduce.filter(example.filterJson);
/** step 4: aggregate your transformations */
const composeFn = transduce.compose(
  filterFn,
  mapFn,
  transduce.map(x => x.toUpperCase() + '!'), // new mapping()
);

/**
 * Expected example output
 *  Note: each is wrapped in `example.data.reduce(x, [])`
 *  1: ['file.html', 'file.txt', 'file.json']
 *  2:  ['file.html', 'file.txt']
 *  3: ['FILE.HTML!', 'FILE.TXT!']
 */
const exampleFns = {
  transducers: [
    mapFn(reduceFn),
    filterFn(mapFn(reduceFn)),
    composeFn(reduceFn),
  ],
  raw: [
    (acc, x) => acc.concat([x.src]),
    (acc, x) => acc.concat(x.src.split('.').pop() !== 'json' ? [x.src] : []),
    (acc, x) => acc.concat(x.src.split('.').pop() !== 'json' ? [x.src.toUpperCase() + '!'] : []),
  ],
};
const execExample = (currentValue, index) =>
  console.log('Example ' + index, example.data.reduce(currentValue, []));

exampleFns.raw.forEach(execExample);
exampleFns.transducers.forEach(execExample);

0

Bạn có thể sử dụng sau khi bạn phương pháp map(). Phương pháp filter()ví dụ trong trường hợp của bạn:

var sources = images.map(function (img) {
  if(img.src.split('.').pop() === "json"){ // if extension is .json
    return null; // skip
  }
  else {
    return img.src;
  }
});

Bộ lọc phương thức:

const sourceFiltered = sources.filter(item => item)

Sau đó, chỉ có các mục hiện có trong mảng mới sourceFiltered.

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.