Observable transformation - javascript

I'm very new to observables, this might be a silly question for others but I can't find a way to do it. How do I convert
Observable<Array<T>> where T is a TypeScript class with property Id(string) into an Array<string>? I want to convert the Id properties into a string array.

You are trying to do 2 things here:
Flatten all arrays from the source array into a single observable of T
Map each instance of T to Id property
You can use mergeAll to flatten the observable as you would use reduce on a normal JS array.
const source = Rx.Observable.of(
[{ Id: '1' }, { Id: '2' }],
[{ Id: '3' }, { Id: '4' }]
);
const flattened = source.mergeAll();
flattened.subscribe(s => console.log(s));
// => {Id:'1'}, {Id:'2'}, {Id:'3'}, {Id:'4'}
You can then use map (as with a normal JS array) to extract the Id property
const mapped = flattened.map(s => s.Id);
mapped.subscribe(s => console.log(s))
// => '1', '2', '3', '4'
Pull it all into 1 statement and...
source
.mergeAll()
.map(s => s.Id)
.subscribe(s => console.log(s))
// => '1', '2', '3', '4'

You can use reduce operator that like on javascript array. RxJS reduce operator will operate on the whole stream when it is completed.
See the example:
let data$ = new Rx.Subject();
data$
.reduce((acc, el) => {acc.push(...el); return acc;}, [])
.subscribe(x=>console.log(x));
data$.next([{id:'1'}, {id:'2'}]);
data$.next([{id:'3'}, {id:'4'}]);
data$.complete();

Related

typescript syntax - how to single line filter/map array [duplicate]

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.
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;
}, []);
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.
Use reduce, Luke!
function renderOptions(options) {
return options.reduce(function (res, option) {
if (!option.assigned) {
res.push(someNewObject);
}
return res;
}, []);
}
With ES6 you can do it very short:
options.filter(opt => !opt.assigned).map(opt => someNewObject)
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.
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);
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.
Use Array.prototype.filter:
function renderOptions(options) {
return options.filter(function(option){
return !option.assigned;
}).map(function (option) {
return (someNewObject);
});
}
I optimized the answers with the following points:
Rewriting if (cond) { stmt; } as cond && stmt;
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.
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.
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)] : []));
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'];
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>
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.
Hey I've just worked on this project and wanted to share my solution based on Array.prototype.flatMap() in MDN 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);
Same approach as the top answers, using Array.prototype.reduce(), but with updated ES6 syntax, and TypeScript typings, as a generic utility function:
function filterThenMap<T>(l: T[], predicate: (el: T) => boolean, transform: (el: T) => T) {
return l.reduce((res: T[], el) => {
if (predicate(el)) {
res.push(transform(el));
}
return res;
}, []);
}

Reduce number of operators in an RxJS mapping expression

I've create an Http request to get json data. Inside that json - there is an object which has an array. ( I need that array).
fromDb$ = of({
Result: {
Countries: [{ <--wanted array
ISOCode: 1,
Name: 'aaa'
}, {
ISOCode: 2,
Name: 'bbb'
}]
}
});
But- the data in the array has a different structure than I actually need.
I need to map (name &ISOcode) to (name and value )
This is what I've tried:
Use pluck to extract the inner Array
mergeMap the array object to a stream of objects (using of())
using map to transform each item to a desired structure
using toArray to wrap all to an array ( so I can bind it to a control)
Here is the actual code :
this.data = this.fromDb$.pipe(pluck<PtCountries, Array<Country>>('Result', 'Countries'),
mergeMap(a => from(a)),
map((c: Country) => ({
name: c.Name,
value: c.ISOCode,
})),
toArray());
The code does work and here is the online demo
Question
It looks like I've complicated it much more than it can be ,Is there a better way of doing it?
This line: mergeMap(a => from(a)) does not make a lot of sense. It's almost as if you did [1,2,3].map(v => v). You can just remove it.
To simplify this you basically need to use Array.map inside Observable.map.
Try this:
this.data = this.fromDb$.pipe(pluck<PtCountries, Array<Country>>('Result', 'Countries'),
map((countries: Country[]) => countries.map(country => ({
name: country.Name,
value: country.ISOCode,
}))));
Live demo
this.data = this.fromDb$.pipe(
mergeMap(object => object.Result.Countries),
map(country => ({ name: country.Name, value: country.ISOCode })),
toArray()
);

Set keyname with spread operator

Is it possible to dynamically set key name to spread operator?
For example I have:
'first, second, third'.split(',');
// Array(3) : [ 'first', 'second', 'third' ]
I want to have an object like this
{ 'first': 'first', 'second': 'second', 'third': 'third' }
By doing this right now I get:
{ ...'first, second, third'.split(',') };
// { 1: 'first', 2: 'second', 3: 'third' }
Can I dynamically set it or I have to iterate through and do it manually at this point?
I've ended up combine the two answers to use this:
const toObject = str => Object.assign(...str.split(/\s*,\s*/).map(key => ({ [key]: key })));
You could spread a list of key/value pairs into object assign:
Object.assign(...'first, second, third'.split(',').map(key => ({[key]: key})))
Jonas' solution is clever. I like it. Here's an alternative:
function toObject(str) {
const parts = str.split(/\s*,\s*/);
return parts.reduce((obj, part) => {
obj[part] = part;
return obj;
}, {});
}
console.log(toObject('first, second, third'));
Note that I use split(/\s*,\s*/) instead of split(',') to eliminate whitespace between parts.
This can be reduced to the following one-liner, if you're into that sort of thing:
const toObject = str =>
str.split(/\s*,\s*/).reduce((o, p) => (o[p] = p, o), {});
console.log(toObject('first, second, third'));

Get union of keys of all objects in js array using reduce

Assume , we have :
var all=[
{firstname:'Ahmed', age:12},
{firstname:'Saleh', children:5 }
{fullname: 'Xod BOD', children: 1}
];
The expected result is ['firstname','age', 'children', 'fullname']: the union of keys of all objects of that array:
all.map((e) => Object.keys(e) ).reduce((a,b)=>[...a,...b],[]);
This is work fine , However, i am seeking a solution more performance using directly reduce method without map , I did the following and it is failed.
all.reduce((a,b) =>Object.assign([...Object.keys(a),...Object.keys(b)]),[])
You can use Set, reduce() and Object.keys() there is no need for map.
var all=[
{firstname:'Ahmed', age:12},
{firstname:'Saleh', children:5 },
{fullname: 'Xod BOD', children: 1}
];
var result = [...new Set(all.reduce((r, e) => [...r, ...Object.keys(e)], []))];
console.log(result)
Here's a solution using generic procedures concat, flatMap, and the ES6 Set.
It's similar to #NenadVracar's solution but uses higher-order functions instead of a complex, do-it-all-in-one-line implementation. This reduces complexity in your transformation and makes it easier to re-use procedures in other areas of your program.
Not that ... spread syntax is bad, but you'll also notice this solution does not necessitate it.
var all = [
{firstname:'Ahmed', age:12},
{firstname:'Saleh', children:5 },
{fullname: 'Xod BOD', children: 1}
];
const concat = (x,y) => x.concat(y);
const flatMap = f => xs => xs.map(f).reduce(concat, []);
const unionKeys = xs =>
Array.from(new Set(flatMap (Object.keys) (xs)));
console.log(unionKeys(all));
// [ 'firstname', 'age', 'children', 'fullname' ]
Just out of curiosity, I've been benchmarking some solutions to your problem using different approaches (reduce vs foreach vs set). Looks like Set behaves well for small arrays but it's extremely slow for bigger arrays (being the best solution the foreach one).
Hope it helps.
var all = [{
firstname: 'Ahmed',
age: 12
}, {
firstname: 'Saleh',
children: 5
}, {
fullname: 'Xod BOD',
children: 1
}],
result,
res = {};
const concat = (x,y) => x.concat(y);
const flatMap = f => xs => xs.map(f).reduce(concat, []);
const unionKeys = xs =>
Array.from(new Set(flatMap (Object.keys) (xs)));
for(var i = 0; i < 10; i++)
all = all.concat(all);
console.time("Reduce");
result = Object.keys(all.reduce((memo, obj) => Object.assign(memo, obj), {}));
console.timeEnd("Reduce");
console.time("foreach");
all.forEach(obj => Object.assign(res, obj));
result = Object.keys(res);
console.timeEnd("foreach");
console.time("Set");
result = [...new Set(all.reduce((r, e) => r.concat(Object.keys(e)), []))];
console.timeEnd("Set");
console.time("Set2");
result = unionKeys(all);
console.timeEnd("Set2");
Try this code:
var union = new Set(getKeys(all));
console.log(union);
// if you need it to be array
console.log(Array.from(union));
//returns the keys of the objects inside the collection
function getKeys(collection) {
return collection.reduce(
function(union, current) {
if(!(union instanceof Array)) {
union = Object.keys(union);
}
return union.concat(Object.keys(current));
});
}

Map and filter an array at the same time

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.
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;
}, []);
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.
Use reduce, Luke!
function renderOptions(options) {
return options.reduce(function (res, option) {
if (!option.assigned) {
res.push(someNewObject);
}
return res;
}, []);
}
With ES6 you can do it very short:
options.filter(opt => !opt.assigned).map(opt => someNewObject)
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.
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);
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.
Use Array.prototype.filter:
function renderOptions(options) {
return options.filter(function(option){
return !option.assigned;
}).map(function (option) {
return (someNewObject);
});
}
I optimized the answers with the following points:
Rewriting if (cond) { stmt; } as cond && stmt;
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.
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.
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)] : []));
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'];
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>
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.
Hey I've just worked on this project and wanted to share my solution based on Array.prototype.flatMap() in MDN 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);
Same approach as the top answers, using Array.prototype.reduce(), but with updated ES6 syntax, and TypeScript typings, as a generic utility function:
function filterThenMap<T>(l: T[], predicate: (el: T) => boolean, transform: (el: T) => T) {
return l.reduce((res: T[], el) => {
if (predicate(el)) {
res.push(transform(el));
}
return res;
}, []);
}

Categories