I have an array with over 50 entries in the form of objects, which I would like to save depending on the Item ID so that I can then apply certain calculations to them. For example, I would like to add up the time of all entries with the Item Id "Las5241Wz".
Since the array can change dynamically, I can't analyze it manually. How can I separate the data beforehand according to their Item ID and push them into new arrays? The real Array contains up to 16 objects with the same ID.
var data= []
data = [
//...objects
{
itemId: "Las5241Wz",
time: 10
},
{
itemId:"Bos1239Zf",
time: 11
},
{
itemId:"Las5241Wz",
time: 15
},
{
itemId:"Bos1239Zf",
time: 21
}
//...more objets
]
The solution for this should look like this:
var Item1 = [
{
itemId: "Las5241Wz",
time: 10
},
{
itemId:"Las5241Wz",
time: 15
},
]
var Item2 = [
{
itemId:"Bos1239Zf",
time: 11
},
{
itemId:"Bos1239Zf",
time: 21
}
]
Here is another solution that builds an object with the properties "item1", "item2" and so on from the given object:
const data = [
//...objects
{
itemId: "Las5241Wz",
time: 10
},
{
itemId:"Bos1239Zf",
time: 11
},
{
itemId:"Las5241Wz",
time: 15
},
{
itemId:"Bos1239Zf",
time: 21
}
//...more objets
]
console.log(
Object.values(
data.reduce((o,e)=>((o[e.itemId]=o[e.itemId]||[]).push(e),o),{}))
.reduce((o,c,i)=>(o["item"+(i+1)]=c,o),{})
);
This is a "one-liner" and for that reason not that easy to read. So, probably not the version you would put into your production code.
Unless you have a performance reason to keep the lists separately, the answer is that you can just store the list of ids as a Set and use array.filter when you want to get the list that is just for that id
Set s = new Set();
for (let i = 0; i < data.length; i++) {
s.add(data[i].itemId);
}
var createFilterFunc(id) {
return function(elem) {
return elem.itemId == id;
}
}
var items = data.filter (createFilterFunc("Las5241Wz"));
Related
$slice allows me to get a slice of nested array. I used it successfully like this:
const user = await User.aggregate([
{ $match: { _id: ObjectId(user_id) } },
{
$lookup: {
from: "users",
let: { friends: "$friends" },
pipeline: [
{ $match: { $expr: { $in: ["$_id", "$$friends"] } } },
{
$lookup: {
from: "profiles",
localField: "profile",
foreignField: "_id",
as: "profile",
},
},
{
$match: {
"profile.online": true,
},
},
{
$project: {
name: "$name",
surname: "$surname",
profile: { $arrayElemAt: ["$profile", 0] },
},
},
],
as: "friends",
},
},
{
$addFields: {
friends: {
$slice: ["$friends", skip, limit],
},
},
},
]);
Now, instead of taking a slice, I would like to take a random sample of the array field friends.
I could not find a way to do this. But, in the group stage I can use something like this:
const pipeline = [
{
$lookup: {
from: "profiles",
let: { profiles_id: "$profile" },
pipeline: [
{
$match: {
online: true,
$expr: { $eq: ["$_id", "$$profiles_id"] },
},
},
],
as: "profile",
},
},
{ $unwind: "$profile" },
{ $sample: { size: 10 } },
];
const users = await User.aggregate(pipeline);
Change the last $addFields stage to this.
Pros: It "works."
Cons: You are not guaranteed unique random entries in the list. To get that is a lot more work. If you have a LOT more friends than the range then you are probably OK.
,{$addFields: {friends: {$reduce: { // overwrite friends array...
// $range is the number of things you want to pick:
input: {$range:[0,4]},
initialValue: [],
in: {
$let: {
// qq will be a random # between 0 and size-1 thanks to mult
// and floor, so we do not have to do qq-1 to get to zero-based
// indexing on the $friends array
vars: {qq: {$floor:{$multiply:[{$rand: {}},{$size:"$friends"}]}} },
// $concat only works for strings, but $concatArrays can be used
// (creatively) on other types. Here $slice returns an array of
// 1 item which we easily pass to $concatArrays to build the
// the overall result:
in: {$concatArrays: [ "$$value", {$slice:["$friends","$$qq",1]} ]}
}}
}}
UPDATED
This version exploits keeping state in the $reduce chain and will not pick dupes. It does so by iteratively shrinking the input candidate list of items as each item is randomly chosen. The output is a little nested (i.e. friends is not set to picked random sample but rather to an object containing picks and the remnant aa list) but this is something easily reformatted after the fact. In MongoDB 5.0 we could finish it off with:
{$addFields: {friends: {$getField: {field: "$friends.picks", input: {$reduce: {
but many people are not yet on 5.0.
{$addFields: {friends: {$reduce: {
// $range is the number of things you want to pick:
input: {$range:[0,6]},
// This is classic use of $reduce to iterate over something AND
// preserve state. We start with picks as empty and aa being the
// original friends array:
initialValue: {aa: "$friends", picks: []},
in: {
$let: {
// idx will be a random # between 0 and size-1 thanks to mult
// and floor, so we do not have to do idx-1 to get to zero-based
// indexing on the $friends array. idx and sz will be eval'd
// each time reduce turns the crank through the input range:
vars: {idx: {$floor:{$multiply:[{$rand: {}},{$size:"$$value.aa"}]}},
// cannot set sz and then use it in same vars; oh well
sz: {$size:"$$value.aa"}
},
in: {
// Add to our picks list:
picks: {$concatArrays: [ "$$value.picks", {$slice:["$$value.aa","$$idx",1]} ]},
// And now shrink up the input candidate array.
// Sadly, we cannot do $slice:[array,pos,0] to yield an empty
// array and keep the $concat logic tight; thus we have to test
// for front and end special conditions.
// This whole bit is to extract the chosen item from the aa
// array by splicing together a new one MINUS the target.
// This will change the value of $sz (-1) as we crank thru
// the picks. This ensures we only pick UNPICKED items from
// $$value.aa!
aa: {$cond: [{$eq:["$$idx",0]}, // if
// idx 0: Take from idx 1 and count size - 1:
{$slice:["$$value.aa",1,{$subtract:["$$sz",1]}]}, // then
// idx last: Take from idx 0 and ALSO count size - 1:
{$cond: [ // else
{$eq:["$$idx",{$subtract:["$$sz",1]}]}, // if
{$slice:["$$value.aa",0,{$subtract:["$$sz",1]}]}, // then
// else not 0 or last item, i.e. idx = 3
{$concatArrays: [
// Start at 0, count idx; this will land
// us BEFORE the target item (because idx
// is n-1:
{$slice:["$$value.aa",0,"$$idx"]},
// Jump over the target (+1), and go n-2
// (1 for idx/n conversion, and 1 for the
// fact we jumped over:
{$slice:["$$value.aa",{$add:["$$idx",1]},{$subtract:["$$sz",2]}]}
]}
]}
]}
}
}}
}}
}}
]);
Starting in MongoDB v4.4 (Jan 2021), you may opt to use the $function operator. The splice function in javascript does all the work of the multiple $slice operations in the previous example.
{$addFields: {friends: {$function: {
body: function(candidates, npicks) {
var picks = []
for(var i = 0; i < npicks; i++) {
var idx = Math.floor(Math.random() * candidates.length);
picks.push(candidates.splice(idx,1)[0]);
}
return picks;
},
args: [ "$friends", 4], // 4 is num to pick
lang: "js"
}}
I am having a problem accessing updated object properties inside a for loop in Javascript.
let distribution = await ctx.db.query.distribution(
{
where: {
id,
},
},
`
{
id
clients {
id
wallet {
amount
}
}
}`,
);
let count = 0;
for (const client of distribution.clients) {
await ctx.db.mutation.updateWallet({
where: {
id: client.id,
},
data: {
amount: client.wallet.amount + someAmount,
},
});
distribution.clients[count].wallet.amount = client.wallet.amount + someAmount;
count++;
}
In the above code, I execute a graphQL query to fetch all information associated with a distribution. I then want to iterate through the associated clients array and update each client’s wallet. The same client may appear multiple times in this array and each time
their wallet must be updated.
A problem occurs when a wallet has already been updated in a previous iteration. When I try to update the amount a second time, the client.wallet.amount reflects the initial value of the wallet rather than value after the first update.
Clearly the property clients[count].wallet.amount attribute of the distribution object isn’t being updated after each iteration. I thought javascript objects were passed by reference, and therefore the object should be updated after each iteration.
Could someone explain to me why the distribution object property is not being updated and how I can update it correctly?
FYI: I cannot use other loops such as forEach as it is not promise-aware and does not suppose async/await
A problem occurs when a wallet has already been updated in a previous iteration.
Each wallet object is only modified once when the loop runs. Two or more wallet objects in the array may represent the same row in the database, but they are still distinct objects -- changing one will not change the other.
const wallets = [
{ id: 1, amount: 0 },
{ id: 2, amount: 0 },
{ id: 1, amount: 0 },
]
for (const wallet of wallets) {
wallet.amount = wallet.amount + 5
}
console.log(wallets[0]) // { id: 1, amount: 5 }
console.log(wallets[1]) // { id: 2, amount: 5 }
console.log(wallets[2]) // { id: 1, amount: 5 }
Note that we don't need to use a count variable at all. Because the const wallet is a reference to the original object in the array, if we modify it, we modify the original object.
If you want need to track the amounts by ID, you'd need to implement that logic. For example:
const wallets = [
{ id: 1, amount: 0 },
{ id: 2, amount: 0 },
{ id: 1, amount: 0 },
]
const amountsById = {}
for (const wallet of wallets) {
amountsById[wallet.id] = (amountsById[wallet.id] || wallet.amount) + 5
}
const updatedWallets = wallets.map(wallet => ({
...wallet,
amount: amountsById[wallet.id],
}))
console.log(updatedWallets[0]) // { id: 1, amount: 10 }
console.log(updatedWallets[1]) // { id: 2, amount: 5 }
console.log(updatedWallets[2]) // { id: 1, amount: 10 }
I am working on a small VueJS webapp. I would like to output data from my array to the view but it has to be the last item of an array and of that last item the second item which is a and in my example equel to 39. I don't know how I can recieve that one.
HTML
<p>The last number in the array (a) is {{userLastCount}} </p>
Javascript/Vue
data () {
return {
event: 'Custom event',
userLastCount: 0,
lineData: [
{ time: '2017-05-01 15:00', a: 0 },
{ time: '2017-05-01 16:00', a: 12 },
{ time: '2017-05-01 17:00', a: 23 },
{ time: '2017-05-01 18:00', a: 28 },
{ time: '2017-05-01 19:00', a: 39 },
]
}
},
components: {
DonutChart, BarChart, LineChart, AreaChart
},
created() {
this.userLastCount = //equal to 39
}
I would like to output the last value of 'a' of the lineData object and assign it to a data string which I can output to the view. So, now the last 'a' = 39. But if I add another row in my object It has to be that one that is assigning to this.userLastCount
The last item of an array is arr[arr.length - 1]. You can use a computed to have that value always set for you, rather than maintaining a data item yourself:
computed: {
userLastCount() {
return this.lineData[this.lineData.length - 1].a;
}
}
Ypu can do this uzing plain javascript
In your created() hook do as follows:
created(){
//get the position of last object in the array.
var lastPosition = this.lineData.length -1;
this.userLastCount = this.lineData[lastPosition].a;
}
This question already has answers here:
How can I access and process nested objects, arrays, or JSON?
(31 answers)
Closed 6 years ago.
I am a beginner and, as I began to increase the complexity of JSON, I started to confuse myself by accessing it. How do I access the following JSON? I would like to access the value and keys of the services
{
employers:{
Mike: {
old: 20,
services:{
cut : {
value : 10
}
hair_straightening : {
value: 20
}
}
}
Penny: {
old: 20,
services:{
cut : {
value : 10
}
hair_straightening : {
value: 20
}
}
}
}
}
Everyone has to start somewhere :)
I'm not sure if I completely understand what you're after, but here goes...
First off, it looks like your notation on your object is subtly off when listing your keys. For example, between Mike and Penny you should have a comma separating each of those keys in the larger employers object.
Something like:
employers: {
Mike: {
...
}, //need a comma here
Penny: {
...
}
}
Also, within each of those employers, there should be a comma between the keys for cut and hair_straightening.
services: {
cut: {
value: 10
}, //need a comma here
hair_straightening: {
value: 20
}
}
Now to your actual question...
To get the keys for each of the services, you can use Object.keys(). This function would get you they keys for a given employer. Then you can also grab the values from inside that same function. (Note this is for only one employer; you'd just want to iterate over both and use this same function on each)
function getServices(employer) {
var services = employer.services;
var servicesKeys = Object.keys(services);
var serviceValueMatrix = [];
servicesKeys.forEach(function(service) {
serviceValueMatrix.push([service, employer.services[service].value])
})
return serviceValueMatrix;
}
// assuming you had var yourJSON = { employers: {...} }
// getServices(yourJSON.employers.Mike);
// returns [["cut",10], ["hair_straightening",20]]
Also, given that your JSON object is already in key:value format, you could probably skip the last set of objects in the format of value: 10, value: 20 etc, and instead just make the last tier of your object something like:
services:{
cut: 10,
hair_straightening: 20
}
Then you could just grab services.cut and services.hair_straightening.
Full code below for clarity:
const yourJSON = {
employers: {
Mike: {
old: 20,
services: {
cut: {
value: 10
},
hair_straightening: {
value: 20
}
}
},
Penny: {
old: 20,
services: {
cut: {
value: 10
},
hair_straightening: {
value: 20
}
}
}
}
}
function getServices(employer) {
var services = employer.services;
var servicesKeys = Object.keys(services);
var serviceValueMatrix = [];
servicesKeys.forEach(function(service) {
serviceValueMatrix.push([service, employer.services[service].value])
})
return serviceValueMatrix;
}
console.log(getServices(yourJSON.employers.Mike));
// returns [["cut",10], ["hair_straightening",20]]
your json should look something like this:
{
employers:[
{
name: "Mike",
age: 20,
services: [
{name:"cut",value:10},
{name:"hair_straightening",value:20}
]
},{
name: "Penny",
age: 20,
services: [
{name:"cut",value:10},
{name:"hair_straightening",value:20}
]
}
]
}
You can use Object.keys to get the keys of an object as an array, then you can loop through that nicely.
// in this case json is a variable representing your parsed data
Object.keys(json).map(function(key) {
console.log(json[key])
return json[key].services
})
That would give you an array of services objects.
I have an array of object and I want to count the number of distinct elements and counts of those objects.
[ { name: 'Suman',
game: '5A'
},
{ name: 'Suman',
game: '5A'
},
{ name: 'Namus',
game: '5A'
},
{ name: 'Namus',
game: '5A'
}
]
I want to count the number of distinct names and store them in an object. I have tried it by 1# pushing all the names in an array,
2# then sorting them,
3# then calculating the number of distinct names and
4# finally pushing them to the object.
This process is too long. Is there a shorter way to do this. I am using Nodejs
Thanks in advance
You will create a new object, where the key is the name and the value the count:
var youArr = [
{ name: 'Suman',
game: '5A'
},
{ name: 'Suman',
game: '5A'
},
{ name: 'Namus',
game: '5A'
},
{ name: 'Namus',
game: '5A'
}
];
var count = {}
for(var i=0; i < youArr.length; i++){
count[youArr[i].name] = count[youArr[i].name] || 0;
count[youArr[i].name]++;
}
alert(count['Namus']); // 2
This is a great place to use the reduce function:
The reduce() method applies a function against an accumulator and each
value of the array (from left-to-right) has to reduce it to a single
value.
...
reduce executes the callback function once for each element present in
the array, excluding holes in the array, receiving four arguments: the
initial value (or value from the previous callback call), the value of
the current element, the current index, and the array over which
iteration is occurring.
It would look something like this:
var arr = [ { name: 'Suman',
game: '5A'
},
{ name: 'Suman',
game: '5A'
},
{ name: 'Namus',
game: '5A'
},
{ name: 'Namus',
game: '5A'
}
]
var counts = arr.reduce(function(counts, item) {
counts[item.name] = (counts[item.name] || 0) + 1;
return counts;
}, {});
counts is then:
{ Suman: 2, Namus: 2 }
Asked in the comments:
what if i want the count as well as name in an array of object like
[{name: 'Suman', count:'2'}, {name:'Namus', count:'2'}]
If you already have counts from the reduce call above, then you can map its keys to the format you want:
var countsArray = Object.keys(counts).map(function(name) {
return {name: name, count: counts[name]};
});
countsArray is then:
[ { name: 'Suman', count: 2 },
{ name: 'Namus', count: 2 } ]