Javascript Reduce Challenge, Getting the value of an Obj's Key - javascript

I'm following a course online and one of the challenges is this:
Write a function called extractKey which accepts two parameters, an array of objects, and the name of a key and returns an array with just the values for that key: You must use reduce.
extractKey([{name: "Elie", isInstructor:true},{name: "Tim", isInstructor:true},{name: "Matt", isInstructor:true}], "name");
// ["Elie", "Tim", "Matt"]
I thought I could do something like this:
function extractKey(arr, key) {
arr.reduce(function(a, b) {
console.log(a.push(b[key]))
}, [])
}
But its returning 1. I have a general idea of pushing each value into the empty array, but when console logging each value, a will return undefined at the next accumulator.

If you have to use reduce then I'd combine it with concat like this.
var arr = [{
name: "Elie",
isInstructor: true
}, {
name: "Tim",
isInstructor: true
}, {
name: "Matt",
isInstructor: true
}]
function extractKeys(array, key) {
return array.reduce((acc, obj) => {
return acc.concat(obj[key])
}, [])
}
console.log(extractKeys(arr, 'name'))

If you use reduce (not really neccessary here), you need to pass on the accumulator:
function extractKey(arr, key) {
arr.reduce(function(acc, el) {
console.log(a.push(el[key]))
return acc; // <--
}, [])
}
Alternatively a good old for loop might be easier to understand:
function extractKey(arr, key){
const result = [];
for(const el of arr)
result.push(el[key]);
return result;
}
Finally you also might use map instead and currying to beautify it:
const extract = key => obj => obj[key];
const result = original.map(extract("name"));

you were very close but with reduce you have to return the value after every iteration of your callback since only the accumulator's value is saved
function extractKey(arr, key) {
arr.reduce(function(a, b) {
a.push(b[key])
console.log(a)
return a;
}, []);
}

Related

Javascript turn a flat array into a nested object of keys with a value [duplicate]

Let's say I have the following array:
['product' , 'model', 'version']
And I would like to have an object such as:
{
product: {
model: {
version: {
}
}
}
}
However, that array is dynamic so it could have 2, 3 or fewer more items.
How can this be achieved in the most efficient way?
Thanks
Just turn it inside out and successively wrap an inner object into an outer object:
const keys = ['product', 'model', 'version'];
const result = keys.reverse().reduce((res, key) => ({[key]: res}), {});
// innermost value to start with ^^
console.log(result);
You can also do it with Array.prototype.reduceRight:
const result = ['product','model','version'].reduceRight((all, item) => ({[item]: all}), {});
console.log(result);
If I understood request correctly, this code might do what you need:
function convert(namesArray) {
let result = {};
let nestedObj = result;
namesArray.forEach(name => {
nestedObj[name] = {};
nestedObj = nestedObj[name];
});
return result;
}
console.log(convert(['a', 'b', 'c']));

Build object accessor from array in vanilla JS

Assuming I have the following array: const props = ["category", "category_name"] and the following object:
const obj = {
category: {
category_name: "some name",
}
}
how can I built a property accessor based on the array, so that I can access: "some name"?
I know there is a method in Lodash get in which you can pass in a path to get the object like 'category.category_name' so a simple props.join(".") would work here. But I want to not use Lodash.
Use forEach like that.
const props = ["category", "category_name"]
const obj = {
category: {
category_name: "some name",
}
}
function propertyAccessor(obj, props){
let value = obj;
props.forEach(name => value = value[name])
return value;
}
console.log(propertyAccessor(obj, props))
This is a good candidate for Array.prototype.reduce().
props.reduce((acc, prop) => acc?.[prop], obj)
The .reduce() method iterates over each element in the array that it is called on and passes both the current element and the value that the previous invocation resolved to, to the next iteration.
It is basically equivalent to
let acc = obj;
for (let prop of props) {
acc = acc[prop];
}
return acc;
I also added the optional chaining operator (?.) so that when the element can't be found, it returns undefined instead of throwing an error.
const obj = {
category: {
category_name: "some name",
}
};
const props = ["category", "category_name"];
const props2 = ["does_not_exist", "category_name"];
console.log(props.reduce((acc, prop) => acc?.[prop], obj));
console.log(props2.reduce((acc, prop) => acc?.[prop], obj));
// Without optional chaining operator
console.log(props.reduce((acc, prop) => acc[prop], obj));
console.log(props2.reduce((acc, prop) => acc[prop], obj));

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;
}, []);
}

Get array of keys based on values from another array

Say I have an array of objects that looks like this
let myArray = [
{item1: true},
{item2: false},
{item3: true},
{item4: false}
]
How would I iterate though this to return a new array of true values that looks like this:
let newArray = ['item1', 'item3']
I found this function but it only returns single items:
function findKey(map, term) {
var found = [];
for(var property in map) {
if(map.hasOwnProperty(property)) {
for(var key in map[property]) {
if(map[property].hasOwnProperty(key) && key === term) {
found.push(property);
}
}
}
}
return found;
}
Assuming myArray always contains objects with only 1 property.
let newArray = myArray
.map(item => Object.entries(item)[0])
.filter(([key, value]) => value)
.map(([key, value]) => key)
You could access the first key of each array item via Object.keys(), and use this to filter items with a true value for that first key, and then complete the process with a call to map() to transform the item to a value based on the same "first key" technique:
let myArray = [
{item1: true},
{item2: false},
{item3: true},
{item4: false}
]
let result = myArray
.filter(item => item[ Object.keys(item)[0] ] === true)
.map(item => Object.keys(item)[0])
console.log(result)
Use the function reduce to build the desired output. The handler of the function reduce will get the keys and check for each value === true.
This approach checks for the whole set of keys within an object. Further, this way you only use one loop.
let myArray = [{item1: true},{item2: false},{item3: true},{item4: false}],
result = myArray.reduce((a, c) => a.concat(Object.keys(c).filter(k => c[k] === true)), []);
console.log(result);
Something much optimized than the accepted answer would look like this:
const arr = [
{ item1: true },
{ item2: false },
{ item3: true },
{ item4: false }
]
const result = [];
const len = arr.length;
for (let i = 0; i < len; ++i) {
const obj = arr[i];
const key = Object.keys(obj)[0];
if(obj[key]) {
result.push(key);
}
}
console.log(result);
There is only one loop over the array, instead of map and filter which ends up looping twice.
Shortest
let newArray = myArray.map( x=>Object.keys(x)[0] ).filter( (k,i)=>myArray[i][k] );
In above solution first we use: map which works as for-loop to get array of keys (using Object.keys) ["item1", "item2", "item3", "item4"]. Then we filter that array by choose only those keys for which original array object has true. e.g myArray[0]["item1"] -> true (we use fact that filter funtion takes array element (k) and its index (i) which is the same for elements in myArray). In map and filter we use arrow functions.

Reduce Object Array to Single Object [duplicate]

I've read this answer on SO to try and understand where I'm going wrong, but not quite getting there.
I have this function :
get() {
var result = {};
this.filters.forEach(filter => result[filter.name] = filter.value);
return result;
}
It turns this :
[
{ name: "Some", value: "20160608" }
]
To this :
{ Some: "20160608" }
And I thought, that is exactly what reduce is for, I have an array, and I want one single value at the end of it.
So I thought this :
this.filters.reduce((result, filter) => {
result[filter.name] = filter.value;
return result;
});
But that doesn't produce the correct result.
1) Can I use Reduce here?
2) Why does it not produce the correct result.
From my understanding, the first iteration the result would be an empty object of some description, but it is the array itself.
So how would you go about redefining that on the first iteration - these thoughts provoke the feeling that it isn't right in this situation!
Set initial value as object
this.filters = this.filters.reduce((result, filter) => {
result[filter.name] = filter.value;
return result;
},{});
//-^----------- here
var filters = [{
name: "Some",
value: "20160608"
}];
filters = filters.reduce((result, filter) => {
result[filter.name] = filter.value;
return result;
}, {});
console.log(filters);
var filters = [{
name: "Some",
value: "20160608"
}];
filters = filters.reduce((result, {name, value}= filter) => (result[name] = value, result), {});
console.log(filters);
Since 2019 (ES2019) you can go with Object.fromEntries() but you need to map to array first.
const filtersObject = Object.fromEntries(filters.map(({ name, value }) => [name, value])

Categories