So I have a JSON object that is dynamically creates based on lots. I'm trying to get the total cost of each lot. Each object in the list is a different purchase.
var lot_list = [{lot:123456,cost:'$4,500.00'}, {lot:654321, cost:'$1,600.00'}, {lot:123456, cost:'$6,500.00'}]
I want the total cost for each lot so I tried
var totalBalances = {};
function addBalance(){
lot_list.forEach(function(lots){
totalBalances[lots[lot]] += parseFloat(lots['cost'].replace('$','').replace(',',''));
});
}
This ends with every lot having a null cost
I also tried
var totalBalances = {};
function addBalance(){
lot_list.forEach(function(lots){
totalBalances[lots[lot]] = parseInt(totalBalances[lots[lot]]) + parseFloat(lots['cost'].replace('$','').replace(',',''));
});
}
Neither of these worked any help is much appreciated.
You cannot get a value to sum with parseFloat('$4,500.00') because of the invalid characters. To remove the dollar sign and commas you can replace using;
> '$4,500.00'.replace(/[^\d\./g,'')
> "4500.00"
Here the regex matches anything that is not a digit or decimal place with the global modifier to replace all occurrances.
You can map your array of objects to the float values using Array.map() to get an array of float values.
> lot_list.map((l) => parseFloat(l.cost.replace(/[^\d\.]/g,'')))
> [4500, 1600, 6500]
With an array of float values you can use Array.reduce() to get the sum;
> lot_list.map((l) => parseFloat(l.cost.replace(/[^\d\.]/g,''))).reduce((a,c) => a+c)
> 12600
EDIT
To get totals for each lot map an object with the lot id included and then reduce onto an object with;
> lot_list
.map((l) => {
return {
id: l.lot,
cost: parseFloat(l.cost.replace(/[^\d\.]/g,''))
}
})
.reduce((a,c) => {
a[c.id] = a[c.id] || 0;
a[c.id] += c.cost;
return a;
}, {})
> { 123456: 11000, 654321: 1600 }
Here the reduce function creates an object as the accumulator and then initialises the sum to zero if the lot id has not been summed before.
based on your code
var totalBalances = {};
function addBalance(){
lot_list.forEach(function(lots){
// totalBalances[123456] is not defined yet, this should gets you problem if you're trying calculate its value
// totalBalances[lots[lot]] += parseFloat(lots['cost'].replace('$','').replace(',',''));
// maybe you should use this instead
totalBalances[lots[lot]] = parseFloat(lots['cost'].replace('$','').replace(',',''));
});
}
but, if you want to count total value of cost, you might considering using array reduce
function countCost(){
return lot_list.reduce((accum, dt) => {
return accum + parseFloat(l.cost.replace(/[^\d\.]/g,''))
}, parseFloat(0));
} //this gonna bring you total count of all cost
Related
I want to generate unique ID in JavaScript. I have tried uuid npm package, it's good but it's not what I want.
For example what I get as a result from generating unique ID from uuid package is
9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d
Is there any way to make specific format as I want.
For example I want my ID to look like this
XZ5678
In this example format is two uppercase letters and 4 numbers.
That's it, I'm looking for answer and thank you all in advance.
If you're just looking to generate a random sequence according to that pattern, you can do so relatively easily. To ensure it's unique, you'll want to run this function and then match it against a table of previously generated IDs to ensure it has not already been used.
In my example below, I created two functions, getRandomLetters() and getRandomDigits() which return a string of numbers and letters the length of the argument passed to the function (default is length of 1 for each).
Then, I created a function called generateUniqueID() which generates a new ID according to the format you specified. It checks to see if the ID already exists within a table of exiting IDs. If so, it enters a while loop which loops until a new unique ID is created and then returns its value.
const existingIDs = ['AA1111','XY1234'];
const getRandomLetters = (length = 1) => Array(length).fill().map(e => String.fromCharCode(Math.floor(Math.random() * 26) + 65)).join('');
const getRandomDigits = (length = 1) => Array(length).fill().map(e => Math.floor(Math.random() * 10)).join('');
const generateUniqueID = () => {
let id = getRandomLetters(2) + getRandomDigits(4);
while (existingIDs.includes(id)) id = getRandomLetters(2) + getRandomDigits(4);
return id;
};
const newID = generateUniqueID();
console.log(newID);
Not sure why you want this pattern but you could do like this:
const { floor, random } = Math;
function generateUpperCaseLetter() {
return randomCharacterFromArray('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
}
function generateNumber() {
return randomCharacterFromArray('1234567890');
}
function randomCharacterFromArray(array) {
return array[floor(random() * array.length)];
}
const identifiers = [];
function generateIdentifier() {
const identifier = [
...Array.from({ length: 2 }, generateUpperCaseLetter),
...Array.from({ length: 4 }, generateNumber)
].join('');
// This will get slower as the identifiers array grows, and will eventually lead to an infinite loop
return identifiers.includes(identifier) ? generateIdentifier() : identifiers.push(identifier), identifier;
}
const identifier = generateIdentifier();
console.log(identifier);
They way of what you are suggesting be pretty sure you are going to get duplicates and collitions, I strongly suggest go with uuid.
Also probably you can find this useful: https://www.npmjs.com/package/short-uuid
Or if you want to continue with your idea this could be helpful: Generate random string/characters in JavaScript
Getting a bit stuck with this one.
I'm looping through an object (dataLayer.Tests) and I'm displaying the values of my content, in a DIV. Here's an example of how this object looks:
I'm doing this by looping through my object with forEach. (And in this example, I'm just console.logging my result result3).
The problem I'm having, is that within my forEach, I want to display create/display buttons, depending on what the number is in the totalVariants key/value.
So for example, if the totalVariants === 2, I want to create 2 buttons. If it is one, I want to create 1 button.
I know I need to for loop through this particular value, but I'm not sure how to do this, within a template literal.
Here's my code.
dataLayer.Tests.forEach((element, index, array) => {
let result3 = '';
let numberOfVariants = element.totalVariants;
if (numberOfVariants >= 1) {
for (i = 0; i < numberOfVariants; i++) {
console.log("The number is ", i + 1);
}
result3 += `
<div class="CRO-variant-result">
<p>Date Launched: ${element.date}</p>
<p>Test ID: ${element.id}</p>
<p>Test Description: ${element.name}</p>
<p>Variant Active: ${element.variant}</p>
<p>Total Variants: ${element.totalVariants}</p>
${(function () {
for (i = 0; i < element.totalVariants; i++) {
return `<button>${i}</button>`
}
})()}
</div>
`
console.log("result3", result3);
};
});
I've seen solutions which include .map and object.keys, but this doesn't seem to work/return anything. (Probably as I just need to loop through a number and not array etc.
Any ideas/pointers, would be appreciated.
Basically, I'm not sure how to loop through a number, within a template literal and return x number of elements.
Thanks,
Reena
numberOfVariants is an number, not an object, so one way you could do this is create a new incrementing array of that length (Array.from(Array(numberOfVariants).keys()) does this nicely) and then map over that array as you're doing.
${Array.from(Array(numberOfVariants).keys()).map(i => (
`<button value="${i}">${i}</button>`
)).join('')}
I'm not quite sure what you want to appear inside the button (maybe the integer of the current number as you increment)?
I have a list of people who have scores. In state I have them listed in an array, one of the items in the array is 'scoreHistory' which is an array of objects containing their scores at different points in time. I want to filter this set for different time periods i.e. -5 days, -30 days so instead of just seeing the overall score I can see the scores if everyone started at 0 say 30 days ago.
I have it (kind of) working. See my code below:
filteredScores () {
if(!this.people) {
return
}
// Here I was trying to ensure there was a copy of the array created in order to not change the original array. I thought that might have been the problem.
let allPeople = this.people.slice(0) // this.people comes from another computed property with a simple getter. Returns an array.
let timeWindow = 30 //days
const windowStart = moment().subtract(timeWindow,'days').toDate()
for (const p of allPeople ) {
let filteredScores = inf.scoreHistory.filter(score => moment(score.date.toDate()).isSameOrAfter(windowStart,'day'))
p.scoreHistory=filteredScores
//calculate new score
p.score = inf.scoreHistory.reduce(function(sum,item) {
return sum + item.voteScore
},0)
}
return allInf
}
I expected it to return to me a new array where each person's score is summed up over the designated time period. It seems to do that OK. The problem is that it is altering the state that this.people reads from which is the overall data set. So once it filters all that data is gone. I don't know how I am altering global state without using vuex??
Any help is greatly appreciated.
Your problem isn't that you're modifying the array, but that you're modifying the objects within the array. You change the scoreHistory and score property of each item in the array. What you want to do instead is create a new array (I recommend using map) where each item is a copy of the existing item plus a new score property.
filteredScores () {
if(!this.people) {
return
}
let timeWindow = 30 //days
const windowStart = moment().subtract(timeWindow,'days').toDate()
return this.people.map(p => {
let filteredScores = p.scoreHistory.filter(score => moment(score.date.toDate()).isSameOrAfter(windowStart,'day'))
//calculate new score
let score = filteredScores.reduce(function(sum, item) {
return sum + item.voteScore
}, 0)
// Create a new object containing all the properties of p and adding score
return {
...p,
score
}
}
})
Script
var companies=[
{name:'Vicky',category:'Devdas',start:1993,end:2090},
{name:'Vikrant',category:'Devdas',start:1994,end:2019},
{name:'Akriti',category:'mental',start:1991,end:2021},
{name:'Dummy',category:'dummyCategory',start:1995,end:2018},
{name:'Dummy 1',category:'dummyCategory',start:1993,end:2029}
];
var mappingComp=companies.map(company=>{company.start+10;return company});
console.log("mapped company function");
console.log(mappingComp.forEach(company=>console.log(company)));
In the above snippet there is no change in start field of companies array . Why ?
In case I do below I do get modified values for start field from companies array.
var mappingComp=companies.map(company=>company.start+10);
You aren't assigning the result of company.start+10 to anything - it's just an orphaned expression.
var mappingComp = companies.map(company => {
company.start + 10;
return company
});
is just like
var mappingComp = companies.map(company => {
33;
return company
});
The expression is evaluated to a value and then discarded. If you want to add 10 to company.start, use += or =:
var companies=[
{name:'Vicky',category:'Devdas',start:1993,end:2090},
{name:'Vikrant',category:'Devdas',start:1994,end:2019},
{name:'Akriti',category:'mental',start:1991,end:2021},
{name:'Dummy',category:'dummyCategory',start:1995,end:2018},
{name:'Dummy 1',category:'dummyCategory',start:1993,end:2029}
];
var mappingComp = companies.map(company => {
company.start += 10;
return company;
});
console.log(mappingComp);
But this will mutate the original array, which is (often) not a great idea when using map. If you don't want to change the original array, map to a new object:
var companies=[
{name:'Vicky',category:'Devdas',start:1993,end:2090},
{name:'Vikrant',category:'Devdas',start:1994,end:2019},
{name:'Akriti',category:'mental',start:1991,end:2021},
{name:'Dummy',category:'dummyCategory',start:1995,end:2018},
{name:'Dummy 1',category:'dummyCategory',start:1993,end:2029}
];
var mappingComp = companies.map(({ start, ...rest }) => ({
start: start + 10,
...rest
}));
console.log(mappingComp);
company.start + 10 is a simple expression. It's not an assignment statement, that you are expecting it to be. And you are returning the initial array company so it makes sense that it will be returned unaltered.
when you tried the single line fat arrow function with the map. What happens is that you created another entirely different array of mutated values. The array created was populated with values (company.start +10) and returned. Note: This actually didn't change the initial array ie company.
Read up on fat arrow functions, map, filter.
Im creating my batch and inserting it to collection using command i specified below
batch = []
time = 1.day.ago
(1..2000).each{ |i| a = {:name => 'invbatch2k'+i.to_s, :user_id => BSON::ObjectId.from_string('533956cd4d616323cf000000'), :out_id => 'out', :created_at => time, :updated_at => time, :random => '0.5' }; batch.push a; }
Invitation.collection.insert batch
As stated above, every single invitation record has user_id fields value set to '533956cd4d616323cf000000'
after inserting my batch with created_at: 1.day.ago i get:
2.1.1 :102 > Invitation.lte(created_at: 1.week.ago).count
=> 48
2.1.1 :103 > Invitation.lte(created_at: Date.today).count
=> 2048
also:
2.1.1 :104 > Invitation.lte(created_at: 1.week.ago).where(user_id: '533956cd4d616323cf000000').count
=> 14
2.1.1 :105 > Invitation.where(user_id: '533956cd4d616323cf000000').count
=> 2014
Also, I've got a map reduce which counts invitations sent by each unique User (both total and sent to unique out_id)
class Invitation
[...]
def self.get_user_invites_count
map = %q{
function() {
var user_id = this.user_id;
emit(user_id, {user_id : this.user_id, out_id: this.out_id, count: 1, countUnique: 1})
}
}
reduce = %q{
function(key, values) {
var result = {
user_id: key,
count: 0,
countUnique : 0
};
var values_arr = [];
values.forEach(function(value) {
values_arr.push(value.out_id);
result.count += 1
});
var unique = values_arr.filter(function(item, i, ar){ return ar.indexOf(item) === i; });
result.countUnique = unique.length;
return result;
}
}
map_reduce(map,reduce).out(inline: true).to_a.map{|d| d['value']} rescue []
end
end
The issue is:
Invitation.lte(created_at: Date.today.end_of_day).get_user_invites_count
returns
[{"user_id"=>BSON::ObjectId('533956cd4d616323cf000000'), "count"=>49.0, "countUnique"=>2.0} ...]
instead of "count" => 2014, "countUnique" => 6.0 while:
Invitation.lte(created_at: 1.week.ago).get_user_invites_count returns:
[{"user_id"=>BSON::ObjectId('533956cd4d616323cf000000'), "count"=>14.0, "countUnique"=>6.0} ...]
Data provided by query, is accurate before inserting the batch.
I cant wrap my head around whats going on here. Am i missing something?
The part that you seemed to have missed in the documentation seem to be the problem here:
MongoDB can invoke the reduce function more than once for the same key. In this case, the previous output from the reduce function for that key will become one of the input values to the next reduce function invocation for that key.
And also later:
the type of the return object must be identical to the type of the value emitted by the map function to ensure that the following operations is true:
So what you see is your reduce function is returning a signature different to the input it receives from the mapper. This is important since the reducer may not get all of the values for a given key in a single pass. Instead it gets some of them, "reduces" the result and that reduced output may be combined with other values for the key ( possibly also reduced ) in a further pass through the reduce function.
As a result of your fields not matching, subsequent reduce passes do not see those values and do not count towards your totals. So you need to align the signatures of the values:
def self.get_user_invites_count
map = %q{
function() {
var user_id = this.user_id;
emit(user_id, {out_id: this.out_id, count: 1, countUnique: 0})
}
}
reduce = %q{
function(key, values) {
var result = {
out_id: null,
count: 0,
countUnique : 0
};
var values_arr = [];
values.forEach(function(value) {
if (value.out_id != null)
values_arr.push(value.out_id);
result.count += value.count;
result.countUnique += value.countUnique;
});
var unique = values_arr.filter(function(item, i, ar){ return ar.indexOf(item) === i; });
result.countUnique += unique.length;
return result;
}
}
map_reduce(map,reduce).out(inline: true).to_a.map{|d| d['value']} rescue []
end
You also do not need user_id in the values emitted or kept as it is already the "key" value for the mapReduce. The remaining alterations consider that both "count" and "countUnique" can contain an exiting value that needs to be considered, where you were simply resetting the value to 0 on each pass.
Then of course if the "input" has already been through a "reduce" pass, then you do not need the "out_id" values to be filtered for "uniqueness" as you already have the count and that is now included. So any null values are not added to the array of things to count, which is also "added" to the total rather than replacing it.
So the reducer does get called several times. For 20 key values the input will likely not be split, which is why your sample with less input works. For pretty much anything more than that, then the "groups" of the same key values will be split up, which is how mapReduce optimizes for large data processing. As the "reduced" output will be sent back to the reducer again, you need to be mindful that you are considering the values you already sent to output in the previous pass.