var arr = [[1,2], [3,4], [5,6]];
This is an array of arrays. It is a little bit easier to read like this:
var arr = [
[1,2],
[3,4],
[5,6]
];
That makes it a little bit easier to see that you have an array of 3 arrays. The outer 'for' will loop through each of 1st level arrays. So the very first outer for loop when i=0 you are going to grab the first inner array [1,2]:
for (var i=0; i < arr.length; i++) {
//First time through i=0 so arr[i]=[1,2];
}
In the inner loop you are going to loop through each of the 3 inner arrays one at a time.
for (var j=0; j < arr[i].length; j++) {
//Handle inner array.
}
This argument grabs the length of the inner array:
arr[i].length
So on your first time through the outer loop i=0 and arr[i] is going to equal [1,2] because you are grabbing the 0th element. Remember, arrays elements are always counted starting at 0, not 1.
Finally you are printing out the results with:
console.log(arr[i][j]);
The first time through you can break it down a little. i=0 and j=0. arr[0][0] which translates as grab the first element from the outer array and then the first element from the first inner array. In this case it is '1':
[
[1,2], <-- 0
[3,4], <-- 1
[5,6] <-- 2
];
The code will loop through the first first set [1,2], then the second [3,4], and so on.
Answer from Gremash on Stack Overflowvar arr = [[1,2], [3,4], [5,6]];
This is an array of arrays. It is a little bit easier to read like this:
var arr = [
[1,2],
[3,4],
[5,6]
];
That makes it a little bit easier to see that you have an array of 3 arrays. The outer 'for' will loop through each of 1st level arrays. So the very first outer for loop when i=0 you are going to grab the first inner array [1,2]:
for (var i=0; i < arr.length; i++) {
//First time through i=0 so arr[i]=[1,2];
}
In the inner loop you are going to loop through each of the 3 inner arrays one at a time.
for (var j=0; j < arr[i].length; j++) {
//Handle inner array.
}
This argument grabs the length of the inner array:
arr[i].length
So on your first time through the outer loop i=0 and arr[i] is going to equal [1,2] because you are grabbing the 0th element. Remember, arrays elements are always counted starting at 0, not 1.
Finally you are printing out the results with:
console.log(arr[i][j]);
The first time through you can break it down a little. i=0 and j=0. arr[0][0] which translates as grab the first element from the outer array and then the first element from the first inner array. In this case it is '1':
[
[1,2], <-- 0
[3,4], <-- 1
[5,6] <-- 2
];
The code will loop through the first first set [1,2], then the second [3,4], and so on.
The double for loop you have above works like so:
var arr = [[1,2], [3,4], [5,6]];
for (var i=0; i < arr.length; i++) {
// i = 0, then we loop below:
for (var j=0; j < arr[i].length; j++) {
//here we loop through the array which is in the main array
//in the first case, i = 0, j = 1, then we loop again, i = 0, j = 1
console.log(arr[i][j]);
//after we finish the stuff in the 'j' loop we go back to the 'i' loop
//and here i = 1, then we go down again, i, remains at 1, and j = 0, then j = 1
//....rinse and repeat,
}
}
In plain english:
We grab the first element in the main array, which is an array itself, we loop through that, and log at each index, this is terminated by our length condition in the second loop. We then move to to the next index of the main array, which is an array itself.... and so on, until we reach the end of the main array
To access and index in the main array, we need to use array[i] - that index holds an array - so to go INTO that array, we need to use array[i][j]
Hope that makes sense!
Videos
As others have already mentioned, Array.prototype.filter() might be the simplest approach (or Array.prototype.reduce() could also be used but would require more conditional logic). It would typically be slower than the nested for loops because it would be adding additional function calls, but for small data sets it typically wouldn't be noticeable. For example, I did a search on Google for "jsperf filter nested loop" and found this jsPerf test.
Using Array.prototype.filter() on A2, pass a callback function that returns true when the value at property value is included in A1 by checking A1.indexOf() for a value greater than -1.
const result = A2.filter(function(o) {
return A1.indexOf(o.value) > -1;
});
This can be simplified to a single line using an ES-6 arrow function and Array.prototype.includes() (Not supported by IE):
const result = A2.filter(o => A1.includes(o.value));
Try it in this snippet:
var A1 = ["1","2","3","4"];
var A2 = [
{label:"one", value:"1"},
{label:"two", value:"2"},
{label:"three", value:"3"},
{label:"four", value:"4"},
{label:"five", value:"5"},
{label:"six", value:"6"},
];
const result = A2.filter(o => A1.includes(o.value));
console.log('result', result);
If you wanted to use Underscore.js, _.filter() and _.includes() could be used to filter out any object in A2 without a value for the value property contained in A1. Expand the snippet below for a demonstration.
var A1 = ["1","2","3","4"];
var A2 = [
{label:"one", value:"1"},
{label:"two", value:"2"},
{label:"three", value:"3"},
{label:"four", value:"4"},
{label:"five", value:"5"},
{label:"six", value:"6"},
];
const result = _.filter(A2, function(o) { return _.includes(A1, o.value);});
console.log('result', result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
There is an Underscore helper _.pluck() but that is used to collect a value from each item in a collection at a given property (similar to Array.prototype.map().
Lodash also has the same helpers: _.filter() and _.includes().
var A1 = ["1","2","3","4"];
var A2 = [
{label:"one", value:"1"},
{label:"two", value:"2"},
{label:"three", value:"3"},
{label:"four", value:"4"},
{label:"five", value:"5"},
{label:"six", value:"6"},
];
const result = _.filter(A2, function(o) { return _.includes(A1, o.value);});
console.log('result', result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
Though some question whether libraries like lodash and underscore are really needed anymore. For a discussion on that, check out this article.
You can use some built in functions instead of the for loops.
var result = A2.filter(e => A1.includes(e.value));
I can't say if this is much faster, since those functions still loop through the arrays. You'll have to time this with some large input to test.
Be aware that Internet Explorer doesn't support .includes or arrow functions. IE friendly version:
var result = A2.filter(function(e) {return A1.indexOf(e.value) !== -1});
As I mentioned in the comments you can get the same result directly from MongoDB using a MapReduce query; However a cleaner JavaScript equivalent for your nested loops can be something like this:
var data = [
{"_id": "81724587125","name": "Object 1", "arrayOne":["1","2","3"]},
{"_id": "87687687687","name": "Object 2", "arrayOne":["4","5","6"]}
];
var result = data.reduce(function (previousValue, currentValue, currentIndex, array) {
return previousValue.arrayOne.concat( currentValue.arrayOne );
});
Nested loops are expensive when they both iterate over independent data in a "criss-cross" fashion, because they may represent a O(n^2) algorithm that can be made more efficient. You may think of such loops as describing the program "for every element in X, loop over every element in Y again".
This is not one of those cases. Your data are an array of objects, each containing further arrays of objects. A nested loop here is not only fine: it's completely expressive and appropriate.
The structure you're using should be more like this:
var data = [
{title:'Row Title 1', contents: [
{leftCol:'Some text for left column',rightCol:'Some text for right column'},
{leftCol:'Some text for left column',rightCol:'Some text for right column'},
{leftCol:'Some text for left column',rightCol:'Some text for right column'}
],
// ...
];
That way, each row is an object with a "title" attribute and a "contents" attribute. Your loop would then look like this:
for (var i=0, j=data.length; i < j; i++) {
if(data[i].title != null){
document.write('<b>'+data[i].title+'</b><br />');
}
for(var p=0, plen=data[i].contents.length; p < plen; p++){
document.write('<p style="background:#eee;">'+data[i].contents[p].leftCol+'</p>');
document.write('<p>'+data[i].contents[p].rightCol+'</p>');
}
}
If you want to make your code more robust follow these guidelines:
- It's always better to initialize for loops like so if you have a length:
for (var i = 0, l = length; l--; i++). The reason for this syntax is explained in fuller detail by Nicholas C. Zakas. - Always store variables accessed multiple times in a local variable. It speeds up execution (e.g.
idata = data[i];). - Avoid duck typing as far as possible (e.g.
data[i].title != null). Check for the type of the variable first. It's slower, but the code is easier to understand and maintain. Try thetypeOffunction at the bottom of the post (e.g.typeOf(idata) === "Object"). - It's usually always better to use
===instead of==and!==instead of!=because they don't perform type coercion which might lead to unexpected results. - Instead of creating multiple inline styles, create a single class
.greyBackground { background-color: #EEEEEE; }and set theclassNameof eachleftColparagraph togreyBackground. - Avoid using
document.write. It's slow, causes reflow of the document, and halts loading assets while the page is downloading. The best way to create dynamic content using JavaScript is to use thedocument.createDocumentFragmentmethod as I'll explain below. - It's always better to create nodes in JavaScript yourself. If you use a string in
document.writeorelement.innerHTMLthen the browser parses the string and converts it into the nodes anyway. Thus using that method is slower.
This is how I would have written your JavaScript:
var data = [
"Row Title 1",
{
"leftCol": "Some text for left column",
"rightCol": "Some text for right column"
}, {
"leftCol": "Some text for left column",
"rightCol": "Some text for right column"
}, {
"leftCol": "Some text for left column",
"rightCol": "Some text for right column"
},
"Row Title 2",
{
"leftCol": "Some text for left column",
"rightCol": "Some text for right column"
}, {
"leftCol": "Some text for left column",
"rightCol": "Some text for right column"
}, {
"leftCol": "Some text for left column",
"rightCol": "Some text for right column"
}
];
function typeOf(value) {
if (value === null) {
return "null";
} else if (typeof value === "undefined") {
return "undefined";
} else {
return Object.prototype.toString.call(value).slice(8, -1);
}
}
var element;
var fragment = document.createDocumentFragment();
var idata;
for (var i = 0, l = data.length; l--; i++) {
idata = data[i];
if (typeOf(idata) === "Object") {
element = document.createElement("p");
element.className = "greyBackground";
element.appendChild(document.createTextNode(idata.leftCol));
fragment.appendChild(element);
element = document.createElement("p");
element.appendChild(document.createTextNode(idata.rightCol));
fragment.appendChild(element);
} else {
element = document.createElement("b");
element.appendChild(document.createTextNode(idata));
fragment.appendChild(element);
element = document.createElement("br");
fragment.appendChild(element);
}
}
document.body.appendChild(fragment);
Test my page and yours. In all probability mine will execute faster. If you have any doubts feel free to ask me. Cheers! =)