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>');
Related
I have an array like below let's say
var myArray = ["1", "", "", "2","3","","","8"];
And I want to fill each " " value with previous not null value
According to that expectation should be like below
var myArray = ["1", "2", "2", "2","3","8","8","8"];
Here what I tried but didn't work
var myArray = ["1", "", "", "2","3","","","8"];
function myFunction() {
let currentEl=myArray [0];
let prevIndex=0;
fruits.map((e,a)=>{
if(e!="" && myArray[a-1]==""){
currentEl=e;
let interval=arrayGenerate(a-currentIndex-1,currentEl);
fruits.splice(currentIndex-1, currentEl+1, interval);
currentIndex=a;
}
})
}
function arrayGenerate(iteration,value){
let arr=[];
for(var i=0;i<iteration;i++){
arr.push(value);
}
return arr;
}
console.log(myArray)
You could map the new values and find the missing following value.
var array = ["1", "", "", "2", "3", "", "", "8"],
result = array.map((v, i, a) => v || a.find((w, j) => j > i && w));
console.log(result);
A solution with the same array, by looping from the end and storing the last value.
var array = ["1", "", "", "2", "3", "", "", "8"],
i = array.length,
value;
while (i--) {
if (array[i]) value = array[i];
else array[i] = value;
}
console.log(array);
I have done it like this. I loop over and awlays check the next element if its falsy and not the last element.
var myArray = ["1", "", "", "2","3","","","8"];
function fillArr(arr){
for(let i = arr.length - 1; i >= 0; i--){
if(!arr[i - 1] && i != arr.length){
arr[i - 1] = arr[i];
}
}
return arr;
}
let result = fillArr(myArray);
console.log(result);
You can make use of a stack array to stack indices of null items and then unstack them whenever you encounter a non null item in myArray. (stack is not a reserved keyword so you can call it anything) :
var stack = []
for(var i = 0; i < myArray.length; i++){
if(myArray[i] === "")
stack.push(i);
else if(stack.length !== 0){
for(var j = stack.length - 1 ; j > =0; j--){
myArray[stack[j]] = myArray[i];
stack.splice(j,1);
}
}
}
It's interesting how many different ways this can be done. Just another slightly ugly way to do the same thing:
const fill = (arr, lastVal) => (
arr.reverse()
.map(el => el.length > 0 ? lastVal = el : lastVal)
.reverse()
);
console.log(fill(["1", "", "", "2","3","","","8"]));
Well my brains are melting... I am trying to accomplish the following:
I know how many arrays and how many elements each array have.
These numbers are dynamic, but lets say there's: 3 arrays with 18 elements in each.
Example:
["106","142","112","77","115","127","87","127","156","118","91","93","107","151","110","79","40","186"]
["117","139","127","108","172","113","79","128","121","104","105","117","139","109","137","109","82","137"]
["111","85","110","112","108","109","107","89","104","108","123","93","125","174","129","113","162","159"]
Now I want to get the average of element 1 of all three arrays, and element 2 of all three and so on.
The end result should be one array with the average of all 18 elements.
Something like:
var result_array = [];
for (i = 0; i < 3; i++) {
result_array.push(arrayone[i] + arraytwo[i] + arraythree[i]) / 3
}
This would work if 3 was fixed, but the amount of arrays is dynamic.
Hope this make sense...
var arrays = [
[106,142,112,77,115,127,87,127,156,118,91,93,107,151,110,79,40,186],
[117,139,127,108,172,113,79,128,121,104,105,117,139,109,137,109,82,137],
[111,85,110,112,108,109,107,89,104,108,123,93,125,174,129,113,162,159],
[104,153,110,112,108,109,107,89,104,108,123,93,125,174,129,113,162,159]
/* Can be any amount of arrays */
],
result = [];
//Rounding to nearest whole number.
for(var i = 0; i < arrays[0].length; i++){
var num = 0;
//still assuming all arrays have the same amount of numbers
for(var i2 = 0; i2 < arrays.length; i2++){
num += arrays[i2][i];
}
result.push(Math.round(num / arrays.length));
}
alert(result);
You did tag this question with underscore.js, so you can use the _.zip method. This puts all the first elements in an array together and so on. You can then average each of those arrays.
See CodePen.
var arr1 = ["106","142","112","77","115","127","87","127","156","118","91","93","107","151","110","79","40","186"]
var arr2 = ["117","139","127","108","172","113","79","128","121","104","105","117","139","109","137","109","82","137"]
var arr3 = ["111","85","110","112","108","109","107","89","104","108","123","93","125","174","129","113","162","159"]
// ... as many more arrays as you want
var avgEmAll = function (arrays) {
// zip with array of arrays https://stackoverflow.com/a/10394791/327074
return _.zip.apply(null, arrays).map(avg)
}
// average an array https://stackoverflow.com/a/10624256/327074
var avg = function (x) {
return x.reduce(function (y, z) {return Number(y) + Number(z)}) / x.length
}
console.log(avgEmAll([arr1, arr2, arr3]))
With ES6 arrow functions (CodePen):
const avgEmAll = arrays => _.zip.apply(null, arrays).map(avg)
const sum = (y, z) => Number(y) + Number(z)
const avg = x => x.reduce(sum) / x.length
Wrote this solution in Java but you can get help with logic. Hope it helps.
ArrayList<Integer> averageArrayList = new ArrayList<>();
int arrayListLength = arrayList1.length(); //assuming previous arraylists have same size.
for(int i=0; i<arrayListLength; i++){
int averageValue = (arrayList1.get(i) + arrayList2.get(i) + arrayList3.get(i)) / 3;
//adds average value to current index.
averageArrayList.add(i, averageValue);
}
//averageArrayList is ready with your values..
var result = getAvg( [ 1, 2, 3 ], [ 2, 3, 4 ] )
console.log( result ) // [ 1.5, 2.5, 3.5 ]
function getAvg() {
var a = arguments
var nar = a.length
var nel = a[ 0 ].length
var el, ar, avg, avgs = []
for( el = 0; el < nel; ++el ) {
avg = 0
for( ar = 0; ar < nar; ++ar ) avg += a[ ar ][ el ]
avgs[ el ] = avg / nar
}
return avgs
}
This function will take any number of arrays and figure out their average. It uses the + operator to automatically cast the string to a number. It is dynamic in that it doesn't care what length the arrays are, or how many there are, as long as they are all equal in length. Since we can assume that they are all the same length( based on the question ) it iterates over the length of the first array. It doesn't round because that wasn't asked for.
function average_of_arrays(...arrays) {
let result = [];
for(let array_index in arrays[0]) {
let total = 0;
for (let arr of arrays) {
total += +arr[array_index]
}
result.push(total / arrays.length);
}
return result;
}
let arr1 = ["106", "142", "112", "77", "115", "127", "87", "127", "156", "118", "91", "93", "107", "151", "110", "79", "40", "186"];
let arr2 = ["117", "139", "127", "108", "172", "113", "79", "128", "121", "104", "105", "117", "139", "109", "137", "109", "82", "137"];
let arr3 = ["111", "85", "110", "112", "108", "109", "107", "89", "104", "108", "123", "93", "125", "174", "129", "113", "162", "159"];
function average_of_arrays(...arrays) {
let result = [];
for(let array_index in arrays[0]) {
let total = 0;
for (let arr of arrays) {
total += +arr[array_index]
}
result.push(total / arrays.length);
}
return result;
}
console.log(average_of_arrays(arr1, arr2, arr3));
The answer here is to use a loop. Let's call your arrays arr1, arr2, and arr3.
var averages = [];
for(i = 0; i < arr1.length; i++) {
var a = arr1[i];
var b = arr2[i];
var c = arr3[i];
var avg = (a + b + c) / 3;
averages.push(avg);
}
For each iteration of this loop, it will:
-Assign the next digit of each array to a variable (starting at index 0)
-Find the average of the three numbers
-Add the result of the calculation to the averages array
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 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
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