How to merge 2 values of variables apps script - javascript

I want to send a email with a formatted list of data (datalist1, datalist2, datalist3) on it. The list of data has a format that I want to make, but when I merge the 3 datalist using "push" into 1 array (merge) and send by email, the format only works for the first data (datalist1). If you guys know the problem or show the other way to achive it, It can be really helpful. Thank you
The sample input value of variable 'data':
The Expected Result that I want:
The Result that I got now: (The Problem)
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('SheetName');
var [_, ...data] = sheet.getDataRange().getValues();
var arr1 = [];
var arr2 = [];
var arr3 = [];
for (var i = 0; i < data.length; i++) {
if (data[i][0] == 'First') {
arr1.push([data[i][1], data[i][2], data[i][0], data[i][3]]);
}
if (data[i][0] == 'Second') {
arr2.push([data[i][1], data[i][2], data[i][0], data[i][3]]);
}
if (data[i][0] == 'Third') {
arr3.push([data[i][1], data[i][2], data[i][0], data[i][3]]);
}
}
var merge = [];
var datalist1;
var datalist2;
var datalist3;
for (var y = 0; y < arr1.length; y++) {
if (arr1[y][2] == 'First') {
if (y == 0) {
datalist1 = "First" + "\n" + "- " + arr1[y];
} else {
datalist1 = datalist1 + "\n" + "- " + arr1[y]
}
}
}
for (var y = 0; y < arr2.length; y++) {
if (arr2[y][2] == 'Second') {
if (y == 0) {
datalist2 = "Second" + "\n" + "- " + arr2[y];
} else {
datalist2 = datalist2 + "\n" + "- " + arr2[y]
}
}
}
for (var y = 0; y < arr3.length; y++) {
if (arr3[y][2] == 'Third') {
if (y == 0) {
datalist3 = "Third" + "\n" + "- " + arr3[y];
} else {
datalist3 = datalist3 + "\n" + "- " + arr3[y]
}
}
}
console.log(datalist1);
console.log(datalist2);
console.log(datalist3);
merge.push(datalist1, datalist2, datalist3);
//merge.sort();
console.log(merge);
var email = '#gmail.com'
var Subject = "data";
var Message = "Hello, \n" + "\n" +
"This is a data message to let you know\n" + "\n" +
"Here is the list of data: \n" + datalist1 +
"\n" + datalist2 + "\n" + datalist3 + "\n";
MailApp.sendEmail(email, Subject, Message);
}

I believe your goal is as follows.
You want to achieve the following situation. (Following images are from your question.)
From
To
Modification points:
From your showing an image of the sample Spreadsheet, it supposes that data is [["First", "low", "medium", "high"], ["Second", "medium", "low", "high"], ["First", "low", "high", "medium"], ["Second", "medium", "low", "high"], ["First", "low", "high", "medium"], ["Third", "low", "high", "medium"]].
When data is used for your script, arr1, arr2, and arr3 are [["low","medium","high"],["low","high","medium"],["low","high","medium"]], [["medium","low","high"],["medium","low","high"]] and [["low","high","medium"]], respectively. In this case, arr1[i][0] == 'First', arr2[i][0] == 'Second' and arr3[i][0] == 'Third' are always false. By this, all values of datalist1, datalist2, datalist3 are null.
From this situation and your question, I'm worried that you might have miscopied your current script.
From the above situation, in this answer, I would like to propose a sample script using the above value of data and your expected result of ``.
Sample script:
const data = [["First", "low", "medium", "high"], ["Second", "medium", "low", "high"], ["First", "low", "high", "medium"], ["Second", "medium", "low", "high"], ["First", "low", "high", "medium"], ["Third", "low", "high", "medium"]]; // This is from your showing image of sample Spreadsheet.
const order = ['First', 'Second', 'Third']; // This is from your script.
const sorted = data.sort((a, b) => {
const i1 = order.indexOf(a[0]);
const i2 = order.indexOf(b[0]);
const len = data.length;
return 1 * ((i1 > -1 ? i1 : len) - (i2 > -1 ? i2 : len));
});
const merged = sorted.reduce((o, [a, ...b]) => {
if (o.temp != a) {
o.merged.push(a);
o.temp = a;
}
o.merged.push(`- ${b.join(",")}`);
return o;
}, { merged: [], temp: "" }).merged.join("\n");
console.log(merged)
Testing:
When the above script is run, the following result is obtained.
First
- low,medium,high
- low,high,medium
- low,high,medium
Second
- medium,low,high
- medium,low,high
Third
- low,high,medium
Note:
If you want a script for retrieving values from Spreadsheet, you can also use the following script.
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
const data = sheet.getRange("A2:D" + sheet.getLastRow()).getValues();
References:
sort()
reduce()
map()

Related

How to take only a few of the column and not all of the column (Extended Question)

This question is the extended from the previous question.
Previous Question
Now, I want to try modify the answer. I want to take only some the column and not all of them. How can I modify that? Thank you.
The data source:
The Expected result:
Here is the previous question's answer:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('sheetName');
var [_, ...data] = sheet.getDataRange().getValues();
const order = ['First', 'Second', 'Third']
const sorted = data.sort((a, b) => {
const i1 = order.indexOf(a[0]);
const i2 = order.indexOf(b[0]);
const len = data.length;
return 1 * ((i1 > -1 ? i1 : len) - (i2 > -1 ? i2 : len));
});
const merged = sorted.reduce((o, [a, ...b]) => {
if (o.temp != a) {
o.merged.push(a);
o.temp = a;
}
o.merged.push(`- ${b.join(",")}`);
return o;
}, { merged: [], temp: "" }).merged.join("\n");
console.log(merged)
var email = ''
var Subject = "data";
var Message = "Hello ", \n" + "\n" +
"Here is the list of data:\n" + merged + "\n" + "\n";
MailApp.sendEmail(email, Subject, Message);
}
In your script, how about the following modification?
From:
var [_, ...data] = sheet.getDataRange().getValues();
To:
var columns = [6, 2, 3, 7, 5]; // This is from your comment of "I want to take Column 6, Column 2, Column 3, Column 7, and Column 5 to as Expected result's data".
columns.unshift(2);
var [_, ...data] = sheet.getDataRange().getValues();
data = data.map(r => columns.map(f => r[f - 1]));
References:
unshift()
map()

Sort jobs by date or day of the week in a javascript object/array

I have a project I am working on where I auto-generate a timesheet for a work employee. I get JSON data from a database, then take that JSON data and turn it into a javascript object. Then using Object.entries and forEach(), I iterate over the object data and restructure it into another object with each job punch being the main object property and the value of each "job" being an array of objects with job data in them. One of these array key/value pairs is day_of_week, which is an integer, and another key/value pair is a string month/day date. I would like to sort the jobs in the array by either the day_of_week or the job_date values and update the existing array, or create a new array, it doesn't matter, but I can not figure out how. Any help is appreciated, and code snippet is below of how the object is created. Also, below is the original JSON data, redacted, for where the laborEntries object is initially originated.
{
"call": ["run_queries"],
"with": {
"run_queries": {
"retrieve_labor_for_payroll_period": [
["480192", "6.00", "Technician", "Regular", "2022-09-16", "88628892", "00:00:00", "00:00:00", "REDACTED", "2209161153520", "REDACTED"],
["482587", "4.00", "Technician", "Regular", "2022-09-15", "88606868", "00:00:00", "00:00:00", "REDACTED", "2209151349576", "REDACTED"],
["480074", "1.00", "OT Technician", "OverTime", "2022-09-16", "88633930", "00:00:00", "00:00:00", "REDACTED", "2209161530274", "REDACTED"]
]
}
}
}
Object.entries(laborEntries).forEach((job => {
// Populate timesheet with jobs from date-range
acc += 1
timesheet[`job${acc}`] = Object(job[1])
const jobDate = new Date(job[1][4])
let isOvertime = false
timesheet[`job${acc}`]["job_date"] = jobDate.getMonth() + 1 + '/' + Number(jobDate.getDate() + 1)
timesheet[`job${acc}`]["day_of_week"] = jobDate.getDay() + 1
timesheet[`job${acc}`]["wo_number"] = job[1][0]
timesheet[`job${acc}`]["job_name"] = job[1][8]
timesheet[`job${acc}`]["work_type_code"] = 'S, M, or T'
timesheet[`job${acc}`]["pw_type_code"] = 'pw type code'
if (job[1][3] === 'OverTime') {
isOvertime = true
}
timesheet[`job${acc}`]["ot_billed"] = isOvertime
if (isOvertime) {
timesheet[`job${acc}`]["reg_hours"] = 0
timesheet[`job${acc}`]["ot_hours"] = Number(`${job[1][1]}`)
} else {
timesheet[`job${acc}`]["reg_hours"] = Number(`${job[1][1]}`)
timesheet[`job${acc}`]["ot_hours"] = 0
}
timesheet[`job${acc}`]["non_worked_hours"] = `${job[1][1]} -- n/w hours`
timesheet[`job${acc}`]["non_worked_code"] = 'non-worked code'
timesheet[`job${acc}`]["is_on_call"] = isOnCall ? '*****ON-CALL WEEK*****' : ''
// delete non-used obj data
for (let i = 0; i <= 10; i++) {
delete timesheet[`job${acc}`][i]
}
I made a try on this. Could you check below code?
var a = {
"call": ["run_queries"],
"with": {
"run_queries": {
"retrieve_labor_for_payroll_period": [
["480192", "6.00", "Technician", "Regular", "2022-09-16", "88628892", "00:00:00", "00:00:00", "REDACTED", "2209161153520", "REDACTED"],
["482587", "4.00", "Technician", "Regular", "2022-09-15", "88606868", "00:00:00", "00:00:00", "REDACTED", "2209151349576", "REDACTED"],
["480074", "1.00", "OT Technician", "OverTime", "2022-09-16", "88633930", "00:00:00", "00:00:00", "REDACTED", "2209161530274", "REDACTED"]
]
}
}
};
var laborEntries = a.with.run_queries.retrieve_labor_for_payroll_period;
var acc = 0;
var timesheet = [];
var isOnCall = true;
Object.entries(laborEntries).forEach((job => {
// Populate timesheet with jobs from date-range
acc += 1
timesheet[`job${acc}`] = Object(job[1])
const jobDate = new Date(job[1][4])
let isOvertime = false
timesheet[`job${acc}`]["job_date"] = jobDate.getMonth() + 1 + '/' + Number(jobDate.getDate() + 1)
timesheet[`job${acc}`]["day_of_week"] = jobDate.getDay() + 1
timesheet[`job${acc}`]["wo_number"] = job[1][0]
timesheet[`job${acc}`]["job_name"] = job[1][8]
timesheet[`job${acc}`]["work_type_code"] = 'S, M, or T'
timesheet[`job${acc}`]["pw_type_code"] = 'pw type code'
if (job[1][3] === 'OverTime') {
isOvertime = true
}
timesheet[`job${acc}`]["ot_billed"] = isOvertime
if (isOvertime) {
timesheet[`job${acc}`]["reg_hours"] = 0
timesheet[`job${acc}`]["ot_hours"] = Number(`${job[1][1]}`)
} else {
timesheet[`job${acc}`]["reg_hours"] = Number(`${job[1][1]}`)
timesheet[`job${acc}`]["ot_hours"] = 0
}
timesheet[`job${acc}`]["non_worked_hours"] = `${job[1][1]} -- n/w hours`
timesheet[`job${acc}`]["non_worked_code"] = 'non-worked code'
timesheet[`job${acc}`]["is_on_call"] = isOnCall ? '*****ON-CALL WEEK*****' : ''
// delete non-used obj data
for (let i = 0; i <= 10; i++) {
delete timesheet[`job${acc}`][i]
}
}));
var newObj = [];
for (var key in timesheet) {
var obj = timesheet[key];
obj['identifier'] = key;
newObj.push(obj)
}
var newObj = newObj.sort(function(a, b) { return a['day_of_week'] - b['day_of_week']; });
var timesheetSorted = []
for(var i = 0; i < newObj.length; i++) {
timesheetSorted[newObj[i]['identifier']] = newObj[i];
delete timesheetSorted[newObj[i]['identifier']]['identifier']
}
console.log(timesheetSorted)
I get the following sort based on day_of_week
... day_of_week: 5, ...
... day_of_week: 6, ...
... day_of_week: 6, ...
Please accept the answer if it works. Avid follower #timeonsite.js

Conditionally split and concat text

I am trying to conditionally split each of the string in an array.This is my array.
const categories = [
"Department of Natural Science",
"Department of public health and sanitation",
"Department of culture and heritage of state"
];
Again by splitting each string I want to change it to an array. This array contains several chunk of the string. For eg. by splitting Department of culture and heritage of state string I want this to separate Department of Natural Science. Here I want to create every different chunk if the chunk contains more than 13 character in length. That's why Natural and Science separated because if we sum of the length of them it becomes 14 .
Here is what I have tried.
const categories = [
"Department of Natural Science",
"Department of public health and sanitation",
"Department of culture and heritage of state"
];
const arrayOfAllString = []; // results at the end
categories.map(cur => {
// looping the array
const splitedItems = cur.trim().split(" "); // splitting the current string into words
const arrayOfSingleString = []; //
let str = "";
splitedItems.map(item => {
// looping the array of splitted words
if (str.length + item.length > 13) {
// trying to make a chunk
arrayOfSingleString.push(str);
str = ""; // clearing the str because it has been pushed to arrayOfSingleString
} else {
str = str.concat(item + " "); // concat the str with curent word
}
});
arrayOfAllString.push(arrayOfSingleString);
});
console.log(arrayOfAllString);
My expected result would be somehow look like this :
arrayOfAllString = [
["Department of", "Natural", "Science"],
["Department of", "public health", "and", "sanitation"],
["Department of", "culture and", "heritage of", "state"]
];
You could take a generator and return chunks in the wanted length.
function* getJoined(string, size) {
var array = string.split(' '),
i = 0;
while (i < array.length) {
let s = array[i];
while (++i < array.length && (s + array[i]).length < size) {
s += ' ' + array[i];
}
yield s;
}
}
console.log([...getJoined('Department of culture and heritage of state', 13)]);
Classic approach without missusing map.
function getJoined(string) {
var array = string.split(' '),
size = 13,
i = 0,
result = [];
while (i < array.length) {
let s = array[i];
while (++i < array.length && (s + array[i]).length < size) {
s += ' ' + array[i];
}
result.push(s);
}
return result;
}
const categories = ["Department of Natural Science", "Department of public health and sanitation", "Department of culture and heritage of state"];
console.log(categories.map(getJoined));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Made few changes.
1) while clearing, change to str = item; instead of str = ''
2) End of loop, do arrayOfSingleString.push(str); for adding last item.
const categories = [
"Department of Natural Science",
"Department of public health and sanitation",
"Department of culture and heritage of state"
];
const arrayOfAllString = categories.map(cur => {
const splitedItems = cur.trim().split(" ");
const arrayOfSingleString = [];
let str = "";
while (splitedItems.length > 0) {
const item = splitedItems.shift();
if (str.length + item.length >= 13) {
// trying to make a chunk
arrayOfSingleString.push(str);
// clearing the str because it has been pushed to arrayOfSingleString
str = item;
} else {
// concat the str with curent word
str = str ? `${str} ${item}` : item;
}
}
arrayOfSingleString.push(str);
return arrayOfSingleString;
});
console.log(arrayOfAllString);

How to make a counter for each array elements?

i'm new in JS and i need your help with some code.
Ok, here's the thing. I'm trying to make a counter for each element of the "names" array.
Here's the code:
var bool = [false, false, false];
var names = ["Banana", "Apple", "Pear"];
var prices = [10, 20, 30]
var sell = ["not purchased!", "not purchased!", "not purchased!"];
var text;
var count = 0;
function checkbuy (names) {
while (text != 0)
{
text = prompt("product list: " + names)
names.sort((a, b) => { return (text.includes(b)) - (text.includes(a)); });
if (names.indexOf(text) >= 0)
{
bool[names.indexOf(text)] = true;
sell[names.indexOf(text)] = "purchased!";
count[names.indexOf(text)] += 1;
alert("Product " + text.toUpperCase() + " is purchased!");
}
for (i = 0; i < names.length; i++)
{
var myList = [ [names[i], prices[i], bool[i], sell[i], count], [names[i], prices[i], bool[i], sell[i], count], [names[i], prices[i], bool[i], sell[i], count ] ];
// alert(myList[i][0]);
alert(names[i] + " " + sell[i] + ", " + bool[i] + count)
}
alert(names);
}
}
I need to enter a value in the prompt, and if it matches the value from the array, then I need to add that item to the list. The addition must occur by using a counter. For example, I enter a value "Banana" in the "prompt field" and the counter value should increase by one. And I need to do so for each element in the array. How can I do that?
Thanks in advance!
You can maintain two arrays, one for list of items and one for their corresponding counter.
For each prompt item, if it matches an item in names, you can increment the corresponding index in sold. Now, in your sold array, anything == 0 is not purchased, and you don't need another array to track that.
var names = ["Banana", "Apple", "Pear"];
var sold = [0, 0, 0];
let exitPrompt = true;
while (exitPrompt) {
let promptVal = prompt('what do you want to buy?');
if (promptVal && promptVal.indexOf('exit') == 0) {
console.log(sold);
break;
} else {
let nameIndex = names.indexOf(promptVal);
if (nameIndex >= 0) {
sold[nameIndex] += 1;
}
}
}

Adding object properties into a JSON object

I have a JSON object in this format.
[
{
"name": "schoolname",
"line id": "0",
"time": "4-5",
"minage": "15",
"maxage": "35"
},
{
"name": "studentname1",
"line id": "1",
"class": "A"
},
{
"name": "studentname2",
"line id": "2",
"class": "B"
}
]
What I want to do
From a set of specified headers, get it from the "line id" : "0" and set it to the other items.
For Example:
headers = ["time", "minage", "maxage"]
I get these from the "line id" : "0" and give it to others like this.
[
{
"name": "schoolname",
"line id": "0",
"time": "4-5",
"minage": "15",
"maxage": "35"
},
{
"name": "studentname1",
"line id": "1",
"class": "A",
"time": "4-5",
"minage": "15",
"maxage": "35"
},
{
"name": "studentname2",
"line id": "2",
"class": "B",
"time": "4-5",
"minage": "15",
"maxage": "35"
}
]
and then remove the element with "line id" : "0", like this:
[
{
"name": "studentname1",
"line id": "1",
"class": "A",
"time": "4-5",
"minage": "15",
"maxage": "35"
},
{
"name": "studentname2",
"line id": "2",
"class": "B",
"time": "4-5",
"minage": "15",
"maxage": "35"
}
]
It is said that the 1st element would be a "line id" : "0".
What I've tried:
var headers = ["time", "minage", "maxage"]
var data =
[
{
"name": "schoolname",
"line id": "0",
"time": "4-5",
"minage": "15",
"maxage": "35"
},
{
"name": "studentname1",
"line id": "1",
"class": "A"
},
{
"name": "studentname2",
"line id": "2",
"class": "B"
}
];
for(var i = 1; i < data.length; i++)//iterate over the data leaving the 1st line
{
for(var j = 0; j < headers.length; j++)//add each header to the data lines
{
data[i][headers[j]] = data[0][headers[j]];
}
}
data.splice(0,1);
Everything works fine and as intended. Is there a way to reduce the time-complexity of this and make it more efficient.
This now has a time complexity of O(n * m).
Is there a way around of adding these few objects to all the elements? As the key value pairs to be added for all the entries remain the same.
You can use Object.defineProperties like
var arr = [{
"name": "schoolname",
"line id": "0",
"time": "4-5",
"minage": "15",
"maxage": "35"
}, {
"name": "studentname1",
"line id": "1",
"class": "A"
}, {
"name": "studentname2",
"line id": "2",
"class": "B"
}],
headers = ["time", "minage", "maxage"];
function addHeaders(arr, headers) {
var header = arr.splice(0, 1)[0],
propObj = headers.reduce(function(acc, el) {
acc[el] = {
value: header[el],
writable: true,
enumerable: true
};
return acc;
}, {});
for (var i = 0, len = arr.length; i < len; i++) {
Object.defineProperties(arr[i], propObj);
}
return arr;
}
document.getElementById('r').innerHTML = 'initial: ' + JSON.stringify(arr,null,2) + '<br/>';
document.getElementById('r').innerHTML += 'result: ' + JSON.stringify(addHeaders(arr, headers),null,2);
<pre id="r"></pre>
Is that data format you have fixed? You should consider doing something more like
school
-> info (name, etc.)
-> [classes]
-> info
-> [student_ids]
-> [students]
-> info (id)
If you can't change your format. You could do something like what you want with Underscore.js#default. Assuming line_id=0 is always data[0]:
var keys = ['minage','maxage','time'];
var temp = _.pick(data.shift(),keys);
data.forEach(function(e, i, a) {
a[i] = _.default(e,temp);
});
It doesn't really reduce your complexity because you're basically looking an array of size N and update properties count M, meaning you'll have a complexity of O(N*M). If you want something less complex, don't move/copy the data. Reuse it in the current form.
Since you say that you are copying the same values from the 0th element, you can store it in a variable(say new_data) and then iterate through the data array and just add them there.
This is as complex as iterating through data and inserting key-val pairs.
Something like this -
> new_data = {}
//Getting all the content with header keys in data into new_data
> headers.forEach(function(v){new_data[v] = data[0][v]})
//Storing the new_data
> new_data
Object {time: "4-5", minage: "15", maxage: "35"}
//Adding the new_data into data
> data.forEach(function(d_val){
for(k_nd in new_data){
d_val[k_nd] = new_data[k_nd];
}
});
//Removing the 0th array element
> data.splice(0, 1)
//Checking it
> JSON.stringify(data[0])
"{"name":"studentname1","line id":"1","class":"A","time":"4-5","minage":"15","maxage":"35"}"
Using lodash library:
var headers = ["time", "minage", "maxage"];
var data = [{
"name": "schoolname",
"line id": "0",
"time": "4-5",
"minage": "15",
"maxage": "35"
}, {
"name": "studentname1",
"line id": "1",
"class": "A"
}, {
"name": "studentname2",
"line id": "2",
"class": "B"
}];
var temp = _.pick(data[0], headers);
data.splice(0, 1);
for (var i = 0; i < data.length; i++) {
_.merge(data[i], temp);
}
var result = JSON.stringify(data);
$('#result').text(result);
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.0/lodash.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="result"></div>
EDIT:
Found a significant performance booster, faster than all other tested solutions so far: extracting one of the loops and applying via: new Function(...). Essentially an eval-like approximation of Object.defineProperties(...). Have added this to the performance tests below:
function addHeadersNewFunc(arr, headers) {
//console.time('addHeadersNewFunc');
var header = arr.shift(),
funcBody = ['return item;'],
headerPropName,
setProps;
for(var h = headers.length; h--;) {
headerPropName = headers[h];
funcBody.unshift('item["' + headerPropName + '"]="' + header[headerPropName] + '";'); //unshift since loop is reversed and we want props in same add order as other implementations, and we've already added our first line
}
setProps = new Function('item', funcBody.join('')); //warning, this is a form of 'eval()'...
for (var i = arr.length; i--;)
{
setProps(arr[i]);
}
//console.timeEnd('addHeadersNewFunc');
return arr;
}
Some interesting results testing a few different approaches. I've only just whipped up the performance testing code, happy for any recommended improvements. I've also added some additional implementations - a string replacement approach, and a lazy getter.
In general it looks like the original loop outperforms most of the other suggestions; except for the implementation by #Chris Anderson-MSFT using Underscore's defaults when tested in Chrome, which appeared to actually be faster (didn't fare as well in IE, however). Otherwise, the lazies performed consistently well, also. (* EDIT: as per above, implementation using new Function() eventually found to be fastest; for large objects/iterations, significantly).
Sample output of the below snippet (Chrome 43):
Items: 2000
Output of functions is all consistent: true
Testing...
addHeadersOrig x 1000: [Avg] 2.3977ms, [Min] 2.3170ms, [Max] 2.8280ms
addHeadersDefineProp x 1000: [Avg] 6.3481ms, [Min] 6.1010ms, [Max] 15.1750ms
addHeadersStrReplace x 1000: [Avg] 3.0551ms, [Min] 2.6630ms, [Max] 5.9910ms
addHeadersUnderscoreDefaults x 1000: [Avg] 1.4344ms, [Min] 1.1800ms, [Max] 9.5100ms
addHeadersLazy x 1000: [Avg] 2.4529ms, [Min] 2.3460ms, [Max] 6.0770ms
addHeadersLazyMemo x 1000: [Avg] 2.4837ms, [Min] 2.3760ms, [Max] 3.8420ms
addHeadersNewFunc x 1000: [Avg] 0.0959ms, [Min] 0.0430ms, [Max] 0.5070ms
(function() {
"use strict";
var arr = [{
"name": "schoolname",
"line id": "0",
"time": "4-5",
"minage": "15",
"maxage": "35"
}, {
"name": "studentname1",
"line id": "1",
"class": "A"
}, {
"name": "studentname2",
"line id": "2",
"class": "B"
}],
headers = ["time", "minage", "maxage"];
//add some more...
for (var i = 3, iLen = 2000; i < iLen; i++) {
arr.push({
name: "studentname" + i,
"line id": String(i),
"class": "C"
});
}
function addHeadersOrig(arr, headers) {
//console.time('addHeadersOrig');
for (var i = 1; i < arr.length; i++) //iterate over the data leaving the 1st line
{
for (var j = 0; j < headers.length; j++) //add each header to the data lines
{
arr[i][headers[j]] = arr[0][headers[j]];
}
}
arr.splice(0, 1);
//console.timeEnd('addHeadersOrig');
return arr;
}
function addHeadersDefineProp(arr, headers) {
//console.time('addHeadersDefineProp');
var header = arr.splice(0, 1)[0],
propObj = headers.reduce(function headerReduce(acc, el) {
acc[el] = {
value: header[el],
writable: true,
enumerable: true
};
return acc;
}, {});
for (var i = 0, len = arr.length; i < len; i++) {
Object.defineProperties(arr[i], propObj);
}
//console.timeEnd('addHeadersDefineProp');
return arr;
}
function addHeadersStrReplace(arr, headers) {
//console.time('addHeadersStrReplace');
var header = arr.shift(),
propObj = {};
for (var i = 0; i < headers.length; i++) {
propObj[headers[i]] = header[headers[i]];
}
//stringify the array, replace each '}' with a ',' followed by the the stringified propObj (minus its opening bracket) which brings its own closing bracket to make up for the one we replaced; then parse back to an object
arr = JSON.parse(JSON.stringify(arr).replace(/\}/g, ',' + JSON.stringify(propObj).slice(1)));
//console.timeEnd('addHeadersStrReplace');
return arr;
}
//only runs using lodash, not underscore
function addHeadersLodashMerge(arr, headers) {
//console.time('addHeadersLodashMerge');
var temp = _.pick(arr.shift(), headers);
for (var i = 0; i < arr.length; i++) {
_.merge(arr[i], temp);
}
//console.timeEnd('addHeadersLodashMerge');
return arr;
}
//runs under both lodash and underscore - faster in underscore AFAICT
function addHeadersUnderscoreDefaults(arr, headers) {
//console.time('addHeadersUnderscoreDefaults');
var temp = _.pick(arr.shift(), headers);
arr.forEach(function(e, i, a) {
a[i] = _.defaults(e, temp);
});
//console.timeEnd('addHeadersUnderscoreDefaults');
return arr;
}
function addHeadersNewFunc(arr, headers) {
//console.time('addHeadersNewFunc');
var header = arr.shift(),
funcBody = ['return item;'],
headerPropName,
setProps;
for(var h = headers.length; h--;) {
headerPropName = headers[h];
funcBody.unshift('item["' + headerPropName + '"]="' + header[headerPropName] + '";'); //unshift since loop is reversed and we want props in same add order as other implementations, and we've already added our first line
}
setProps = new Function('item', funcBody.join('')); //warning, this is a form of 'eval()'...
for (var i = arr.length; i--;)
{
setProps(arr[i]);
}
//console.timeEnd('addHeadersNewFunc');
return arr;
}
function addHeadersLazy(arr, headers) {
//console.time('addHeadersLazy');
var lazy = new Lazy(arr, headers),
result = [];
for (var i = 1; i < arr.length; i++) {
result.push(lazy.get(i));
}
//console.timeEnd('addHeadersLazy');
return result;
}
function addHeadersLazyMemo(arr, headers) {
//console.time('addHeadersLazyMemo');
var lazy = new Lazy(arr, headers, true),
result = [];
for (var i = 1; i < arr.length; i++) {
result.push(lazy.get(i));
}
//console.timeEnd('addHeadersLazyMemo');
return result;
}
function Lazy(arr, headers, useMemo) {
var headerValSrc = arr[0],
headerLen = headers.length,
memo = [];
function _get(index) {
for (var j = 0; j < headerLen; j++) {
arr[index][headers[j]] = headerValSrc[headers[j]];
}
return arr[index];
}
function _getMemo(index) {
if (memo[index]) {
return memo[index];
}
for (var j = 0; j < headerLen; j++) {
arr[index][headers[j]] = headerValSrc[headers[j]];
}
return (memo[index] = arr[index]);
}
return {
get: (useMemo ? _getMemo : _get)
};
}
function clone(data) {
return JSON.parse(JSON.stringify(data));
}
function perfTest(name, testFunc) {
name = name ? name : "Test";
var iterations = 1000,
argsSliced = Array.prototype.slice.call(arguments, 2),
args = [],
t0 = 0,
t1,
t2,
t3,
tmin = 1000000,
tmax = 0,
output;
setTimeout(function delayAllowingDocWrite() {
for (var i = 0; i < iterations; i++) {
args = clone(argsSliced);
t1 = performance.now();
testFunc.apply(this, args);
t2 = performance.now();
t3 = t2 - t1;
tmin = t3 < tmin ? t3 : tmin;
tmax = t3 > tmax ? t3 : tmax;
t0 += t3;
}
output = name + " x " + iterations + ": [Avg] " + (t0 / iterations).toFixed(4) + "ms, [Min] " + tmin.toFixed(4) + "ms, [Max] " + tmax.toFixed(4) + "ms";
console.log(output);
document.body.innerHTML += (output + "<br />");
}, 10);
return testFunc.apply(this, clone(argsSliced)); //return output of function immed, once, for comparing results
}
document.body.innerHTML += "Items: " + arr.length + "<br />";
console.log("Items: ", arr.length);
//*
var resultOrig = perfTest("addHeadersOrig", addHeadersOrig, arr, headers),
resultDefineProp = perfTest("addHeadersDefineProp", addHeadersDefineProp, arr, headers),
resultStrReplace = perfTest("addHeadersStrReplace", addHeadersStrReplace, arr, headers),
//resultLodashMerge = perfTest("addHeadersLodashMerge", addHeadersLodashMerge, arr, headers), //re-enable if using lodash.min.js
resultUnderscoreDefaults = perfTest("addHeadersUnderscoreDefaults", addHeadersUnderscoreDefaults, arr, headers),
resultLazy = perfTest("addHeadersLazy", addHeadersLazy, arr, headers),
resultLazyMemo = perfTest("addHeadersLazyMemo", addHeadersLazyMemo, arr, headers),
resultNewFunc = perfTest("addHeadersNewFunc", addHeadersNewFunc, arr, headers);
//*/
var resultOrigStr = JSON.stringify(resultOrig),
outputIsConsistent = "Output of functions is all consistent: " + (
resultOrigStr === JSON.stringify(resultDefineProp) &&
resultOrigStr === JSON.stringify(resultStrReplace) &&
//resultOrigStr === JSON.stringify(resultLodashMerge) &&
resultOrigStr === JSON.stringify(resultUnderscoreDefaults) &&
resultOrigStr === JSON.stringify(resultLazy) &&
resultOrigStr === JSON.stringify(resultLazyMemo) &&
resultOrigStr === JSON.stringify(resultNewFunc)
);
document.body.innerHTML += outputIsConsistent + "<br /><em>Testing...</em><br /><br />";
console.log(outputIsConsistent);
if (!window.performance || !window.performance.now) {
document.body.innerHTML += "Your browser does not seem to support performance.now()...";
}
/*
var arr1 = clone(arr),
arr2 = clone(arr),
arr3 = clone(arr),
arr4 = clone(arr),
arr5 = clone(arr),
arr6 = clone(arr);
var resultOrig = addHeadersOrig(arr1, headers),
resultDefineProp = addHeadersDefineProp(arr2, headers),
resultStrReplace = addHeadersStrReplace(arr3, headers),
resultLodash = addHeadersLodash(arr4, headers),
resultLazy = addHeadersLazy(arr5, headers),
resultLazyMemo = addHeadersLazyMemo(arr6, headers);
console.log(resultOrig);
console.log(resultDefineProp);
console.log(resultStrReplace);
console.log(resultLodash);
console.log(resultLazy);
console.log(resultLazyMemo);
//*/
})();
body {
font-size: 0.8em;
font-family: "Arial", sans-serif;
}
<!--script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.0/lodash.min.js"></script-->
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<p>Use a browser that supports performance.now().</p>
For easier playing around with: Plnkr

Categories

Resources