Push value to object if key already exists during Array.map - javascript

I have an array that looks like this:
let movies = [
'terminator.1',
'terminator.2',
'terminator.3',
'harry-potter.1',
'harry-potter.3',
'harry-potter.2',
'star-wars.1'
]
and I would like to have an object like this:
{
"terminator": [1,2,3],
"harry-potter": [1,2,3],
"star-wars": [1]
}
so far I'm able to have an object like this
{
{ terminator: [ '1' ] },
{ terminator: [ '2' ] },
{ terminator: [ '3' ] },
{ 'harry-potter': [ '1' ] },
{ 'harry-potter': [ '3' ] },
{ 'harry-potter': [ '2' ] },
{ 'star-wars': [ '1' ] }
}
I would like to know if there is a way to check during an Array.map when I'm generating my object if there is already a certain key and if there is to push the value to the corresponding array instead of creating a new key-value pair.
This is the code that I currently use for my solution. Thanks in advance.
let movies = [
'terminator.1',
'terminator.2',
'terminator.3',
'harry-potter.1',
'harry-potter.3',
'harry-potter.2',
'star-wars.1'
]
let t = movies.map(m => {
let [name, number] = [m.split('.')[0],m.split('.')[1]]
return {[name]: [number]}
})
console.log(t)

You can do this with a single Array.reduce and one array destructuring in order to get the key/value combination:
let movies = [ 'terminator.1', 'terminator.2', 'terminator.3', 'harry-potter.1', 'harry-potter.3', 'harry-potter.2', 'star-wars.1' ]
const result = movies.reduce((r,c) => {
let [k,v] = c.split('.')
r[k] = [...r[k] || [], +v]
return r
},{})
console.log(result)

const movies = ['terminator.1', 'terminator.2', 'terminator.3', 'harry-potter.1', 'harry-potter.3', 'harry-potter.2', 'star-wars.1']
const moviesMap = {}
movies.forEach(data => {
const [title, id] = data.split('.')
if (moviesMap[title]) {
moviesMap[title].push(id)
} else {
moviesMap[title] = [id]
}
})
console.log(moviesMap)

That's a job for Array#reduce, not Array#map:
let t = movies.reduce((acc, movie) => { // for each movie in movies
let [name, number] = movie.split('.'); // split the movie by "." and store the first part in name and the second in number
if(acc[name]) { // if the accumulator already has an entry for this movie name
acc[name].push(number); // then push this movie number into that entry's array
} else { // otherwise
acc[name] = [number]; // create an entry for this movie name that initially contains this movie number
}
return acc;
}, Object.create(null)); // Object.create(null) is better than just {} as it creates a prototypeless object which means we can do if(acc[name]) safely
Note: If you want to coerce the numbers into actual numbers and not keep them as strings, then use the unary + to implicitly convert them: +number.
Example:
let movies = [ 'terminator.1', 'terminator.2', 'terminator.3', 'harry-potter.1', 'harry-potter.3', 'harry-potter.2', 'star-wars.1' ];
let t = movies.reduce((acc, movie) => {
let [name, number] = movie.split(".");
if(acc[name]) {
acc[name].push(number);
} else {
acc[name] = [number];
}
return acc;
}, Object.create(null));
console.log(t);

Related

How to create multiple objects from array of entries in JavaScript?

I receive an array of entries from form using FormData(). It consists of information about the recipe. Like this:
const dataArr = [
['title', 'pizza'],
['image', 'url'],
['quantity-0', '1'],
['unit-0', 'kg'],
['description-0', 'Flour'],
['quantity-1', '2'],
['unit-1', 'tbsp'],
['description-1', 'Olive oil'],
... // more ingredients
];
which I need to reorganize in new object, like this:
const recipe = {
title: 'pizza',
image: 'url',
ingredients: [
{ quantity: '1', unit: 'kg', ingredient: 'Flour' },
{ quantity: '2', unit: 'tbsp', ingredient: 'Olive oil' },
...
],
};
So, for ingredients array I need to create multiple objects from received data. I came up with needed result, but it's not clean. I would appreciate your help coming up with universal function, when number of ingredients is unknown.
My solution: Form receives 6 ingredients max, therefore:
const ingredients = [];
// 1. Create an array with length of 6 (index helps to get ingredient-related data looping over the array)
const arrayOf6 = new Array(6).fill({});
arrayOf6.forEach((_, i) => {
// 2. In each iteration filter over all data to get an array for each ingredient
const ingArr = dataArr.filter(entry => {
return entry[0].startsWith(`unit-${i}`) ||
entry[0].startsWith(`quantity-${i}`) ||
entry[0].startsWith(`ingredient-${i}`);
});
// 3. Loop over each ingredient array and rename future properties
ingArr.forEach(entry => {
[key, value] = entry;
if(key.includes('ingredient')) entry[0] = 'description';
if(key.includes('quantity')) entry[0] = 'quantity';
if(key.includes('unit')) entry[0] = 'unit';
});
// 4. Transform array to object and push into ingredients array
const ingObj = Object.fromEntries(ingArr);
ingredients.push(ingObj);
});
// To finalize new object
const dataObj = Object.fromEntries(dataArr);
const recipe = {
title: dataObj.title,
image: dataObj.image,
ingredients,
};
You'll have to parse the values of the input array to extract the index. To build the result object, you could use reduce:
const dataArr = [['title', 'pizza'],['image', 'url'],['quantity-0', '1'],['unit-0', 'kg'],['description-0', 'Flour'],['quantity-1', '2'],['unit-1', 'tbsp'], ['description-1', 'Olive oil']];
const recipe = dataArr.reduce((recipe, [name, value]) => {
const [, prop, index] = name.match(/^(\w+)-(\d+)$/) ?? [];
if (prop) {
(recipe.ingredients[index] ??= {})[prop] = value;
} else {
recipe[name] = value;
}
return recipe;
}, { ingredients: [] });
console.log(recipe);
You don't need arrayOf6. You never use its elements for anything -- it seems like you're just using it as a replacement for a loop like for (let i = 0; i < 6; i++).
Just loop over dataArr and check whether the name has a number at the end. If it does, use that as an index into the ingredients array, otherwise use the name as the property of the ingredients object. Then you don't need to hard-code a limit to the number of ingredients.
const dataArr = [
['title', 'pizza'],
['image', 'url'],
['quantity-0', '1'],
['unit-0', 'kg'],
['description-0', 'Flour'],
['quantity-1', '2'],
['unit-1', 'tbsp'],
['description-1', 'Olive oil'],
// more ingredients
];
const recipe = {
ingredients: []
};
dataArr.forEach(([name, value]) => {
let match = name.match(/^(\w+)-(\d+)$/);
if (match) {
let type = match[1];
let index = match[2];
if (!recipe.ingredients[index]) {
recipe.ingredients[index] = {};
}
recipe.ingredients[index][type] = value;
} else {
recipe[name] = value;
}
});
console.log(recipe);
Separating the key-parsing logic helps me think about the concerns more clearly:
const orderedKeyRegExp = /^(.+)-(\d+)$/;
function parseKey (key) {
const match = key.match(orderedKeyRegExp);
// Return -1 for the index if the key pattern isn't part of a numbered sequence
if (!match) return {index: -1, name: key};
return {
index: Number(match[2]),
name: match[1],
};
}
function transformRecipeEntries (entries) {
const result = {};
const ingredients = [];
for (const [key, value] of entries) {
const {index, name} = parseKey(key);
if (index >= 0) (ingredients[index] ??= {})[name] = value;
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// Assign an empty object to the element of the array at the index
// (if it doesn't already exist)
else result[name] = value;
}
if (ingredients.length > 0) result.ingredients = ingredients;
return result;
}
const entries = [
['title', 'pizza'],
['image', 'url'],
['quantity-0', '1'],
['unit-0', 'kg'],
['description-0', 'Flour'],
['quantity-1', '2'],
['unit-1', 'tbsp'],
['description-1', 'Olive oil'],
// ...more ingredients
];
const result = transformRecipeEntries(entries);
console.log(result);

Create an Dynamic Array Object with key value pairs

I need to create an Dynamic key value pairs from the existing object
const balanceScheule = 1255;
const reqCost = [{Labour Cost: "1555"}, {Material Cost: "1575"}]; // key is dynamic and keeps on changing
const amfqtyCost = 1416;
Here the logic is to create an new array of object and subtract the amfqtyCost from reqCost
Logic i Have written
reqCost.forEach(element => {
const adjustedAmount = Object.entries(element).map((m) => {
let adjustedAmount = parseInt(m[1]) - amfqtyCost;
return adjustedAmount;
});
// console.log(...adjustedAmount)
});
this return 139 and 159 which is (1555 - 1416 = 139) and (1575 1416 = 159) respectively
Expected output :
[{Labour Cost: "139"}, {Material Cost: "159"}]
How to do i merge ?
You just need to return the updated object from within map function. Also for the outer iteration use map instead of forEach to return the final result
const balanceScheule = 1255;
const reqCost = [{
'Labour Cost': "1555",
}, {
'Material Cost': "1575",
}]; // key is dynamic and keeps on changing
const amfqtyCost = 1416;
const updatedData = reqCost.map(element => {
return Object.assign({}, ...Object.entries(element).map(([key, value]) => {
let adjustedAmount = parseInt(value) - amfqtyCost;
return {
[key]: String(adjustedAmount)
};
}));
});
console.log(updatedData);
You can do something like this:
const reqCost = [{
'Labour Cost': "1555"
}, {
'Material Cost': "1575"
}];
const amfqtyCost = 1416;
const adjustedCost = reqCost.map(cost => ({
[Object.keys(cost)[0]]: (parseInt(Object.values(cost)[0]) - amfqtyCost).toFixed(0)
}));
console.log(adjustedCost);
// OR, if you prefer to be a bit more verbose:
const adjustedCost2 = reqCost.map(cost => {
const [key, value] = Object.entries(cost)[0];
return {
[key]: (parseInt(value) - amfqtyCost).toFixed(0)
}
});
console.log(adjustedCost2);
You can reverse the Object.entries
{ key : value } => [ [ key, value ] ]
transformation by using Object.fromEntries
[ [ key, value ] ] => { key : value }
the code will look like this
reqCost.map((obj) =>
Object.fromEntries(
Object.entries(obj).map(([key, value]) => [
key,
parseInt(value) - amfqtyCost,
])
)
);

how to get array according to conditions in javascript

My array comes like this
var data=[{PRODUCT : P1}, {PRODUCT: P2}]
I wantt to convert this into [P1, P2].
Sometimes my array comes like this
var data=[{ITEM: I1, QUANTITY:1}, {ITEM: I2, QUANTITY:2}]
I wantt to convert this into [I1, I2].
so can we make a common function, where I just want to extract particular value of array and make a new array.
p.s. Thank you in advance
I tried to write the logic like this:
data.map((d, index) => { var result= [];
result.includes(d[0]); })
but it,s not dynamic
You could define a function which will always get the first value of the first object key, this should satisfy your needs based on the above
var data1 = [{
ITEM: 'I1',
QUANTITY: 1
}, {
ITEM: 'I2',
QUANTITY: 2
}]
var data2 = [{
PRODUCT: 'P1'
}, {
PRODUCT: ' P2'
}]
function getArrayOfValues(list) {
return list.reduce((acc, x) => {
const firstValue = Object.values(x)[0];
acc.push(firstValue)
return acc;
}, [])
}
const result1 = getArrayOfValues(data1)
console.log(result1)
const result2 = getArrayOfValues(data2)
console.log(result2)
function getProductOrItem(list) {
return list.reduce((accumulator, obj) => {
if (obj.PRODUCT) {
accumulator.push(obj.PRODUCT);
} else if (obj.ITEM) {
accumulator.push(obj.ITEM);
}
return accumulator;
}, [])
}
you can iterate through your array with map() method and inside it extract the value of a first entity of an object in your array and simply get a new array with all values:
const data1 =[{PRODUCT : 'P1'}, {PRODUCT: 'P2'}]
const data2 = [{ITEM: 'I1', QUANTITY: 1}, {ITEM: 'I2', QUANTITY: 2 }]
const transformValuesOfArray = (arrayToTransform) =>
arrayToTransform.map(value => {
const firstObjectValue = Object.values(value)[0]
return firstObjectValue
})
console.log(transformValuesOfArray(data1))
console.log(transformValuesOfArray(data2))

filtering non empty or undefined object value

Trying to filter out undefined object values.
const activeCard = _.pickBy(cards);
console output of activeCard shows the below object.
{ cardValue1:
[ { account_value: '4422444443333004',
country: 'US',
month: '01',
year: '2029',
confirmation: [Object] } ],
cardValue2: [ { account_value: undefined } ],
cardValue3: [ { account_value: undefined } ] }
I tried something like this which didnt work
const newObject = _.omitBy(activeCard, _.isNil);
// also tried to filter out at the level of _.pickBy which didnt work
const activeCard = _.pickBy(cards, (value) => { return value.length > 0; });
// output i am looking for is something like below
[ { account_value: '4422444443333004',
country: 'US',
month: '01',
year: '2029',
confirmation: [Object] } ]
// So basically, Im looking for output with 'undefined' object values filtered out.
use a custome function maybe?
function sanitize(a) {
let b = JSON.parse(JSON.stringify(a))
for (const key in b) {
if (Array.isArray(b[key])) {
if (_.isEmpty(b[key][0])) delete b[key]
}
}
return b;
}
const filteredActiveCard = sanitize(activeCard)
Use _.filter() and check with Array.some() (or lodash _.some()) if the property's array contains an account_value which is not nil. Then flatten the results to a single array.
const activeCard = {"cardValue1":[{"account_value":"4422444443333004","country":"US","month":"01","year":"2029","confirmation":[null]}],"cardValue2":[{}],"cardValue3":[{}]}
const result = _.flatten(_.filter(activeCard, arr =>
arr.some(o => !_.isNil(o.account_value))
))
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
With lodash/fp you can create a function that filters by iterating with _.some(), getting the account_value, and checking if it's not nil. Then flattens the results to a single array.
const { filter, some, flow, get, negate, isNil, flatten } = _
const fn = flow(
filter(some(flow(
get('account_value'),
negate(isNil)
))),
flatten
)
const activeCard = {"cardValue1":[{"account_value":"4422444443333004","country":"US","month":"01","year":"2029","confirmation":[null]}],"cardValue2":[{}],"cardValue3":[{}]}
const result = fn(activeCard)
console.log(result)
<script src='https://cdn.jsdelivr.net/g/lodash#4(lodash.min.js+lodash.fp.min.js)'></script>
Lodash makes this super easy.
const cards = {
cardValue1:
[ { account_value: '4422444443333004',
country: 'US',
month: '01',
year: '2029',
confirmation: [Object] } ],
cardValue2: [ { account_value: undefined } ],
cardValue3: [ { account_value: undefined } ]
}
const activeCards = _(cards)
.pickBy(v => v.length)
.values()
.flatten()
.filter('account_value')
console.log(activeCards);
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js"></script>

How to reduce an array with duplicate value?

I have an array like this
let oldArray=[
{type:16,img:['1']},
{type:16,img:['2']},
{type:16,img:['3']},
{type:17,img:['4']}
]
if the type is the same, i want to concat the value.
The result I want is:
let newArray=[
{type:16,img:['1','2','3']},
{type:17,img:['4']}
]
I tried to used reduce function:
oldArray.reduce((acc,cur,idx,src)=>{
if(cur.type===a[idx+1].type){
cur.img.concat(a[idx+1].img);
acc.push(cur)
} else {
acc.push(a[idx+1])
}
return acc
},[])
It seems that there is an error
Can anyone help? Thanks.
Alternative to Bibberty's solution:flatMap is much clearer than reduce
let newArray = [...new Set(oldArray.map(e => e.type))]
.map(e => {
return {
type: e,
img: (oldArray.filter(i => i.type === e).map(x => x.img)).reduce((acc,cur,idx,src)=>{
let length=src.length
let tep=cur.concat(src[idx+1]);
src[idx+1]=tep
return src[idx=length-1]
},[])
}
});
console.log(newArray);
You can use reduce:
let oldArray = [{type: 16,img: ['1']},{type: 16,img: ['2']},{type: 16,img: ['3']},{type: 17,img: ['4']}];
let newArray = oldArray.reduce((acc, curr) => {
acc.some(({
type
}) => type == curr.type) ? acc.find(({
type
}) => type == curr.type).img.push(curr.img[0]) : acc.push(curr);
return acc;
}, []);
console.log(newArray);
We use a Set and then a map.
The Set is populate with the unique types by using a map to extract.
We wrap in [] to give us an array the we then re map to build our object back.
The map then rebuilds our objects and note the use of filter and map to get the img values from the original host array.
let oldArray=[
{type:16,img:['1']},
{type:16,img:['2']},
{type:16,img:['3']},
{type:17,img:['4']}
]
let newArray = [...new Set(oldArray.map(e => e.type))]
.map(e => {
return {
type: e,
img: oldArray.filter(i => i.type === e).flatMap(x => x.img)
}
});
console.log(newArray);
This solution is not a reduce but return result you are looking for is the same
let oldArray = [
{type:16,img:['1']},
{type:16,img:['2']},
{type:16,img:['3']},
{type:17,img:['4']}
];
const transitoryMap = new Map();
for (const item of oldArray) {
if (!transitoryMap.has(item.type)) {
transitoryMap.set(item.type, [item.img[0]])
} else {
const value = transitoryMap.get(item.type)
value.push(item.img[0])
transitoryMap.set(item.type, value)
}
}
const newArray = [];
for (const item of transitoryMap.keys()) {
newArray.push({type:item,img:transitoryMap.get(item)})
}
console.log(newArray)
Here is an example using reduce. I have added a tracker to keep track of type in the newArray.
let oldArray = [
{type:16,img:['1']},
{type:16,img:['2']},
{type:16,img:['3']},
{type:17,img:['4']}
];
oldArray.reduce((a,c)=>{
let index = a.tracker.indexOf(c.type);
if(index === -1) {
a.tracker.push(c.type);
a.newArray.push({...c, img:[...c.img]});
} else {
a.newArray[index].img.push(...c.img);
}
return a;
},{tracker:[],newArray:[]}).newArray;
You might want to consider breaking up the processing into separate simple steps, for example:
Create a flattened object with the appropriate data.
build a new array with the wanted structure.
This will not only keep your code simple, but will allow you to focus on what your code is actually doing instead of how it is doing the task.
var oldArray=[
{type:16,img:['1']},
{type:16,img:['2']},
{type:16,img:['3']},
{type:17,img:['4']}
]
flattenMyObject = (arr) =>
arr.reduce((accum, current) => {
!!accum[current.type] ? accum[current.type].push(...current.img) : accum[current.type] = current.img;
return accum;
}, {});
buildNewArray = (type) => {
return {type: type, img: flattenedObject[type] }
}
Object
.keys(flattenMyObject(oldArray))
.map(buildNewArray);

Categories

Resources