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
Related
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
I am trying to build a javascript function to get all combinations of size N from an array.
lets say I got:
const Xarray = ["19", "21","42","23", "25", "28"];
const n = 4;
combinationsOfN(Xarray, n) =>
[ ["19", "21", "42", "23"],
["19", "21", "42", "25"],
["19", "21", "42", "28"],
["19", "21", "23", "25"],
["19", "21", "23", "28"],
["19", "21", "25", "28"],
…. ]
Doing this by yourself might be rather tough, because I've tried that. There's already a js tool that does this for you, combinations.js
/**
* Copyright 2012 Akseli Palén.
* Created 2012-07-15.
* Licensed under the MIT license.
*/
function k_combinations(set, k) {
var i, j, combs, head, tailcombs;
if (k > set.length || k <= 0) {
return [];
}
if (k == set.length) {
return [set];
}
if (k == 1) {
combs = [];
for (i = 0; i < set.length; i++) {
combs.push([set[i]]);
}
return combs;
}
combs = [];
for (i = 0; i < set.length - k + 1; i++) {
head = set.slice(i, i+1);
tailcombs = k_combinations(set.slice(i + 1), k - 1);
for (j = 0; j < tailcombs.length; j++) {
combs.push(head.concat(tailcombs[j]));
}
}
return combs;
}
function combinations(set) {
var k, i, combs, k_combs;
combs = [];
for (k = 1; k <= set.length; k++) {
k_combs = k_combinations(set, k);
for (i = 0; i < k_combs.length; i++) {
combs.push(k_combs[i]);
}
}
return combs;
}
var array = ["19", "21","42","23", "25", "28"];
document.body.innerHTML += "<pre>" + JSON.stringify(k_combinations(array, 4), false, "\t") + "</pre>";
Well i have to say i hate JS sometimes. I definitely hate push() that for sure. When you do functional you need more and more a reference to the mutated objects. A method mutating an object it's called upon shall return a reference to that object.
Anyways the code could have been much simpler looking but alas... this is as far as it gets. It's nothing more than a simple recursive run. The complicated looking parts are actually the stupid parts such as;
a.slice(0,i).concat(a.slice(i+1))
in fact means delete the element at index position i and return the resulting array. When you need to use this functionality as a single instruction or a chainable instruction as an argument to a function this seems to be the only way. Just like the
(t.push(c),t)
instruction. Which means push c to t array and return t array. Silly push(c) would return the length... I hate it. You give me the reference man i can get the length from that if needed. So the rest is easy to understand.
So i have two solutions one for the permutations and one for the combinations.
var xarray = ["19", "21", "42", "23", "25", "28"],
n = 4;
function getPermutations(a,n,s=[],t=[]){
return a.reduce((p,c,i,a) => { n > 1 ? getPermutations(a.slice(0,i).concat(a.slice(i+1)), n-1, p, (t.push(c),t))
: p.push((t.push(c),t).slice(0));
t.pop();
return p},s)
}
document.write("<pre>" + JSON.stringify(getPermutations(xarray,n),null,2) + "</pre>");
And now the combinations...
var xarray = ["19", "21", "42", "23", "25", "28"],
n = 4;
function getCombinations(a,n,s=[],t=[]){
return a.reduce((p,c,i,a) => { n > 1 ? getCombinations(a.slice(i+1), n-1, p, (t.push(c),t))
: p.push((t.push(c),t).slice(0));
t.pop();
return p},s)
}
document.write("<pre>" + JSON.stringify(getCombinations(xarray,n),null,2) + "</pre>");
I offer two solutions, the first with a result which includes the items at any place.
function combine(array, length) {
function c(l, r) {
var i, ll;
if (r.length === length) {
result.push(r);
return;
}
for (i = 0; i < l.length; i++) {
ll = l.slice();
c(ll, r.concat(ll.splice(i, 1)));
}
}
var result = [];
c(array, []);
return result;
}
document.write('<pre>' + JSON.stringify(combine(["19", "21", "42", "23", "25", "28"], 4), 0, 4) + '</pre>');
The second which returns only one item at in original order. This result set is shorter then the above and like the answer of akinuri.
function combine(array, length) {
function c(l, r) {
var ll = l.slice();
if (r.length === length) {
result.push(r);
return;
}
while (ll.length) {
c(ll, r.concat(ll.shift()));
}
}
var result = [];
c(array, []);
return result;
}
document.write('<pre>' + JSON.stringify(combine(["19", "21", "42", "23", "25", "28"], 4), 0, 4) + '</pre>');
Edit:
Ultimately I want to print out something like this:
Scooter has 3 materials: "wood", "metal", "glass"; Baseball bat has two materials: "wood", metal" only using a for loop.
I'm using the following:
var data = [
{
"title": "scooter",
"materials": ["wood", "metal", "glass"]
},
{
"title": "baseball bat",
"materials": ["wood", "metal"]
},
{
"title": "coffee table",
"materials": ["wood"]
}];
I thought I could do a simple pass through with a for loop to get the title, count, and create an array with something like this:
function testing () {
let items = []; //contains the titles
let container = []; //contains the materials
let count = 0; //count of items
for (let i=0; i<data.length; i++){
if (data[i].materials.length > 2){
container.push(data[i].materials);
count = data[i].materials.length;
items.push(data[i].title)
}
}return container;
}
console.log(testing);
I don't know if the code is correct to get what I want out. Any thoughts?
Edit: So when I run this code I'm simply getting the list of the materials. When doing a return items and console.log(testing); I'm getting a list of the items.
It also looks like the count is just getting an array count of the titles not of the materials.
Further Edit with additional code:
function testing () {
let items = [];
let container = [];
let count = 0;
let result = '';
for (let i=0; i<data.length; i++){
if (data[i].materials.length > 2){
container.push(data[i].materials);
items.push(data[i].title)
for (let j=0; j<container.length; j++){
result= `${items[j]} has materials :${container[j]}`;
}
}
}return result;
}
console.log(testing());
With the above I can get the last title to show that meets the if statement with a list of items. I'm not however able to get a count of the materials. So for example it's simply showing:
Baseball bat has materials wood, metal
The question I'm facing then is how do I get the count and then display all objects that match. So it would be instead:
Scooter has 3 materials that are: wood, metal, glass
Baseball bat has 2 materials that are: wood, metal
If I understood your question correctly, you don't really need for loop. Arrays have map method, which can apply your function to each element of the array.
var data = [
{
"title": "scooter",
"materials": ["wood", "metal", "glass"]
},
{
"title": "baseball bat",
"materials": ["wood", "metal"]
},
{
"title": "coffee table",
"materials": ["wood"]
}];
var result = data.map(item => `${item.title} has ${item.materials.join(', ')}`).join('\n')
console.log(result);
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
See if the below code helps you in any way:
console.log(data.map(item => `${item.title} has ${item.materials.join(', ')}`).join('\n'))
All you need to do is map each of the items and concatenate the formatted strings.
You will need to know the count of the materials to display the quantity.
const capitalize = s => s.charAt(0).toUpperCase() + s.substr(1);
const quantity = x => 'one,two,three,four,...'.split(',')[x - 1];
var data = [{
"title": "scooter",
"materials": ["wood", "metal", "glass"]
}, {
"title": "baseball bat",
"materials": ["wood", "metal"]
}, {
"title": "coffee table",
"materials": ["wood"]
}
];
console.log(data.map(item => {
return [
capitalize(item.title),
' has ',
quantity(item.materials.length),
' material' + (item.materials.length > 1 ? 's' : '') + ': ',
item.materials.map(material => {
return '"' + material + '"';
}).join(', ')
].join('');
}).join('; '));
.as-console-wrapper { top: 0; max-height: 100% !important; }
first,
console.log(testing);
is just going to return the text of the function, you have to run the function, like this
console.log(testing());
Here's my function, written is ES6:
var data = [
{
"title": "scooter",
"materials": ["wood", "metal", "glass"]
},
{
"title": "baseball bat",
"materials": ["wood", "metal"]
},
{
"title": "coffee table",
"materials": ["wood"]
}];
function newTestingES6() {
let printStr = '';
for (let obj of data) {
printStr = `${obj.title} has ${obj.materials.length} materials: ${[...obj.materials]}`
console.log(printStr);
}
return 1;
}
function runTest() {
console.log(newTestingES6());
}
Try to write a function, that works for one object inside your array. If this works you can simple call this function inside a for-loop and push it to an array or use Array.map() which returns an array by default.
The Function
function createConstructionKit(object) {
var matarials = object.materials.join('", "') // this will give you for the first case -> wood", "metal", "glass
var title = object.title // no logic -> just did it to have lesser to write in the next line..
var capitilizedTitle = title.charAt(0).toUpperCase() + title.slice(1) // capitilize the first one and return all letters off the first index
return capitilizedTitle + ' has ' + object.materials.length + ' materials: "' + matarials + '"'
}
Solution with Map
var buildingInstructionsdata = data.map(function(object) {
return createConstructionKit(object)
})
Solution with a Loop
var buildingInstructionsdata = []
for (var i = 0; i < data.length; i++) {
buildingInstructionsdata.push(createConstructionKit(data[i]))
}
This was what I ended up getting to work:
function testing () {
for (let i = 0; i < data.length; i++) {
if (data[i].materials.length >2) {
console.log(`${data[i].title} has ${data[i].materials.length}
materials:`);
for (let j = 0; j < data[i].materials.length; j++) {
const material = data[i].materials[j];
console.log("- " + material);
}
}
}
}
console.log(testing());
I am trying to build a javascript function to get all combinations of size N from an array.
lets say I got:
const Xarray = ["19", "21","42","23", "25", "28"];
const n = 4;
combinationsOfN(Xarray, n) =>
[ ["19", "21", "42", "23"],
["19", "21", "42", "25"],
["19", "21", "42", "28"],
["19", "21", "23", "25"],
["19", "21", "23", "28"],
["19", "21", "25", "28"],
…. ]
Doing this by yourself might be rather tough, because I've tried that. There's already a js tool that does this for you, combinations.js
/**
* Copyright 2012 Akseli Palén.
* Created 2012-07-15.
* Licensed under the MIT license.
*/
function k_combinations(set, k) {
var i, j, combs, head, tailcombs;
if (k > set.length || k <= 0) {
return [];
}
if (k == set.length) {
return [set];
}
if (k == 1) {
combs = [];
for (i = 0; i < set.length; i++) {
combs.push([set[i]]);
}
return combs;
}
combs = [];
for (i = 0; i < set.length - k + 1; i++) {
head = set.slice(i, i+1);
tailcombs = k_combinations(set.slice(i + 1), k - 1);
for (j = 0; j < tailcombs.length; j++) {
combs.push(head.concat(tailcombs[j]));
}
}
return combs;
}
function combinations(set) {
var k, i, combs, k_combs;
combs = [];
for (k = 1; k <= set.length; k++) {
k_combs = k_combinations(set, k);
for (i = 0; i < k_combs.length; i++) {
combs.push(k_combs[i]);
}
}
return combs;
}
var array = ["19", "21","42","23", "25", "28"];
document.body.innerHTML += "<pre>" + JSON.stringify(k_combinations(array, 4), false, "\t") + "</pre>";
Well i have to say i hate JS sometimes. I definitely hate push() that for sure. When you do functional you need more and more a reference to the mutated objects. A method mutating an object it's called upon shall return a reference to that object.
Anyways the code could have been much simpler looking but alas... this is as far as it gets. It's nothing more than a simple recursive run. The complicated looking parts are actually the stupid parts such as;
a.slice(0,i).concat(a.slice(i+1))
in fact means delete the element at index position i and return the resulting array. When you need to use this functionality as a single instruction or a chainable instruction as an argument to a function this seems to be the only way. Just like the
(t.push(c),t)
instruction. Which means push c to t array and return t array. Silly push(c) would return the length... I hate it. You give me the reference man i can get the length from that if needed. So the rest is easy to understand.
So i have two solutions one for the permutations and one for the combinations.
var xarray = ["19", "21", "42", "23", "25", "28"],
n = 4;
function getPermutations(a,n,s=[],t=[]){
return a.reduce((p,c,i,a) => { n > 1 ? getPermutations(a.slice(0,i).concat(a.slice(i+1)), n-1, p, (t.push(c),t))
: p.push((t.push(c),t).slice(0));
t.pop();
return p},s)
}
document.write("<pre>" + JSON.stringify(getPermutations(xarray,n),null,2) + "</pre>");
And now the combinations...
var xarray = ["19", "21", "42", "23", "25", "28"],
n = 4;
function getCombinations(a,n,s=[],t=[]){
return a.reduce((p,c,i,a) => { n > 1 ? getCombinations(a.slice(i+1), n-1, p, (t.push(c),t))
: p.push((t.push(c),t).slice(0));
t.pop();
return p},s)
}
document.write("<pre>" + JSON.stringify(getCombinations(xarray,n),null,2) + "</pre>");
I offer two solutions, the first with a result which includes the items at any place.
function combine(array, length) {
function c(l, r) {
var i, ll;
if (r.length === length) {
result.push(r);
return;
}
for (i = 0; i < l.length; i++) {
ll = l.slice();
c(ll, r.concat(ll.splice(i, 1)));
}
}
var result = [];
c(array, []);
return result;
}
document.write('<pre>' + JSON.stringify(combine(["19", "21", "42", "23", "25", "28"], 4), 0, 4) + '</pre>');
The second which returns only one item at in original order. This result set is shorter then the above and like the answer of akinuri.
function combine(array, length) {
function c(l, r) {
var ll = l.slice();
if (r.length === length) {
result.push(r);
return;
}
while (ll.length) {
c(ll, r.concat(ll.shift()));
}
}
var result = [];
c(array, []);
return result;
}
document.write('<pre>' + JSON.stringify(combine(["19", "21", "42", "23", "25", "28"], 4), 0, 4) + '</pre>');
I have an object containing a bunch of similar objects. I would like to get the count of the object only for those where a object property (status) is of a given value (true). For instance, the count of the below object is 3.
{
6:{"name":"Mary", "status":true},
2:{"name":"Mike", "status":true},
1:{"name":"John", "status":false},
4:{"name":"Mark", "status":true},
5:{"name":"Jane", "status":false}
}
Thanks
I recognize you are iterating over an object, not an array, but since the others provide solutions for arrays I recon a solution with array.reduce is in place. Works in most modern browsers (IE9+)
var myArray = [
{"name":"Mary", "status":true},
{"name":"Mike", "status":true},
{"name":"John", "status":false},
{"name":"Mark", "status":true},
{"name":"Jane", "status":false}
];
var result = myArray.reduce(function(previousValue, currentObject){
return previousValue + (currentObject.status ? 1: 0);
}, 0);
Specifically:
var i = 0;
var count = 0;
while (i < array.length) {
if (array[i]['status'] == true) count += 1;
i += 1;
}
More generally, you can use some functional programming:
function count_matches(array, func) {
var i = 0;
var count = 0;
while (i < array.length) {
if (func(array[i])) count += 1;
i += 1;
}
return count;
}
function status_true(obj) {
return obj['status'] == true;
}
count_matches(array, status_true);
The above snippets do the same thing, but the latter is more flexible/potentially neater.
just loop over the array and count how many times the status property is true.
var count = 0;
for (var i = 0; i < yourArray.length; i++){
var current = yourArray[i];
if (current.status) count++
}
LinqJs would work (might be too much for the simple example posted in the question) -
http://linqjs.codeplex.com/
var jsonArray = [
{ "user": { "id": 100, "screen_name": "d_linq" }, "text": "to objects" },
{ "user": { "id": 130, "screen_name": "c_bill" }, "text": "g" },
{ "user": { "id": 155, "screen_name": "b_mskk" }, "text": "kabushiki kaisha" },
{ "user": { "id": 301, "screen_name": "a_xbox" }, "text": "halo reach" }]
// ["b_mskk:kabushiki kaisha", "c_bill:g", "d_linq:to objects"]
var queryResult = Enumerable.From(jsonArray)
.Where(function (x) { return x.user.id < 200 })
.OrderBy(function (x) { return x.user.screen_name })
.Select(function (x) { return x.user.screen_name + ':' + x.text })
.ToArray();
// shortcut! string lambda selector
var queryResult2 = Enumerable.From(jsonArray)
.Where("$.user.id < 200")
.OrderBy("$.user.screen_name")
.Select("$.user.screen_name + ':' + $.text")
.ToArray();
var obj = {
6:{"name":"Mary", "status":true},
2:{"name":"Mike", "status":true},
1:{"name":"John", "status":false},
4:{"name":"Mark", "status":true},
5:{"name":"Jane", "status":false}
};
var count = 0;
for (var prop in obj) {
if(obj[prop].status === true){
count += 1;
}
}
console.log("Output: "+count);
$("#debug").text("Output: "+count);
live demo http://jsbin.com/uwucid/2/edit