How to group array elements by arbitrary set of keys? - javascript

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>

Related

How to write function that filters a dictionary when given a key/value pair in Javascript?

I have a dictionary called teamData
var teamData = {
app: {
sortCol:"name",
sortDir:"asc"
},
data: [
{
id: 1,
name:"Raptors",
coachId: 1,
coachFirst: "Ken",
coachLast: "jenson",
coachPhone: "801-333-4444",
coachEmail: "ken.jenson#uvu.edu",
coachLicenseLevel: 1,
league: 1,
division: 1
},
{
id: 2,
name:"Killer Bunnies",
coachId: 2,
coachFirst: "Peter",
coachLast: "Rabbit",
coachPhone: "801-333-4444",
coachEmail: "peter.rabbit#uvu.edu",
coachLicenseLevel: 1,
league: 1,
division: 2
},
{
id: 3,
name:"Thunderbirds",
coachId: 3,
coachFirst: "Harry",
coachLast: "DirtyDog",
coachPhone: "801-333-4444",
coachEmail: "harry.dirty.dog#uvu.edu",
coachLicenseLevel: 2,
league: 1,
division: 2
}
]
}
I'm trying to write a function that takes a key/value object and returns a filtered dictionary. So if the function is
let teams = filter({coachLicenseLevel:1});
then the expected result is to return a filtered dictionary with only two elements that have that key/value pair
Here is the function I have so far, but I'm stuck on how to get the key object.
filter(filterObj) {
const v = Object.values(filterObj);
const k = Object.keys(filterObj);
const res = teamData.filter(({???}) => v.includes(???));
}
any help would be appreciated.
If you want to filter only the data array, you could do something like this:
function filterArrayByParamAndValue(arr, itemParam, value) {
return arr.filter(item => item.itemParam === value)
}
And in your code just replace the data property, if
let teamData = {
....,
data: [...filterArrayByParamAndValue(teamData.data, coachLicenseLevel, 1)],
....
}
Of course you should also add all necessary checks in the filter function, or even add an object property to check for and pass the whole object.
Instead of passing an object, you may consider using the filter function with your custom filter logic. Here is an example for your specific case:
let teams = teamData.data.filter(item => item.coachLicenseLevel == 1)

How can I add an array item into another array

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

Data from the object doesn't convert when using JSON.stringify

I'm stuck in this weird issue that I'm having hard time in understanding what's doing on. When a button is clicked it calls the function onSubmit. What onSubmit function should do is stringify is the object to JSON however for me this doesn't happen. The result I get when I console.log(JSON.stringify(obj)); is [[]]. When I console.log(obj); I can see the object.
I was not able to replicate the same issue in playcode.io and codesandbox.io
async function onSubmit() {
let l = [];
l["Channel"] = undefined;
l["MetricsData"] = [
{ id: 1, name: "CPA", year: "" },
{ id: 2, name: "Average Gift", year: "" },
{ id: 3, name: "Average Gift Upgrade %", year: "" }
];
let obj = [];
l.Channel = 1;
obj.push(l);
console.log(obj);
console.log(JSON.stringify(obj)); //[[]]
}
As others have pointed in comments, you're instantiating l as an array and then attempt populating named keys (Channel, Metricsdata).
Note: Technically, arrays are objects, but they're special objects (their keys are intended to be numeric and they also have a few extra props and methods, specific to arrays - e.g: length, pop, push, slice, etc...). Use the link above to read more about arrays.
What you need to do is use l as an object (e.g: {}):
const l = {
Channel: 1,
MetricsData: [
{ id: 1, name: "CPA", year: "" },
{ id: 2, name: "Average Gift", year: "" },
{ id: 3, name: "Average Gift Upgrade %", year: "" }
]
};
// Uncomment any of the following:
// console.log('object:', l);
// console.log('stringified object:', JSON.stringify(l));
const items = [];
items.push(l);
// console.log('items:', items);
// console.log('first item:', items[0]);
console.log(JSON.stringify(items));

Algorithm + ES6 - Compare 4 arrays of Objects and remove all repeated items

I am trying to delete all repeated objects between four arrays by preference. All the arrays have unique elements, and may not be ordered. Here is a picture that tries to explain the problem:
As you can see, if the array has a lower preference, the elements will stay inside it. For example, the object with id "6" is repeated in the arrays with preference 2, 3, and 4. So, the algorithm has to detect this and remove these objects from the arrays with preference 3 and 4, because 2 < 3 < 4.
So, if the input data is:
arr_p1 = [{ id: "892d" }, {id: "kla8x" }, {id: "sys32" }]
arr_p2 = [{id: "saa1" }, { id: "892d" }]
arr_p3 = [{ id: "kla8x" }, {id: "saa1" }, {id: "pp182" }]
the output must be:
arr_p1 = [{ id: "892d" }, {id: "kla8x" }, {id: "sys32" }]
arr_p2 = [{id: "saa1" }]
arr_p3 = [{id: "pp182" }]
Any ideas on how to solve this situation in a good complexity order?
All arrays have a limited size of 40 objects.
The only thing I can think of is to sort all the objects, in each array, by identifier. Then, take the lowest identifier of an object moving with the pointer of each list, from the lowest preference (1) to the highest (4), and if it is in one of the higher preference lists, delete it... but I need to do it without altering the order of the elements ...
Pd: I am using JS and ES6.
Combine all items to a single array, and then reduce them to a Map in a reversed order using Array.reduceRight(). The reversed order will cause the 1st items to override the last items.
Now you can filter each array by using the Map, and keeping only items that exist on the Map.
Complexity is O(N1 + N2 + N3) where Nx is the length of that array.
const arr_p1 = [{ id: "892d" }, {id: "kla8x" }, {id: "sys32" }]
const arr_p2 = [{id: "saa1" }, { id: "892d" }]
const arr_p3 = [{ id: "kla8x" }, {id: "saa1" }, {id: "pp182" }]
// create an array of all items and reduce it in a reversed order to a Map
const dupsMap = [...arr_p1, ...arr_p2, ...arr_p3]
// create the Map by using the `id` as the key, and the object as the value
.reduceRight((acc, o) => acc.set(o.id, o), new Map())
const filterArr = arr => arr.filter(o =>
dupsMap.get(o.id) === o // keep the item if it was the object that was used as value
)
const arr_p1f = filterArr(arr_p1)
const arr_p2f = filterArr(arr_p2)
const arr_p3f = filterArr(arr_p3)
console.log({ arr_p1f, arr_p2f, arr_p3f })
You can easily create a generic function that can handle any number of arrays, and get the individual arrays from it's returned value using destructuring.
const dedupArrays = (...arrs) => {
const dupsMap = arrs.flat() // convert arrays to a single array
// a reduce right to create a Map of [id, object]
.reduceRight((acc, o) => acc.set(o.id, o), new Map())
// map the array of arrays, and filter each sub array
return arrs.map(arr => arr.filter(o => dupsMap.get(o.id) === o))
}
const arr_p1 = [{ id: "892d" }, {id: "kla8x" }, {id: "sys32" }]
const arr_p2 = [{id: "saa1" }, { id: "892d" }]
const arr_p3 = [{ id: "kla8x" }, {id: "saa1" }, {id: "pp182" }]
const [arr_p1f, arr_p2f, arr_p3f] = dedupArrays(arr_p1, arr_p2, arr_p3)
console.log({ arr_p1f, arr_p2f, arr_p3f })
You could generate a preference object (hash map) to map the id to preference. Run it from 3rd array to the first so that lower order overrides the higher one.
Then when you have the preference map, you can filter all arrays by checking if the id's preference matches the current array.
let arr_p1 = [{ id: "892d" }, {id: "kla8x" }, {id: "sys32" }];
let arr_p2 = [{id: "saa1" }, { id: "892d" }];
let arr_p3 = [{ id: "kla8x" }, {id: "saa1" }, {id: "pp182" }];
let pref = {};
arr_p3.forEach(e => pref[e.id] = 3);
arr_p2.forEach(e => pref[e.id] = 2);
arr_p1.forEach(e => pref[e.id] = 1);
arr_p1 = arr_p1.filter(e => pref[e.id] === 1);
arr_p2 = arr_p2.filter(e => pref[e.id] === 2);
arr_p3 = arr_p3.filter(e => pref[e.id] === 3);
console.log(arr_p1);
console.log(arr_p2);
console.log(arr_p3);
I have several tips for you, rather than a full answer, since I assume this is a homework question?
Strategy
Build a set of "items already seen"
Check each new array against that, deleting any duplicate entries (in the new array).
Start with the most preferred array
That way, whenever something is deleted, it is being deleted from the less-preferred array.
For example, in pseudocode
let elementsSeen = new Set( most preferred array of elements )
for array in listOfArraysInDecreasingOrderOfPreference {
for element in array {
if element is in elementsSeen, delete it from array
}
elementsSeen = union of elementsSeen and array
}
Complexity
Every item has to be looked at. It has to be compared with every other item, but the complexity of that need not be enormous, because the `Set` process can make use of hashes, i.e. not have to do an individual comparison of each incoming object with each existing object. Almost all incoming objects will have a hash table value that is different from those of existing objects, which is quick, at the expense of some time spent on hashing and some memory spent on the table.
In the worst case, where hashing is no longer helping you, it is O(N x M) where N is the number of arrays, and M is the size of each.
Your question implies you want to mutate the original arrays.
So if you still want to mutate the arrays you could.
create a SET of the ID's for each level.
Loop each level backward, if any id's in higher level then remove from array.
A couple of optimisation here too, eg. slice(0, -1), is so we don't need to create a SET for the last level, as were check previous ones. Inside the loop once item is known to be deleted, use a break to then go onto next. To be honest, I've no idea what the complexity on this is.. :)
eg.
const arr_p1 =
[{ id: "892d" }, {id: "kla8x" }, {id: "sys32" }];
const arr_p2 =
[{id: "saa1" }, { id: "892d" }];
const arr_p3 =
[{ id: "kla8x" }, {id: "saa1" }, {id: "pp182" }];
function dedupe(alist) {
const hasList = alist.map(
m => new Set(m.slice(0, -1).map(i => i.id)));
for (let l = alist.length -1; l > 0; l --) {
for (let i = alist[l].length -1; i >= 0; i --) {
for (let h = 0; h < l; h += 1) {
if (hasList[h].has(alist[l][i].id)) {
alist[l].splice(i, 1);
break;
}
}
}
}
}
dedupe([arr_p1, arr_p2, arr_p3]);
console.log(arr_p1);
console.log(arr_p2);
console.log(arr_p3);

Given a value, search for an adjacent key:value in an object

Given a data structure like this:
var userList= [
{
employeeId: 10,
name: "Bill",
},
{
employeeId: 12,
name: "Tom",
},
{
employeeId: 14,
name: "Sue",
},
]
And an input of 10 how can I find the value Bill?
So, in other words, given a value that pairs with another Key: Value in an array of objects, how might I search for the other keys/values within that object?
I'm guessing step 1 involves searching for the value within each object in the array and step 2 involves returning the index of the object within the array meaning step 3 would be mapping to that index somehow.
EDIT: So, I ultimately just used a function with a for...in loop to solve this problem as shown below. While I may have done a particularly poor job of articulating my problem I was trying to take an input of an employee number and match it with an employee's name. The for...in loop seems to be the simplest I can find but I was looking for a more elegant object method or some such to find it. Cheers!
function personLookup(x){
for (i = 0; i<userList.length; i++){
if (userList[i].employeeID== x){
return userList[i].name;
}
};
}
Arrays have a .find function which can be used to look for an element that matches a certain condition. So to find the person with employeeNumber 10, you can do the following:
let employees = [{
name: 'Jill',
employeeNumber: 9
}, {
name: 'Bill',
employeeNumber: 10
}];
let numberTen = employees.find(function (person) {
return person.employeeNumber == 10;
});
if (numberTen !== undefined) {
console.log(numberTen.name)
}
Note that .find is appropriate if you want exactly one result, but if there are possibly multiple that match the criterion you're looking for, .filter is a better choice.
See documentation for these at:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
You can do
let person= {
"name" : "Bill",
"employeeNumber" : 10
}
for(let element of Object.keys(person)){
if(person[element] == 10){
console.log(element);
console.log(person.name);
}
}
If there are multiple number 10s in your data, you can use .filter
let employees = [{
name: 'Jill',
employeeNumber: 9
}, {
name: 'Bill',
employeeNumber: 10
}, {
name: 'Billy',
employeeNumber: 10
}
}];
const employData = employees.filter(employee => employee.employeeNumber == 10)
if(employData.length > 0){
console.log("names are ", employData.map(employee => employee.name))
} else {
console.log("no employ found")
}
So the for...in loop seemed to be the easiest in the long run.
https://codepen.io/mizzoudavis/pen/pWNvxw
var userList= [
{
'employeeID': 10,
'name': "Bill",
},
{
'employeeID': 12,
'name': "Tom",
},
{
'employeeID': 14,
'name': "Sue",
},
]
var x;
$('#submit').click(function(){
x = $('#empID').val();
personLookup(x);
})
function personLookup(x){
for (i = 0; i<userList.length; i++){
if (userList[i].employeeID== x){
$('div').text(userList[i].name);
}
};
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="empID"/>
<button id="submit">submit</button>
<div></div>

Categories

Resources