Map and filter an array at the same time

JavascriptArrays

Javascript Problem Overview


I have an array of objects that I want to iterate over to produce a new filtered array. But also, I need to filter out some of the objects from the new array depending of a parameter. I'm trying this:

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

Is that a good approach? Is there a better method? I'm open to use any library such as lodash.

Javascript Solutions


Solution 1 - Javascript

You should use Array.reduce for this.

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>


Alternatively, the reducer can be a pure function, like this

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

Solution 2 - Javascript

Since 2019, Array.prototype.flatMap is good option.

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

From the MDN page linked above:

> flatMap can be used as a way to add and remove items (modify the > number of items) during a map. In other words, it allows you to map > many items to many items (by handling each input item separately), > rather than always one-to-one. In this sense, it works like the > opposite of filter. Simply return a 1-element array to keep the item, > a multiple-element array to add items, or a 0-element array to remove > the item.

Solution 3 - Javascript

Use reduce, Luke!

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

Solution 4 - Javascript

With ES6 you can do it very short:

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

Solution 5 - Javascript

One line reduce with ES6 fancy spread syntax is here!

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

Solution 6 - Javascript

I'd make a comment, but I don't have the required reputation. A small improvement to Maxim Kuzmin's otherwise very good answer to make it more efficient:

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

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

console.log(filtered);

Explanation

Instead of spreading the entire result over and over for each iteration, we only append to the array, and only when there's actually a value to insert.

Solution 7 - Javascript

At some point, isn't it easier(or just as easy) to use a 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>

However it would be nice if there was a malter() or fap() function that combines the map and filter functions. It would work like a filter, except instead of returning true or false, it would return any object or a null/undefined.

Solution 8 - Javascript

Use Array.prototype.filter:

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

Solution 9 - Javascript

I optimized the answers with the following points:

  1. Rewriting if (cond) { stmt; } as cond && stmt;
  2. Use ES6 Arrow Functions

I'll present two solutions, one using forEach, the other using reduce:

Solution 1: Using forEach

The solution works by using forEach to iterate through every element. Then, in the body of the forEach loop, we have the conditional to act as a filter and it determines whether we are going to append something to the result array.

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

Solution 2: Using reduce

This solution uses Array.prototype.reduce instead of forEach to iterate through the array. It recognizes the fact that reduce has both an initializer and a looping mechanism built in. Other than that, this solution is more or less the same as the forEach solution, so, the difference comes down to cosmetic syntax sugar.

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

I leave it up to you to decide which solution to go for.

Solution 10 - Javascript

Using reduce, you can do this in one Array.prototype function. This will fetch all even numbers from an array.

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>

You can use the same method and generalize it for your objects, like this.

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

arr will now contain the filtered objects.

Solution 11 - Javascript

I've covert these great answers into utility functions and I'd like to share them:

Example: filter only odd numbers and increment it

  • e.g. [1, 2, 3, 4, 5] -filter-> [1, 3, 5] -map-> [2, 4, 6]

Normally you'd do it like this with filter and map

const inputArray = [1, 2, 3, 4, 5];
const filterOddPlusOne = inputArray.filter((item) => item % 2).map((item) => item + 1); // [ 2, 4, 6 ]

Using reduce
const filterMap = <TSource, TTarget>(
  items: TSource[],
  filterFn: (item: TSource) => boolean,
  mapFn: (item: TSource) => TTarget
) =>
  items.reduce((acc, cur): TTarget[] => {
    if (filterFn(cur)) return [...acc, mapFn(cur)];
    return acc;
  }, [] as TTarget[]);
Using flatMap
const filterMap = <TSource, TTarget>(
  items: TSource[],
  filterFn: (item: TSource) => boolean,
  mapFn: (item: TSource) => TTarget
) => items.flatMap((item) => (filterFn(item) ? [mapFn(item)] : []));
Usage (same for both reduce and flatMap solution):
const inputArray = [1, 2, 3, 4, 5];
const filterOddPlusOne = filterMap(
  inputArray,
  (item) => item % 2, // Filter only odd numbers
  (item) => item + 1 // Increment each number
); // [ 2, 4, 6 ]

JavaScript version

The above codes are in TypeScript but the question asks about JavaScript. So, I've remove all the generics and types for you:

const filterMap = (items, filterFn, mapFn) =>
  items.reduce((acc, cur) => {
    if (filterFn(cur)) return [...acc, mapFn(cur)];
    return acc;
  }, []);
const filterMap = (items, filterFn, mapFn) =>
  items.flatMap((item) => (filterFn(item) ? [mapFn(item)] : []));

Solution 12 - Javascript

Direct use of .reduce can be hard to read, so I'd recommend creating a function that generates the reducer for you:

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

Use it like so:

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'];

Solution 13 - Javascript

You can use Array.reduce with an arrow function is a single line of code

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

const reduced = options.reduce((result, option) => option.assigned ? result.concat({ name: option.name, newProperty: 'Foo' }) : result, []);

document.getElementById('output').innerHTML = JSON.stringify(reduced);

<h1>Only assigned options</h1>
<pre id="output"> </pre>

Solution 14 - Javascript

The most efficient way of doing filter + map at once is to process data as a generic iterable, and do both things at once. In this case, you will end up going through data once, at most.

The example below is using iter-ops library, and doing exactly that:

import {pipe, filter, map} from 'iter-ops';

const i = pipe(
    inputArray,
    filter(value => value === 123), // filter on whatever key you want
    map(value => /* any mapping here*/) // remap data as you like
);

// i = iterable that can be processed further;

console.log([...i]); //=> list of new objects

Above, I was saying at most, because if you apply further logic to the iterable result, like limit the number of mapped items, for example, you will end up iterating through your list of objects even less than once:

const i = pipe(
    inputArray,
    filter(value => value === 123), // filter on whatever key you want
    map(value => /* any mapping here*/), // remap as you like
    take(10) // take up to 10 items only
);

Above, we limit iteration further, to stop once 10 resulting items have been generated, and so we are iterating through data less than once. That's as efficient as it gets.

UPDATE

I was asked to add to the answer why this solution is more efficient than reduce, and so here it is...

Array's reduce is a finite operation, which goes through the complete set of data, in order to produce the result. So when you need to do further processing on that output data, you will end up producing a new iteration sequence, and so on.

When you have a complex business logic to be applied to a sequence/iterable, it is always much more efficient to chain that logic, while iterating through the sequence just once. In many cases, you will end up doing complex processing on a sequence, without going through the complete set of data even once. That's the efficiency of iterable data processing.

P.S. I'm the author of the aforesaid library.

Solution 15 - Javascript

Hey I`ve just working on this project nd wanted to share my solution based on Array.prototype.flatMap() on MDM docs:

 const places = [
 {latitude: 40,longitude: 1}, {latitude:41, longitude:2}, {latitude:44, longitude:2}, {latitude:NaN, longitude:NaN },{ latitude:45, longitude:4},{latitude:48, longitude:3}, {latitude:44, longitude:5}, {latitude:39, longitude:13}, {latitude:40, longitude:8}, {latitude:38, longitude:4}
 ]
 
 let items = places?.map((place) => [
    {
      latitude: (place.latitude),

      longitude:(place.longitude),
    },
  ]);
   console.log("Items: ", items);
   
   //Remove elements with NaN latitude and longitude
   
    let newItems = places?.flatMap((o) =>
    Number(o.longitude, o.latitude)
      ? { lng: Number(o.longitude), lat: Number(o.latitude) }
      : []
  ); 
  
 console.log("Coordinates after NaN values removed: ", newItems);

a

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDaniel Calderon MoriView Question on Stackoverflow
Solution 1 - JavascriptthefourtheyeView Answer on Stackoverflow
Solution 2 - JavascriptTrevor DixonView Answer on Stackoverflow
Solution 3 - JavascriptZukerView Answer on Stackoverflow
Solution 4 - JavascriptNati Lara-DiazView Answer on Stackoverflow
Solution 5 - JavascriptMaxim KuzminView Answer on Stackoverflow
Solution 6 - JavascriptmatsveView Answer on Stackoverflow
Solution 7 - JavascriptDanielView Answer on Stackoverflow
Solution 8 - JavascriptAmmarCSEView Answer on Stackoverflow
Solution 9 - JavascriptStephen QuanView Answer on Stackoverflow
Solution 10 - JavascriptBhargav PonnapalliView Answer on Stackoverflow
Solution 11 - JavascriptphwtView Answer on Stackoverflow
Solution 12 - JavascriptQuelklefView Answer on Stackoverflow
Solution 13 - JavascriptDerL30NView Answer on Stackoverflow
Solution 14 - Javascriptvitaly-tView Answer on Stackoverflow
Solution 15 - JavascriptcodingtimeView Answer on Stackoverflow