How do I add a value from one array into another array to create a new array?
I have two arrays and I want to filter through arrayTwo find where id === productID from arrayOne. Then add quantity from arrayOne to arrayTwo so I can get a result such as arrayThree
arrayOne = [
{
productID: "DuWTLdYkpwF1DJ2x8SGB",
quantity: 2
},
]
arrayTwo = [
{
id: "DuWTLdYkpwF1DJ2x8SGB",
minQuantity: 1,
name: "5 Shade Palette",
price: "950",
size: "30g",
unitPrice: 950,
},
]
Wanted result::
arrayThree = [
{
id: "DuWTLdYkpwF1DJ2x8SGB",
minQuantity: 1,
name: "5 Shade Palette",
price: "950",
size: "30g",
unitPrice: 950,
quantity: 2,
},
]
Below is one possible way to achieve the target.
Code Snippet
// add "quantity" to existing products
const addDeltaToBase = (delta, base) => (
// iterate over the "base" (ie, existing product array)
base.map(
({ id, ...rest }) => { // de-structure to access "id"
// check if "id" is part of the delta (to update "quantity")
const foundIt = delta.find(({ productID }) => productID === id);
if (foundIt) { // found a match, so update "quantity
return ({
id, ...rest, quantity: foundIt.quantity
})
};
// control reaches here only when no match. Return existing data as-is
return { id, ...rest }
}
) // implicit return from "base.map()"
);
const arrayOne = [
{
productID: "DuWTLdYkpwF1DJ2x8SGB",
quantity: 2
},
];
const arrayTwo = [
{
id: "DuWTLdYkpwF1DJ2x8SGB",
minQuantity: 1,
name: "5 Shade Palette",
price: "950",
size: "30g",
unitPrice: 950,
},
];
console.log(addDeltaToBase(arrayOne, arrayTwo));
.as-console-wrapper { max-height: 100% !important; top: 0 }
Explanation
Inline comments added in the snippet above.
NOTE
This answer will be able to handle both arrayOne & arrayTwo with multiple objects.
It matches the productId with the id and when matched, it merges the quantity into the output (ie, arrayThree).
It aims to be immutable so the input arrays may remain as-is
Time complexity is O(n^2) here. If the given arrays are really long, not the best option. Basically: for each item in arrayOne, find its pair in arrayTwo and merge them.
let arrayThree = arrayOne.map(first => {
return {
...first,
...arrayTwo.find(second => second.id == first.productID)
}
});
You can merge the two objects easily by using the spread operator:
arrayOne = [
{
productID: "DuWTLdYkpwF1DJ2x8SGB",
quantity: 2
},
]
arrayTwo = [
{
id: "DuWTLdYkpwF1DJ2x8SGB",
minQuantity: 1,
name: "5 Shade Palette",
price: "950",
size: "30g",
unitPrice: 950,
},
]
console.log({...arrayOne[0], ...arrayTwo[0]})
Use this in combination with your initial filter and you should have what you want. However I would advise to use 'find()' instead.
This will look something like this:
// Loop every item of one array
arrayOne.forEach( (product) => {
// Find linked product
let productToMerge = arrayTwo.find(p => p.productID === product.productID)
// Let's merge them
let newItem = {...product, ...productToMerge}
})
Now it's just a matter of pushing this newItem in an array to collect all new items.
Spread
Find
Related
There are two array of objects and i want to filter two values by iterating differently to get two different id.
Here is the example
1st array : list_of_products: [ { text: "Shoes", value: 1},{text:"Clothing", value: 2},{text:"Foods", value: 3}]
2nd Array: list_of_names: [{ text: "jim" , value: 1},{text:"Sim", value: 2},{text:"Tim",value:3}]
Now, i want to get the ids by filtering out two arrays based on names like this
product_name: "Clothing", person_name:"Tim"
Then i want to store the ids like this
const newIds = { product_id: 2,name_id: 3}
This i have tried:
const newProduct_name = list_of_products.find(name => name.text === product_name);
const newName = list_of_names.find(name => name.text === person_name);
storing it into new object like this
const values = {product_id: newProduct_name.value ,name_id: newName.value}
How to do this by minimal use of variables and faster execution?
Creating Maps or objects using the lookup values as keys lets you iterate each of your source arrays once and then have o(1) searches rather than using find() to iterate each array many times
const products= [ { text: "Shoes", value: 1},{text:"Clothing", value: 2},{text:"Foods", value: 3}],
names= [{ text: "jim" , value: 1},{text:"Sim", value: 2},{text:"Tim",value:3}];
const createMap = (arr) => new Map(arr.map(o => [o.text, o.value])),
prodMap = createMap(products),
namesMap = createMap(names);
const data = [{
product_name: "Clothing",
person_name: "Tim"
}];
const res = data.map(o => {
return {
product_id: prodMap.get(o.product_name),
name_id: namesMap.get(o.person_name)
};
})
console.log(res)
I have a problem I am working on solving and am having trouble coming up with a good solution.
I have some data which looks like so:
[
{ id: 0, dad: 'Carl', mum: 'Amanda', cat: 'Mittens' },
{ id: 1, dad: 'Ron', mum: 'Amanda', cat: 'Scratch' },
{ id: 2, dad: 'Carl', mum: 'Lucy', cat: 'Tiddles' },
{ id: 3, dad: 'Barry', mum: 'Florence', cat: 'Nyanko' },
{ id: 4, dad: 'Barry', mum: 'Florence', cat: 'Fluffy' },
{ id: 5, dad: 'Carl', mum: 'Stefanie', cat: 'Snuggles' },
...
]
I want to group together entries which share a common dad, mum or cat name. So I end up with groups such that any member of group 1 doesn't share either a dad, mum or cat's name with any member of any of the other groups. A member of group 1 shares either a dad, mum or cat's name with each of the members of it's group.
I have gone about it by grouping them first by each category like so:
const groupedByDad = groupBy(data, ({dad}) => dad);
const groupedByMum = groupBy(data, ({mum}) => mum);
const groupedByCat = groupBy(data, ({cat}) => cat);
const groups = [groupedByDad, groupedByMum, groupedByCat]; // array of groups
I then extract out just the Ids as I don't need the extra data anymore, just which ids belong in which group.
// in next block I extract just the IDs from the original data
groups.forEach((group) => {
Object.entries(group).forEach(([key, grouping]) => {
group[key] = grouping.map(({id}) => id);
});
});
I've then come up with a solution where I put all the arrays of Ids in 1 object and iterate through the arrays of ids, for each array I find all arrays of ids which intersect the first and group them together in a new array and delete them from the array of arrays. I then proceed to the next remaining array and repeat until the original array of arrays is empty. The problem with this is:
1: it's slow
2: If I have the following arrays of ids in the array of arrays:
0: [0, 1, 2]
1: [0, 1, 9]
2: [9, 8, 4]
then my algorithm finds that array 1 intersects array 0 and adds it to the matching groups, but finds that array 2 doesn't, as it doesn't intersect array 0. However array 2 DOES intersect array 1 (as they both have id 9 in) and therefore they must share either a cat, dad or mum's name, therefore that group should also be added to the group, but is missed in my implementation. I could repeat the procedure multiple times until no new matches are found, but this seems slow and really inefficient.
There must be a better method/algorithm to group together entries that share at least 1 common property. Could someone advise me on how best to proceed?
I have put the code below to generate some test data and perform the initial grouping:
const {groupBy, intersection} = require("lodash");
const dadsNames = ["Barry", "John", "Bill", "Ron", "Carl", "Danny",
"Dodger", "Filbert", "Charlie", "Frank"];
const mumsNames = ["Lucy", "Mary", "Alice", "Sarah", "Yvonne", "Sandra",
"Suzie", "Stefanie", "Pearl", "Amanda", "Florence"];
const catsNames = ["Tiddles", "Nyanko", "Paws", "Fluffy", "Scratch", "Snuggles",
"Impy", "Chris", "Mandrew", "Mittens", "Tuxedo", "Sultan"];
const getRandomEntry = (array) => {
return array[Math.floor((Math.random() * array.length))];
};
const data = [];
for (let i = 0; i < 100; i++) {
data.push({id: i, dad: getRandomEntry(dadsNames), mum: getRandomEntry(mumsNames), cat: getRandomEntry(catsNames)});
}
const groupedByDad = groupBy(data, ({dad}) => dad);
const groupedByMum = groupBy(data, ({mum}) => mum);
const groupedByCat = groupBy(data, ({cat}) => cat);
const groups = [groupedByDad, groupedByMum, groupedByCat]; // array of groups
// in next block I extract just the IDs from the original data
groups.forEach((group) => {
Object.entries(group).forEach(([key, grouping]) => {
group[key] = grouping.map(({id}) => id);
});
});
Seems that you need a recursive solution to chase down all the combinations that should fall into a single group, otherwise as noted above in your small sample of 3 ids, subsequent data row entries are not properly included in the group...
Another alternative is to assign bit values to each of the dad, mum, and cat attribute values, then determine the bit mask of the row, and use logical AND to determine the matches that belong to a group. Of course, once a match is found, the group bit mask is expanded via logical OR and the search continues until no more data rows are found for the group.
Using the example data at the beginning of the question, the bit masks for each attribute value are...
0: {"Carl" => 1n}
1: {"Amanda" => 2n}
2: {"Mittens" => 4n}
3: {"Ron" => 8n}
4: {"Scratch" => 16n}
5: {"Lucy" => 32n}
6: {"Tiddles" => 64n}
7: {"Barry" => 128n}
8: {"Florence" => 256n}
9: {"Nyanko" => 512n}
10: {"Fluffy" => 1024n}
11: {"Stefanie" => 2048n}
12: {"Snuggles" => 4096n}
...and then these values are used to calculate the data row bit mask values...
[
{ id: 0, dad: 'Carl', mum: 'Amanda', cat: 'Mittens' }, ==> 7n
{ id: 1, dad: 'Ron', mum: 'Amanda', cat: 'Scratch' }, ==> 26n
{ id: 2, dad: 'Carl', mum: 'Lucy', cat: 'Tiddles' }, ==> 97n
{ id: 3, dad: 'Barry', mum: 'Florence', cat: 'Nyanko' }, ==> 896n
{ id: 4, dad: 'Barry', mum: 'Florence', cat: 'Fluffy' }, ==> 1408n
{ id: 5, dad: 'Carl', mum: 'Stefanie', cat: 'Snuggles' } ==> 6145n
]
Now, the rows are walked, searching for matches. First, the group mask is set to the first available row, in this case 7n. Then, the rows are walked, ANDing the group mask with the row mask. So, the group mask (7n) is ANDed with the row id 1 mask (26n) resulting in 2n ( "Amanda" ). Since this indicates a match, row 1 is then added to the group, and the group mask is updated to 7n OR 26n which is 31n, which is a bit mask representing the sum of "Carl", "Amanda", "Mittens", "Ron", and "Scratch". So now, 31n is the group mask, and 31n ANDed with the row id 2 value (97n) results in 1n, representing "Carl" as the common element. So, row id 2 is added to the group, and now 31n OR 97n results in 127n as the group mask, which represents the attributes "Carl" through "Tiddles" in the list above. This continues, rewalking the data rows remaining in the list ( to find related rows that were passed over due to attributes being added to the group later in the search ) until the list is walked and no more data rows are added to the current group. Then, if there are still data rows remaining, a fresh group is created and the next available data row is used to create the new group, and the cycle of checks repeats...
The implementation ( which randomly creates 10 data rows using the code in the question ) is...
const dadsNames = ["Barry", "John", "Bill", "Ron", "Carl", "Danny", "Dodger", "Filbert", "Charlie", "Frank"];
const mumsNames = ["Lucy", "Mary", "Alice", "Sarah", "Yvonne", "Sandra", "Suzie", "Stefanie", "Pearl", "Amanda", "Florence"];
const catsNames = ["Tiddles", "Nyanko", "Paws", "Fluffy", "Scratch", "Snuggles", "Impy", "Chris", "Mandrew", "Mittens", "Tuxedo", "Sultan"];
const getRandomEntry = (array) => {
return array[Math.floor((Math.random() * array.length))];
};
var data = [];
for (let i = 0; i < 10; i++) {
data.push({id: i, dad: getRandomEntry(dadsNames), mum: getRandomEntry(mumsNames), cat: getRandomEntry(catsNames)});
}
function getGroupings( data, attr ) {
function getAttrMap( data, attr ) {
let attrMap = new Map();
let attrMapValue = 1n;
let dataMapValue = new Map();
let dataIndexList = new Set();
data.forEach( ( d, i ) => {
dataIndexList.add( i );
dataMapValue.set( i, 0n );
attr.forEach( a => {
let attrValue = d[ a ];
if ( !attrMap.has( attrValue ) ) {
attrMap.set( attrValue, attrMapValue );
attrMapValue <<= 1n;
}
dataMapValue.set( i, dataMapValue.get( i ) + attrMap.get( attrValue ) );
} );
} );
console.log( `Binary mapping of attributes:` );
attrMap.forEach( (v,k) => console.log( `${k}: ${v.toString()}` ) );
console.log( `\nBinary value of each row of data:` );
dataMapValue.forEach( (v,k) => console.log( `${k}: ${v.toString()}` ) );
return [ dataMapValue, dataIndexList ];
}
let groupings = [];
let [ dataMapValue, dataIndexList ] = getAttrMap( data, ['dad','mum','cat'] );
while ( dataIndexList.size ) {
let group = new Set();
let dataRow = dataIndexList.keys().next().value;
let mask = dataMapValue.get( dataRow );
do {
let entryLength = dataIndexList.size;
dataIndexList.forEach( k => {
if ( mask & dataMapValue.get( k ) ) {
group.add( k );
dataIndexList.delete( k );
mask |= dataMapValue.get( k );
}
} );
if ( entryLength === dataIndexList.size ) break;
} while ( true );
groupings.push( group );
}
return groupings;
}
let result = getGroupings( data, ['dad','mum','cat'] );
console.log( `\nData:` );
console.log( data );
console.log( `\nFinal Groupings` );
console.log( result.map( s => [...s] ) );
Note that due to the small number of dad, mum, and cat attributes, that as you increase the number of data rows, the higher the likelihood that all the rows will fall into the same group. Hence, the code above only selects 10 random entries.
If given an array of ids [1,2,3,4,5]
And an object array:
[{animal:tiger, id:1}, {animal:"fish", id:2}]
What would be the suggested way to return 'tiger, fish'. Would that be through using .map or would a for loop be better for constructing the sentence?
What you need is just go through the list of ids and find corresponding animal in the animals list.
Note, that in case animals list is not expected to store all the animals and some of them are missing, you will need to add additional filter step to be sure that no undefined values appear on the last map step.
const ids = [1,5,2,4,3,6]; // added 6 which is missing in animals
const animals = [
{name:'Tiger',id:1},
{name:'Horse',id:2},
{name:'Mouse',id:3},
{name:'Elephant',id:4},
{name:'Cat',id:5}
];
const result = ids
.map(id => animals.find(a => a.id === id))
.filter(Boolean) // this will exclude undefined
.map(a => a.name)
.join(',');
console.log(result);
var ids = [1,2,3,4,5];
var objects = [{ animal:"tiger", id: 1 }, { animal: "fish", id: 2 }];
objects.map(function(o) { if(ids.includes(o.id)) return o.animal }).join();
I'm guessing you only want the animal names who's id appears in your array. If so, you could filter the array of objects first, followed by a map and a join.
let idarr = [1, 2, 3, 4];
let objarr = [{
animal: "tiger",
id: 1
}, {
animal: "fish",
id: 2
}];
console.log(objarr.filter(x => idarr.includes(x.id)).map(x => x.animal).join(', '))
I suggest two options, depending on your data & use cases.
1. map + find if the animal kingdoms are not too many to loop through.
const animals = [
{animal:tiger, id:1},
{animal:"fish", id:2}
]
const ids = [1,2,3,4,5];
const names = ids.map(id =>
animals.find(animal => animal.id === id));
2. convert animals array to object first, for easier frequent access later. One upfront loop, then easier to access by id later.
const animals = [
{animal: "tiger", id:1},
{animal: "fish", id:2}
]
/*
Convert
[{animal:tiger, id:1}, {animal:"fish", id:2}]
to
{
1: { animal: "tiger", id: 1 },
2: { animal: "fish", id: 2 },
}
*/
const animalsObj = animals.reduce((acc, animal) => {
return {
...acc,
[animal.id]: animal,
}
}, {});
const ids = [1,2,3,4,5];
const names = ids.map(id => animalsObj[id].animal)
I havve two different arrays with different property names like below
arrayA = [
{ id: 20, name: 'Jason' },
{ id: 15, name: 'Harry' },
{ id: 5, name: 'Clara' },
{ id: 9, name: 'Melonie' }
]
arrayB = [
{ courseID: 12, studentID: 20 },
{ courseID: 12, studentID: 15 }
]
I want to compare these two different arrays and remove unmatched ids from arrayA. For comparison, id field of arrayA and studentID field of arrayB matters. if these fileds aren't equal to each other, they should be removed from arrayA.
Expected is below
arrayA = [{id: 20, name: 'Jason' }, { id: 15, name: 'Harry' }]
Here is what I tried below but didn't work. Gave me empty array.
filteredElements = this.arrayA.map(e => e.id).filter(
val => this.arrayB.indexOf(val.studentID) !== -1
);
You can do that in following steps:
Use map() on arrayB and create array of courseID.
Then create a Set() from that Array
Then use filter() arrayA and check whether id of object exists in above created Set or not using Set.prototype.has()
const arrayA = [{id:20,name:'Jason'},{id:15,name:'Harry'},{id:5,name:'Clara'},{id:9,name:'Melonie'}]
const arrayB =[{courseID:12,studentID:20},{courseID:12,studentID:15}];
const ids = new Set(arrayB.map(x => x.studentID));
const res = arrayA.filter(x => ids.has(x.id));
console.log(res);
let arrayA = [{id: 20,name: 'Jason'},{id: 15,name: 'Harry'},{id: 5,name: 'Clara'},{id: 9,name: 'Melonie'}]
let arrayB = [{courseID: 12,studentID: 20},{courseID: 12,studentID: 15}];
let filtered=arrayA.filter(obj =>{ if(arrayB.find(course => course.studentID == obj.id))return true;return false;
});
console.log(filtered);
Try this:
var studentIds = arrayB.map(course => course.studentID);
var result = arrayA.filter(student => studentIds.includes(student.id));
The variable result contains your result.
Create a dictionary from courseMembers, keyed on studentID, to enable O(1) lookup.
Filter students according to the dictionary.
const students = [{id:20,name:'Jason'},{id:15,name:'Harry'},{id:5,name:'Clara'},{id:9,name:'Melonie'}]
const courseMembers = [{courseID:12,studentID:20},{courseID:12,studentID:15}]
function withCourses(students, courseMembers) {
const map = courseMembers.reduce((acc, {studentID}) =>
(acc[studentID] = true, acc), {})
return students.filter(({id}) => map[id])
}
const result = withCourses(students, courseMembers)
console.log(result) // [{ id:20, name:"Jason" },{ id:15, name:"Harry" }]
Having an array of objects I would like to sum the values by combining different set of keys. To be more specific, having an array of objects, describing the meal (0 - Breakfast, 1 - Snack...), I would like to make two different sums:
Sum nutritional values (quantity) for each meal
sum nutritional values (quantity) for whole day
The object array example is the following:
var arrayIngredients = [{mealNumber: 4, name: "Sugars, total", quantity: 1.4300000000000002}, {mealNumber: 4, name: "Magnesium, Mg", quantity: 14.950000000000001}, {mealNumber: 3, name: "Vitamin A, IU", quantity: 27.9}]
Does anyone know what is the most efficient way to sum the values for a given key (name) or multiple keys (mealNumber, name)?
You need to use Array.prototype.reduce (no need of third-party libraries).
Run the following code snippet for a detailed sample:
var arrayIngredients = [{
mealNumber: 4,
name: "Sugars, total",
quantity: 1.4300000000000002
}, {
mealNumber: 4,
name: "Magnesium, Mg",
quantity: 14.950000000000001
}, {
mealNumber: 3,
name: "Vitamin A, IU",
quantity: 27.9
}];
var dayTotals = arrayIngredients.reduce(function(result, next) {
if (!result.hasOwnProperty(next.name))
result[next.name] = {
totalQuantity: 0
};
result[next.name].totalQuantity += next.quantity;
return result;
}, {}); // this empty object is injected as "result" argument
// in the Array.prototype.reduce callback #1 parameter
document.getElementById("result").textContent = JSON.stringify(dayTotals);
<div id="result"></div>