I have this code:
const uniform = {
zone: 'BOTTOM'
}
const sizes = [
{
_id: 'sizeId2',
zones: ['BOTTOM'],
value: '48'
},
{
_id: 'sizeId3',
zones: ['BOTTOM'],
value: '42'
},
]
sizes.map((size) => (size.zones.includes(uniform.zone) ? {
_id: size._id,
value: size.value,
} : null))
https://jsfiddle.net/pmiranda/945fsdw7/5/
// here I'm trying another way than map and the ternary
console.log(sizes.filter((size) => (size.zones.includes(uniform.zone) && {
_id: size._id,
value: size.value,
})))
I wonder, how could I replace that map with a filter? Because I think that mapping with a ternary with null could be done in a better way, plus, in the map way I'm getting that null in the end, I want to not add it to the array
I think that you'd filter then map:
sizes.filter(size => size.zones.includes(uniform.zone)).map(size => ({
_id: size._id,
value: size.value,
}));
You probably need to return the result, or assign it to a variable.
const uniform = { zone: 'BOTTOM' }
const sizes = [
{
_id: 'sizeId2',
zones: ['BOTTOM'],
value: '48'
},
{
_id: 'sizeId3',
zones: ['BOTTOM'],
value: '42'
},
]
// reduce
const newSizes = sizes.reduce((acc, size) =>
{
if(size.zones.includes(uniform.zone)) {
acc.push(
{
_id: size._id,
value: size.value
})
}
return acc;
}, []
)
console.log(newSizes);
Using either map or filter does not make sense in the cuurent usecase as you are not utilizing the return value.
If you need to just loop over the array and create new entries (containing or nnot containing null) you can use forEach or reduce.
From the "comments", reduce is more suitable for you.
Without map and filter, just create new array
let newSizes = [];
sizes.forEach(({zones, _id, value}, key) => zones.includes(uniform.zone) && (newSizes[key] = {_id, value}))
Related
I have a very strange issue, and I'm not able to fix it.
I have this Json response from an API:
{
"type::new":"#000000",
"type::closed":"#000011",
"priority::low":"#FFFFFF",
"priority::normal":"#FF0000",
"priority::high":"#FFFF00",
"type::bug":"#001100"
}
I need to get, for example, only key that start with "type", so:
"type::new", "type::closed", "type::bug"
and assign each element to an array object with these values:
items: [
{
id: 'type::new',
value: 'type::new',
title: 'type::new'
},
{
id: 'type::closed',
value: 'type::closed',
title: 'type::closed'
},
{
id: 'type::bug',
value: 'type::bug',
title: 'type::bug'
}
]
I'm in a Svelte script, I tryed this but it not works (and I'm stopping only on get without filter them) [response is a result of fetch API):
const data = await response.json();
return data.map((item: any) => ({ id: item.key(), value: item.key(), title: item.key()}));
Thank you so much :)
As stated in my comment, you can't map() objects properties, but you can loop them over using Object.keys() or for...in.
const data = {
"type::new": "#000000",
"type::closed": "#000011",
"priority::low": "#FFFFFF",
"priority::normal": "#FF0000",
"priority::high": "#FFFF00",
"type::bug": "#001100"
}
//
// USING for...in
//
const results = []
for (let key in data) {
if (key.includes('type::')) {
results.push({ id: key, value: key, title: key })
}
}
console.log(results)
//
// USING Object.keys(), filter() AND map()
//
const results2 = Object.keys(data).filter(item => item.includes('type::')).map(key => {
return { id: key, value: key, title: key }
})
console.log(results2)
I have an array of objects. I want to reduce this array of objects based on the property category, so that I can get a total sum of each category. I wrote this function which returns an object:
const expenses =
[{
title: 'burger',
sum: 57,
category: 'eating_out',
notes: 'was good',
},
{
title: 'water bill',
sum: 142.81,
category: 'utilities',
notes: 'this sucks',
},
{
title: 'electricity bill',
sum: 112.90,
category: 'utilities',
notes: 'could be worse',
}]
const totalExpensesByCategory = expenses.reduce(
(acc, curr) => {
if (!acc[curr.category]) {
acc[curr.category] = curr.sum;
} else {
acc[curr.category] = +acc[curr.category] + curr.sum;
}
return acc;
},
{}
);
console.log(totalExpensesByCategory)
I want it to be an iterable object so that I can easily render components based on those entries.
How can I write it differently so that maybe it will return an array of objects that I can use map() on?
Object.entries() doesn't seem like the ideal way to do it.
Since you have valuable info in the object keys (the category) and similar in the values (the sum) you'll need both the key and the value from each object entry to create an array element. One approach is Object.entries, followed by map to create the individual objects that go into the output array.
const expenses =
[{
title: 'burger',
sum: 57,
category: 'eating_out',
notes: 'was good',
},
{
title: 'water bill',
sum: 142.81,
category: 'utilities',
notes: 'this sucks',
},
{
title: 'electricity bill',
sum: 112.90,
category: 'utilities',
notes: 'could be worse',
}]
const totalExpensesByCategory = Object.entries(expenses.reduce(
(acc, curr) => {
if (!acc[curr.category]) {
acc[curr.category] = curr.sum;
} else {
acc[curr.category] = +acc[curr.category] + curr.sum;
}
return acc;
},
{}
)).map(([k, v]) => ({category: k, sum: v}));
console.log(totalExpensesByCategory);
There's no reason not to build it up as an array of objects inside the reduce in the first place:
const totalExpensesByCategory = expenses.reduce(
(acc, curr) => {
const categoryIndex = acc.findIndex((category) => category.name === curr.category)
if (categoryIndex === -1) {
acc.push({name: curr.category, sum: curr.sum})
} else {
acc[categoryIndex].sum = acc[categoryIndex].sum + curr.sum;
}
return acc;
},
[]
);
Heres the output:
Just use Maps instead of objects. That's what they are for. Unlike objects, Maps are iterable:
const totalExpensesByCategory = expenses.reduce(
(m, curr) =>
m.set(curr.category, (m.get(curr.category) ?? 0) + curr.sum),
new Map
);
for (let [category, sum] of totalExpensesByCategory)
console.log(category, sum)
As a bonus, your reducer looks much nicer without curly braces and return
I have an array of products which in turn have an array of categories. I want to extract distinct values of the type property on category object.
Both the Lodash and native versions below do the job.
I want to make a generic function which takes the path of property and return unique values.
Essentially I am looking at something terse like
map(products, property("categories[].type") but heres the longer version(s)
import { compact, flatten, map, property, uniq } from "lodash";
export const getAllTypes1 = (products) => {
return uniq(
compact(map(flatten(map(products, property("categories"))), "type"))
);
};
export const getAllTypes2 = (products) => {
const types = [];
products.forEach((product) => {
product.categories.forEach((category) => {
if (!types.some((t) => t === category.type)) {
types.push(category.type);
}
});
});
return types;
};
Example data
const product1 = {
name: 'Wilson Orange',
price: 72.50,
categories: [{
type: 'flash sale',
discountable: false,
},{
type: 'tennis',
discountable: true,
}]
};
const product2 = {
name: 'Babolat Green',
price: 65.50,
categories: [{
type: 'tennis',
discountable: true,
}]
};
const products = [product1, product2];
Result
const result = getAllTypes2(products);
console.log(result); // ["flash sale", "tennis"]
Here's a working example
Here's a vanilla JS function that takes the path without needing [] and automatically checks arrays wherever it finds one.
How it works is:
Create an empty Set to easily remove duplicates
Turn the path string to an array of properties -> props
Call a recursive function recurse(currObj, props) which:
Checks if the currObj is an array, and if it is:
a. Recurses again with the array values as currObj
b. Use the same props since we didn't check an object in the path
Check if we're at the last prop in the path, if yes
a. Add the property's value in the current object to the set
Otherwise. recurse with currObj[currProp], and the remaining props
Convert the set to an array and return it.
const product1 = {
name: 'Wilson Orange',
price: 72.5,
categories: [
{
type: 'flash sale',
discountable: false,
},
{
type: 'tennis',
discountable: true,
},
],
};
const product2 = {
name: 'Babolat Green',
price: 65.5,
categories: [
{
type: 'tennis',
discountable: true,
},
],
};
const products = [product1, product2];
function getProperties(array, path) {
const props = path.split('.');
const values = new Set();
function recurse(currObj, props) {
const currProp = props[0]
const nextProps = props.slice(1);
if (Array.isArray(currObj)) {
for (let val of currObj) {
recurse(val, props);
}
return
}
if (nextProps.length === 0) {
values.add(currObj[currProp])
} else {
recurse(currObj[currProp], nextProps)
}
}
recurse(array, props);
return [...values];
}
console.log(getProperties(products,'categories.type'))
console.log(getProperties(products,'price'))
console.log(getProperties(products,'name'))
It's not a property path string, but it's pretty terse and expressive:
const pipe = (...fs) => fs.reduceRight(
(next, f) => x => f(x, next), x => x,
);
const getAllTypes = pipe(
(x, next) => [...new Set(x.flatMap(next))],
(x, next) => x.categories.map(next),
(x) => x.type,
);
// equivalent to
// const getAllTypes =
// x => [...new Set(x.flatMap(y => y.categories.map(z => z.type)))];
const products = [{
name: 'Wilson Orange',
price: 72.50,
categories: [{
type: 'flash sale',
discountable: false,
}, {
type: 'tennis',
discountable: true,
}]
}, {
name: 'Babolat Green',
price: 65.50,
categories: [{
type: 'tennis',
discountable: true,
}]
}];
console.log(getAllTypes(products));
The parameter x => x allows pipe to be called without any arguments and return the identity function. It also allows the last function argument of pipe to accept a next parameter for consistency, i.e. (x, next) => next(x.type) would have been equivalent to (x) => x.type.
Reference:
Array.prototype.flatMap
Array.prototype.map
Array.prototype.reduceRight
Set
My brain froze with this advanced filtering. This task has exceeded my basic knowledge of filter, map etc.
Here I have an array with nested objects with array:
const DATA = [
{
title: 'Spongebob',
data: [
{ id: 1, name: 'Mr Crabs' },
{ id: 2, name: 'Sandy' }
]
},
{
title: 'Dragon Balls Z',
data: [
{ id: 1, name: 'GoKu' },
{ id: 2, name: 'Zamasu' }
]
}
];
You may have seen this sort of style if you've worked with React Native (RN). This question is not for RN. I need to perform a filter on the name property in the nested array and when I get a match, I must return the format as the DATA variable.
const handleFiltering = (value) => {
const _value = value.toLowerCase();
const results = DATA.map(o => {
return o.data.filter(o => o.name.toLowerCase().indexOf(_value) != -1)
});
console.log(results);
};
My limited knowledge of deep filtering returns the basic filtering for the data array but need to retain the structure for DATA. The expected results I'd expect:
// I'm now querying for "ZAMASU"
const handleFiltering = (value='ZAMA') => {
const _value = value.toLowerCase();
const results = DATA.map(o => {
return o.data.filter(o => o.name.toLowerCase().indexOf(_value) != -1)
});
// console.log(results) should now be
// [
// {
// title: 'Dragon Balls Z',
// data: [
// { id: 2, name: 'Zamasu' }
// ]
// }
// ];
};
What comes to mind is the use of {...DATA, something-here } but my brain has frozen as I need to get back the title property. How to achieve this, please?
Another solution would be first use filter to find only objects containing the name in data passed through the argument, subsequently mapping data.
Here is your adjusted filter method
const handleFiltering = (value) => {
const _value = value.toLowerCase();
const results = DATA.filter((obj) =>
obj.data.some((character) => character.name.toLowerCase() === _value)
).map((obj) => ({
title: obj.title,
data: obj.data.filter(
(character) => character.name.toLowerCase() === _value
),
}));
console.log(results);
};
You can use reduce method of array. First find out the object inside data array and then add that to accumulator array as new entry by preserving the original structure.
const DATA = [
{
title: 'Spongebob',
data: [
{ id: 1, name: 'Mr Crabs', where: 'tv' },
{ id: 2, name: 'Sandy' }
]
},
{
title: 'Dragon Balls Z',
data: [
{ id: 1, name: 'GoKu' },
{ id: 2, name: 'Zamasu' }
]
}
];
let handleFiltering = (value='tv') => {
return DATA.reduce((acc,d) => {
let obj = d.data.find(a => a.name?.toLowerCase().includes(value.toLowerCase())
|| a.where?.toLowerCase().includes(value.toLowerCase()));
obj ? acc.push({...d, data:[obj]}) : null;
return acc;
}, []);
}
let result = handleFiltering();
console.log(result);
I have two arrays that have properties: minValue, maxValue, avgValue. I would like to write a very clean approach to identifying which objects match (the displayName) in the array and then assign the values of the new array to the old (minPrice, maxPrice, avgPrice).
I have this code so far
export interface Player {
displayName: string;
MinPrice: number;
MaxPrice: number;
AvgPrice: number;}
export const MERGE_PLAYER_STATS = (playerStat: Player[], auctionStat: Player[]): any => {
const reducer = (playerStat, auctionStat) => {
playerStat.MinPrice = auctionStat.minPrice,
playerStat.MaxPrice = auctionStat.maxPrice,
playerStat.AvgPrice = auctionStat.avgPrice,
playerStat.Team = auctionStat.team;
}
return reducer(playerStat, auctionStat =>
filter(auctionStat, playerStat => auctionStat.displayName.includes(playerStat.displayName)));
}
Input: two different set of player arrays that have common display Names.
playerStat: [] = [];
auctionStat: [] = [];
playerStat.push( {
displayName: "Josh Allen",
minPrice: "",
maxPrice: "",
avgPrice: ""
},
{
displayName: "No One",
minPrice: "",
maxPrice: "",
avgPrice: ""
});
auctionStat.push( {
displayName: "Josh Allen",
minPrice: 1,
maxPrice: 2,
avgPrice: 1
},
{
displayName: "No One 2",
minPrice: 1,
maxPrice: 1,
avgPrice: 2
});
The output should only have Josh Allen stats being updated from blank values to 1,2,1 respectively.
Please let me know what your clean approach this. FYI, this code is not returning what I want it to.
The simple approach would be to simply write a mapping function that copies in new objects based on matching keys.
Given an interface:
interface MyInterface {
key: string;
value: number;
}
And two arrays:
const existingArray: MyInterface[] = [
{ key: 'a', value: 1 },
{ key: 'b', value: 2 },
{ key: 'c', value: 3 }
];
const newArray: MyInterface[] = [
{ key: 'b', value: 20 },
{ key: 'c', value: 30 },
{ key: 'd', value: 40 }
];
Expected output
I want to update the existingArray with the values from newArray where the keys match.
If a value isn't found in newArray, the existing item is untouched.
If a new value is found in newArray, it isn't merged in to oldArray
All property values are to be overwritten
My expected output is:
const expected = [
{ key: 'a', value: 1 },
{ key: 'b', value: 20 },
{ key: 'c', value: 30 }
];
Solution
I would achieve this by using this merge function. It is just a javascript array map:
private merge<T>(existing: T[], updated: T[], getKey: (t: T) => string): T[] {
// start with the existing array items
return existing.map(item => {
// get the key using the callback
const key: string = getKey(item);
// find the matching item by key in the updated array
const matching: T = updated.find(x => getKey(x) === key);
if (matching) {
// if a matching item exists, copy the property values to the existing item
Object.assign(item, matching);
}
return item;
});
}
I have made it generic to allow you to use it with any type. All you need to do it provide the 2 arrays, and a callback to identify the key.
I would use it to created a merged array like this:
const merged = this.merge(existingArray, newArray, x => x.key);
DEMO: https://stackblitz.com/edit/angular-nn8hit
A word of warning
This wouldn't scale well with very large arrays. If you are using large arrays, I would optimise by creating indexes of type Map<string, T>, to keep the number of loops fixed. This implementation is out of scope of this question.
Edit:
If you only want to update specific values, you could pass in an update callback:
private merge<T>(existing: T[], updated: T[], getKey: (t: T) => string,
update: (item, matching) => void
): T[] {
return existing.map(item => {
const key: string = getKey(item);
const matching: T = updated.find(x => getKey(x) === key);
if (matching) {
update(item, matching);
}
return item;
});
}
And call it like this:
const merged = this.merge(existingArray, newArray, x => x.key,
(item, matching) => item.value = matching.value);
Use Object.assign()
Ex: oldArray = Object.assign(oldArray, newArray)
playerStat.forEach((initItem) => {
let result = auctionStat.filter((item) => (
initItem.displayName === item.displayName
))
// let me suppose the same displayName only has one
result.length && Object.assign(initItem, result[0])
})