Does this for loop iterate multiple times?
I have been discussing some code with colleagues:
for(const a of arr) {
if(a.thing)
continue;
// do a thing
}
A suggestion was to filter this and use a forEach
arr.filter(a => a.thing)
.forEach(a => /* do a thing */);
There was a discussion about iterating more than necessary. I've looked this up, and I can't find anything. I also tried to figure out how to view the optimized output, but I don't know how to do that either.
I would expect that the filter
and forEach
turn into code that is very much like the for of
with the continue
, but I don't know how to be sure.
How can I find out? The only thing I've tried so far is google.
javascript performance
|
show 2 more comments
I have been discussing some code with colleagues:
for(const a of arr) {
if(a.thing)
continue;
// do a thing
}
A suggestion was to filter this and use a forEach
arr.filter(a => a.thing)
.forEach(a => /* do a thing */);
There was a discussion about iterating more than necessary. I've looked this up, and I can't find anything. I also tried to figure out how to view the optimized output, but I don't know how to do that either.
I would expect that the filter
and forEach
turn into code that is very much like the for of
with the continue
, but I don't know how to be sure.
How can I find out? The only thing I've tried so far is google.
javascript performance
1
The second snippet definitely loops twice, while the first one would loop only once.
– Taplar
11 hours ago
2
btw, the first approach is faster, even if you take a singleforEach
.
– Nina Scholz
11 hours ago
1
Good question! Just as a side note I would usefor of
instead offor in
to iterate over an array.
– kev
11 hours ago
2
By the way, these two snippets are not functionally identical.filter
predicates are written in the positive sense, in other words,filter
will return entries which match, rather than discarding matches. So you should remove the!
.
– Brandon
9 hours ago
1
@Brandon Actually, the!
is required in this case, since the action is supposed to happen ifa.thing
is falsy (the code hitscontinue
whena.thing
is truthy in the first example). The code was correct before.
– Brian McCutchon
7 hours ago
|
show 2 more comments
I have been discussing some code with colleagues:
for(const a of arr) {
if(a.thing)
continue;
// do a thing
}
A suggestion was to filter this and use a forEach
arr.filter(a => a.thing)
.forEach(a => /* do a thing */);
There was a discussion about iterating more than necessary. I've looked this up, and I can't find anything. I also tried to figure out how to view the optimized output, but I don't know how to do that either.
I would expect that the filter
and forEach
turn into code that is very much like the for of
with the continue
, but I don't know how to be sure.
How can I find out? The only thing I've tried so far is google.
javascript performance
I have been discussing some code with colleagues:
for(const a of arr) {
if(a.thing)
continue;
// do a thing
}
A suggestion was to filter this and use a forEach
arr.filter(a => a.thing)
.forEach(a => /* do a thing */);
There was a discussion about iterating more than necessary. I've looked this up, and I can't find anything. I also tried to figure out how to view the optimized output, but I don't know how to do that either.
I would expect that the filter
and forEach
turn into code that is very much like the for of
with the continue
, but I don't know how to be sure.
How can I find out? The only thing I've tried so far is google.
javascript performance
javascript performance
edited 9 hours ago
asked 11 hours ago
loctrice
8951923
8951923
1
The second snippet definitely loops twice, while the first one would loop only once.
– Taplar
11 hours ago
2
btw, the first approach is faster, even if you take a singleforEach
.
– Nina Scholz
11 hours ago
1
Good question! Just as a side note I would usefor of
instead offor in
to iterate over an array.
– kev
11 hours ago
2
By the way, these two snippets are not functionally identical.filter
predicates are written in the positive sense, in other words,filter
will return entries which match, rather than discarding matches. So you should remove the!
.
– Brandon
9 hours ago
1
@Brandon Actually, the!
is required in this case, since the action is supposed to happen ifa.thing
is falsy (the code hitscontinue
whena.thing
is truthy in the first example). The code was correct before.
– Brian McCutchon
7 hours ago
|
show 2 more comments
1
The second snippet definitely loops twice, while the first one would loop only once.
– Taplar
11 hours ago
2
btw, the first approach is faster, even if you take a singleforEach
.
– Nina Scholz
11 hours ago
1
Good question! Just as a side note I would usefor of
instead offor in
to iterate over an array.
– kev
11 hours ago
2
By the way, these two snippets are not functionally identical.filter
predicates are written in the positive sense, in other words,filter
will return entries which match, rather than discarding matches. So you should remove the!
.
– Brandon
9 hours ago
1
@Brandon Actually, the!
is required in this case, since the action is supposed to happen ifa.thing
is falsy (the code hitscontinue
whena.thing
is truthy in the first example). The code was correct before.
– Brian McCutchon
7 hours ago
1
1
The second snippet definitely loops twice, while the first one would loop only once.
– Taplar
11 hours ago
The second snippet definitely loops twice, while the first one would loop only once.
– Taplar
11 hours ago
2
2
btw, the first approach is faster, even if you take a single
forEach
.– Nina Scholz
11 hours ago
btw, the first approach is faster, even if you take a single
forEach
.– Nina Scholz
11 hours ago
1
1
Good question! Just as a side note I would use
for of
instead of for in
to iterate over an array.– kev
11 hours ago
Good question! Just as a side note I would use
for of
instead of for in
to iterate over an array.– kev
11 hours ago
2
2
By the way, these two snippets are not functionally identical.
filter
predicates are written in the positive sense, in other words, filter
will return entries which match, rather than discarding matches. So you should remove the !
.– Brandon
9 hours ago
By the way, these two snippets are not functionally identical.
filter
predicates are written in the positive sense, in other words, filter
will return entries which match, rather than discarding matches. So you should remove the !
.– Brandon
9 hours ago
1
1
@Brandon Actually, the
!
is required in this case, since the action is supposed to happen if a.thing
is falsy (the code hits continue
when a.thing
is truthy in the first example). The code was correct before.– Brian McCutchon
7 hours ago
@Brandon Actually, the
!
is required in this case, since the action is supposed to happen if a.thing
is falsy (the code hits continue
when a.thing
is truthy in the first example). The code was correct before.– Brian McCutchon
7 hours ago
|
show 2 more comments
3 Answers
3
active
oldest
votes
Your first example (the for in loop) is O(n), which will execute n times (n being the size of the array).
Your second example (the filter forEach) is O(n+m), which will execute n times in the filter (n being the size of the array), and then m times (m being the size of the resulting array after the filter takes place).
As such, the first example is faster. However, in this type of example without an exceedingly large sample set the difference is probably measured in microseconds or nanoseconds.
With regards to compilation optimization, that is essentially all memory access optimization. The major interpreters and engines will all analyze issues in code relating to function, variable, and property access such as how often and what the shape of the access graph looks like; and then, with all of that information, optimize their hidden structure to be more efficient for access. Essentially no optimization so far as loop replacement or process analysis is done on the code as it for the most part is optimized while it is running (if a specific part of code does start taking an excessively long time, it may have its code optimized).
When first executing the JavaScript code, V8 leverages full-codegen which directly translates the parsed JavaScript into machine code without any transformation. This allows it to start executing machine code very fast. Note that V8 does not use intermediate bytecode representation this way removing the need for an interpreter.
When your code has run for some time, the profiler thread has gathered enough data to tell which method should be optimized.
Next, Crankshaft optimizations begin in another thread. It translates the JavaScript abstract syntax tree to a high-level static single-assignment (SSA) representation called Hydrogen and tries to optimize that Hydrogen graph. Most optimizations are done at this level.
-https://blog.sessionstack.com/how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code-ac089e62b12e
*While continue
may cause the execution to go to the next iteration, it still counts as an iteration of the loop.
1
Is there nolazy
filtering stream construct in JS?
– Alexander
9 hours ago
@Alexander No, at least nothing built-in, and the engines don't do stream fusion optimization (yet). Though there are libraries which provide lazy collections, such as Lazy.js.
– interphx
8 hours ago
2
Your asymptotic analysis is incorrect. Filtering cannot increase the size of the array, hence m <= n and O(n+m) = O(n + (x)n) = O((1+x)n) = O(n) where 0 >= x >= 1.
– ApproachingDarknessFish
2 hours ago
add a comment |
An easy way to see how many times each part of that statement is called would be to add log statements like so and run it in the Chrome console
var arr = [1,2,3,4];
arr.filter(a => {console.log("hit1") ;return a%2 != 0;})
.forEach(a => {console.log("hit2")});
"Hit1" should print to the console 4 times regardless in this case. If it were to iterate too many times, we'd see "hit2" output 4 times, but after running this code it only outputs twice. So your assumption is partially correct, that the second time it iterates, it doesn't iterate over the whole set. However it does iterate over the whole set once in the .filter
and then iterates again over the part of the set that matches the condition again in the .filter
Another good place to look is in the MDN developer docs here especially in the "Polyfill" section which outlines the exact equivalent algorithm and you can see that .filter()
here returns the variable res
, which is what .forEach
would be performed upon.
So while it overall iterates over the set twice, in the .forEach
section it only iterates over the part of the set that matches the .filter
condition.
1
of course it iterates twice, if the first filtering returns some elements. but it's slower than thefor
loop. the only advantage of the second is to use callbacks with functional patterns.
– Nina Scholz
11 hours ago
Right, definitely slower. Just wanted to illustrate to them how they can break down the problem in the future if they want to analyze something like this real quick.
– nkorai
11 hours ago
1
I get your point, but I wasn't so much asking how the language itself worked. I think adding the console.log (and making it an actual function body) would make it harder to optimize, and create the same amount of operation even after optimization.
– loctrice
10 hours ago
add a comment |
The right answer is "it really doesn't matter". Some previously posted answer states that the second approach is O(n+m), but I beg to differ. The same exact "m" operations will also run in the first approach. In the worst case, even if you consider the second batch of operations as "m" (which doesn't really make much sense - we're talking about the same n elements given as input - that's not how complexity analysis work), in the worst case m==n and the complexity will be O(2n), which is just O(n) in the end anyway.
To directly answer your question, yes, the second approach will iterate over the collection twice while the first one will do it only once. But that probably won't make any difference to you. In cases like these, you probably want to improve readability over efficiency. How many items does your collection have? 10? 100? It's better to write code that will be easier to maintain over time than to strive for maximum efficiency all the time - because most of the time it just doesn't make any difference.
Moreover, iterating more than once doesn't mean your code runs slower. It's all about what's inside each loop. For instance:
for (const item of arr) {
// do A
// do B
}
Is virtually the same as:
for (const item of arr) {
// do A
}
for (const item of arr) {
// do B
}
The for loop itself doesn't add any significant overhead to the CPU. Although you would probably want to write a single loop anyway, if your code readability is improved when you do two loops, go ahead and do it.
If you really need to be efficient, you don't want to iterate through the whole collection, not even once. You want some smarter way to do it: either divide and conquer (O(log n)) or use hash maps (O(1)). A hash map a day keeps the inefficiency away :-)
Now, back to your example, if I find myself iterating over and over and doing the same operation every time, I'd just run the filtering operation only once, at the beginning:
const things = ;
const notThings = ;
for (const item of arr) {
item.thing ? things.push(item) : notThings.push(item);
}
And then you can freely iterate over notThings
, knowing that unwanted items were already filtered out. Makes sense?
It's not multiple times for the same operation. Your example is just manually implementing a filter method. I don't see how that is different than just calling filter.
– loctrice
9 hours ago
You could just go ahead and call filter() (and I would probably do that myself too). My point is that if you're doing the check over and over for the same collection, you should probably filter it out from the start.
– Lucio Paiva
9 hours ago
1
If we follow your assumption, then I would agree with you.
– loctrice
9 hours ago
On the other hand, if your code does that O(n) only once during the whole program execution, then go ahead and do what you're doing already - that's perfectly fine and it doesn't matter if you use filter()+forEach() orfor of
.
– Lucio Paiva
9 hours ago
Not sure if I made myself clear enough, so I edited my answer and added the part "Moreover, iterating more than once...". Please check it out and see if it makes sense to you.
– Lucio Paiva
9 hours ago
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53990895%2fdoes-this-for-loop-iterate-multiple-times%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
Your first example (the for in loop) is O(n), which will execute n times (n being the size of the array).
Your second example (the filter forEach) is O(n+m), which will execute n times in the filter (n being the size of the array), and then m times (m being the size of the resulting array after the filter takes place).
As such, the first example is faster. However, in this type of example without an exceedingly large sample set the difference is probably measured in microseconds or nanoseconds.
With regards to compilation optimization, that is essentially all memory access optimization. The major interpreters and engines will all analyze issues in code relating to function, variable, and property access such as how often and what the shape of the access graph looks like; and then, with all of that information, optimize their hidden structure to be more efficient for access. Essentially no optimization so far as loop replacement or process analysis is done on the code as it for the most part is optimized while it is running (if a specific part of code does start taking an excessively long time, it may have its code optimized).
When first executing the JavaScript code, V8 leverages full-codegen which directly translates the parsed JavaScript into machine code without any transformation. This allows it to start executing machine code very fast. Note that V8 does not use intermediate bytecode representation this way removing the need for an interpreter.
When your code has run for some time, the profiler thread has gathered enough data to tell which method should be optimized.
Next, Crankshaft optimizations begin in another thread. It translates the JavaScript abstract syntax tree to a high-level static single-assignment (SSA) representation called Hydrogen and tries to optimize that Hydrogen graph. Most optimizations are done at this level.
-https://blog.sessionstack.com/how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code-ac089e62b12e
*While continue
may cause the execution to go to the next iteration, it still counts as an iteration of the loop.
1
Is there nolazy
filtering stream construct in JS?
– Alexander
9 hours ago
@Alexander No, at least nothing built-in, and the engines don't do stream fusion optimization (yet). Though there are libraries which provide lazy collections, such as Lazy.js.
– interphx
8 hours ago
2
Your asymptotic analysis is incorrect. Filtering cannot increase the size of the array, hence m <= n and O(n+m) = O(n + (x)n) = O((1+x)n) = O(n) where 0 >= x >= 1.
– ApproachingDarknessFish
2 hours ago
add a comment |
Your first example (the for in loop) is O(n), which will execute n times (n being the size of the array).
Your second example (the filter forEach) is O(n+m), which will execute n times in the filter (n being the size of the array), and then m times (m being the size of the resulting array after the filter takes place).
As such, the first example is faster. However, in this type of example without an exceedingly large sample set the difference is probably measured in microseconds or nanoseconds.
With regards to compilation optimization, that is essentially all memory access optimization. The major interpreters and engines will all analyze issues in code relating to function, variable, and property access such as how often and what the shape of the access graph looks like; and then, with all of that information, optimize their hidden structure to be more efficient for access. Essentially no optimization so far as loop replacement or process analysis is done on the code as it for the most part is optimized while it is running (if a specific part of code does start taking an excessively long time, it may have its code optimized).
When first executing the JavaScript code, V8 leverages full-codegen which directly translates the parsed JavaScript into machine code without any transformation. This allows it to start executing machine code very fast. Note that V8 does not use intermediate bytecode representation this way removing the need for an interpreter.
When your code has run for some time, the profiler thread has gathered enough data to tell which method should be optimized.
Next, Crankshaft optimizations begin in another thread. It translates the JavaScript abstract syntax tree to a high-level static single-assignment (SSA) representation called Hydrogen and tries to optimize that Hydrogen graph. Most optimizations are done at this level.
-https://blog.sessionstack.com/how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code-ac089e62b12e
*While continue
may cause the execution to go to the next iteration, it still counts as an iteration of the loop.
1
Is there nolazy
filtering stream construct in JS?
– Alexander
9 hours ago
@Alexander No, at least nothing built-in, and the engines don't do stream fusion optimization (yet). Though there are libraries which provide lazy collections, such as Lazy.js.
– interphx
8 hours ago
2
Your asymptotic analysis is incorrect. Filtering cannot increase the size of the array, hence m <= n and O(n+m) = O(n + (x)n) = O((1+x)n) = O(n) where 0 >= x >= 1.
– ApproachingDarknessFish
2 hours ago
add a comment |
Your first example (the for in loop) is O(n), which will execute n times (n being the size of the array).
Your second example (the filter forEach) is O(n+m), which will execute n times in the filter (n being the size of the array), and then m times (m being the size of the resulting array after the filter takes place).
As such, the first example is faster. However, in this type of example without an exceedingly large sample set the difference is probably measured in microseconds or nanoseconds.
With regards to compilation optimization, that is essentially all memory access optimization. The major interpreters and engines will all analyze issues in code relating to function, variable, and property access such as how often and what the shape of the access graph looks like; and then, with all of that information, optimize their hidden structure to be more efficient for access. Essentially no optimization so far as loop replacement or process analysis is done on the code as it for the most part is optimized while it is running (if a specific part of code does start taking an excessively long time, it may have its code optimized).
When first executing the JavaScript code, V8 leverages full-codegen which directly translates the parsed JavaScript into machine code without any transformation. This allows it to start executing machine code very fast. Note that V8 does not use intermediate bytecode representation this way removing the need for an interpreter.
When your code has run for some time, the profiler thread has gathered enough data to tell which method should be optimized.
Next, Crankshaft optimizations begin in another thread. It translates the JavaScript abstract syntax tree to a high-level static single-assignment (SSA) representation called Hydrogen and tries to optimize that Hydrogen graph. Most optimizations are done at this level.
-https://blog.sessionstack.com/how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code-ac089e62b12e
*While continue
may cause the execution to go to the next iteration, it still counts as an iteration of the loop.
Your first example (the for in loop) is O(n), which will execute n times (n being the size of the array).
Your second example (the filter forEach) is O(n+m), which will execute n times in the filter (n being the size of the array), and then m times (m being the size of the resulting array after the filter takes place).
As such, the first example is faster. However, in this type of example without an exceedingly large sample set the difference is probably measured in microseconds or nanoseconds.
With regards to compilation optimization, that is essentially all memory access optimization. The major interpreters and engines will all analyze issues in code relating to function, variable, and property access such as how often and what the shape of the access graph looks like; and then, with all of that information, optimize their hidden structure to be more efficient for access. Essentially no optimization so far as loop replacement or process analysis is done on the code as it for the most part is optimized while it is running (if a specific part of code does start taking an excessively long time, it may have its code optimized).
When first executing the JavaScript code, V8 leverages full-codegen which directly translates the parsed JavaScript into machine code without any transformation. This allows it to start executing machine code very fast. Note that V8 does not use intermediate bytecode representation this way removing the need for an interpreter.
When your code has run for some time, the profiler thread has gathered enough data to tell which method should be optimized.
Next, Crankshaft optimizations begin in another thread. It translates the JavaScript abstract syntax tree to a high-level static single-assignment (SSA) representation called Hydrogen and tries to optimize that Hydrogen graph. Most optimizations are done at this level.
-https://blog.sessionstack.com/how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code-ac089e62b12e
*While continue
may cause the execution to go to the next iteration, it still counts as an iteration of the loop.
edited 11 hours ago
answered 11 hours ago
Travis J
63.8k28148223
63.8k28148223
1
Is there nolazy
filtering stream construct in JS?
– Alexander
9 hours ago
@Alexander No, at least nothing built-in, and the engines don't do stream fusion optimization (yet). Though there are libraries which provide lazy collections, such as Lazy.js.
– interphx
8 hours ago
2
Your asymptotic analysis is incorrect. Filtering cannot increase the size of the array, hence m <= n and O(n+m) = O(n + (x)n) = O((1+x)n) = O(n) where 0 >= x >= 1.
– ApproachingDarknessFish
2 hours ago
add a comment |
1
Is there nolazy
filtering stream construct in JS?
– Alexander
9 hours ago
@Alexander No, at least nothing built-in, and the engines don't do stream fusion optimization (yet). Though there are libraries which provide lazy collections, such as Lazy.js.
– interphx
8 hours ago
2
Your asymptotic analysis is incorrect. Filtering cannot increase the size of the array, hence m <= n and O(n+m) = O(n + (x)n) = O((1+x)n) = O(n) where 0 >= x >= 1.
– ApproachingDarknessFish
2 hours ago
1
1
Is there no
lazy
filtering stream construct in JS?– Alexander
9 hours ago
Is there no
lazy
filtering stream construct in JS?– Alexander
9 hours ago
@Alexander No, at least nothing built-in, and the engines don't do stream fusion optimization (yet). Though there are libraries which provide lazy collections, such as Lazy.js.
– interphx
8 hours ago
@Alexander No, at least nothing built-in, and the engines don't do stream fusion optimization (yet). Though there are libraries which provide lazy collections, such as Lazy.js.
– interphx
8 hours ago
2
2
Your asymptotic analysis is incorrect. Filtering cannot increase the size of the array, hence m <= n and O(n+m) = O(n + (x)n) = O((1+x)n) = O(n) where 0 >= x >= 1.
– ApproachingDarknessFish
2 hours ago
Your asymptotic analysis is incorrect. Filtering cannot increase the size of the array, hence m <= n and O(n+m) = O(n + (x)n) = O((1+x)n) = O(n) where 0 >= x >= 1.
– ApproachingDarknessFish
2 hours ago
add a comment |
An easy way to see how many times each part of that statement is called would be to add log statements like so and run it in the Chrome console
var arr = [1,2,3,4];
arr.filter(a => {console.log("hit1") ;return a%2 != 0;})
.forEach(a => {console.log("hit2")});
"Hit1" should print to the console 4 times regardless in this case. If it were to iterate too many times, we'd see "hit2" output 4 times, but after running this code it only outputs twice. So your assumption is partially correct, that the second time it iterates, it doesn't iterate over the whole set. However it does iterate over the whole set once in the .filter
and then iterates again over the part of the set that matches the condition again in the .filter
Another good place to look is in the MDN developer docs here especially in the "Polyfill" section which outlines the exact equivalent algorithm and you can see that .filter()
here returns the variable res
, which is what .forEach
would be performed upon.
So while it overall iterates over the set twice, in the .forEach
section it only iterates over the part of the set that matches the .filter
condition.
1
of course it iterates twice, if the first filtering returns some elements. but it's slower than thefor
loop. the only advantage of the second is to use callbacks with functional patterns.
– Nina Scholz
11 hours ago
Right, definitely slower. Just wanted to illustrate to them how they can break down the problem in the future if they want to analyze something like this real quick.
– nkorai
11 hours ago
1
I get your point, but I wasn't so much asking how the language itself worked. I think adding the console.log (and making it an actual function body) would make it harder to optimize, and create the same amount of operation even after optimization.
– loctrice
10 hours ago
add a comment |
An easy way to see how many times each part of that statement is called would be to add log statements like so and run it in the Chrome console
var arr = [1,2,3,4];
arr.filter(a => {console.log("hit1") ;return a%2 != 0;})
.forEach(a => {console.log("hit2")});
"Hit1" should print to the console 4 times regardless in this case. If it were to iterate too many times, we'd see "hit2" output 4 times, but after running this code it only outputs twice. So your assumption is partially correct, that the second time it iterates, it doesn't iterate over the whole set. However it does iterate over the whole set once in the .filter
and then iterates again over the part of the set that matches the condition again in the .filter
Another good place to look is in the MDN developer docs here especially in the "Polyfill" section which outlines the exact equivalent algorithm and you can see that .filter()
here returns the variable res
, which is what .forEach
would be performed upon.
So while it overall iterates over the set twice, in the .forEach
section it only iterates over the part of the set that matches the .filter
condition.
1
of course it iterates twice, if the first filtering returns some elements. but it's slower than thefor
loop. the only advantage of the second is to use callbacks with functional patterns.
– Nina Scholz
11 hours ago
Right, definitely slower. Just wanted to illustrate to them how they can break down the problem in the future if they want to analyze something like this real quick.
– nkorai
11 hours ago
1
I get your point, but I wasn't so much asking how the language itself worked. I think adding the console.log (and making it an actual function body) would make it harder to optimize, and create the same amount of operation even after optimization.
– loctrice
10 hours ago
add a comment |
An easy way to see how many times each part of that statement is called would be to add log statements like so and run it in the Chrome console
var arr = [1,2,3,4];
arr.filter(a => {console.log("hit1") ;return a%2 != 0;})
.forEach(a => {console.log("hit2")});
"Hit1" should print to the console 4 times regardless in this case. If it were to iterate too many times, we'd see "hit2" output 4 times, but after running this code it only outputs twice. So your assumption is partially correct, that the second time it iterates, it doesn't iterate over the whole set. However it does iterate over the whole set once in the .filter
and then iterates again over the part of the set that matches the condition again in the .filter
Another good place to look is in the MDN developer docs here especially in the "Polyfill" section which outlines the exact equivalent algorithm and you can see that .filter()
here returns the variable res
, which is what .forEach
would be performed upon.
So while it overall iterates over the set twice, in the .forEach
section it only iterates over the part of the set that matches the .filter
condition.
An easy way to see how many times each part of that statement is called would be to add log statements like so and run it in the Chrome console
var arr = [1,2,3,4];
arr.filter(a => {console.log("hit1") ;return a%2 != 0;})
.forEach(a => {console.log("hit2")});
"Hit1" should print to the console 4 times regardless in this case. If it were to iterate too many times, we'd see "hit2" output 4 times, but after running this code it only outputs twice. So your assumption is partially correct, that the second time it iterates, it doesn't iterate over the whole set. However it does iterate over the whole set once in the .filter
and then iterates again over the part of the set that matches the condition again in the .filter
Another good place to look is in the MDN developer docs here especially in the "Polyfill" section which outlines the exact equivalent algorithm and you can see that .filter()
here returns the variable res
, which is what .forEach
would be performed upon.
So while it overall iterates over the set twice, in the .forEach
section it only iterates over the part of the set that matches the .filter
condition.
answered 11 hours ago
nkorai
115110
115110
1
of course it iterates twice, if the first filtering returns some elements. but it's slower than thefor
loop. the only advantage of the second is to use callbacks with functional patterns.
– Nina Scholz
11 hours ago
Right, definitely slower. Just wanted to illustrate to them how they can break down the problem in the future if they want to analyze something like this real quick.
– nkorai
11 hours ago
1
I get your point, but I wasn't so much asking how the language itself worked. I think adding the console.log (and making it an actual function body) would make it harder to optimize, and create the same amount of operation even after optimization.
– loctrice
10 hours ago
add a comment |
1
of course it iterates twice, if the first filtering returns some elements. but it's slower than thefor
loop. the only advantage of the second is to use callbacks with functional patterns.
– Nina Scholz
11 hours ago
Right, definitely slower. Just wanted to illustrate to them how they can break down the problem in the future if they want to analyze something like this real quick.
– nkorai
11 hours ago
1
I get your point, but I wasn't so much asking how the language itself worked. I think adding the console.log (and making it an actual function body) would make it harder to optimize, and create the same amount of operation even after optimization.
– loctrice
10 hours ago
1
1
of course it iterates twice, if the first filtering returns some elements. but it's slower than the
for
loop. the only advantage of the second is to use callbacks with functional patterns.– Nina Scholz
11 hours ago
of course it iterates twice, if the first filtering returns some elements. but it's slower than the
for
loop. the only advantage of the second is to use callbacks with functional patterns.– Nina Scholz
11 hours ago
Right, definitely slower. Just wanted to illustrate to them how they can break down the problem in the future if they want to analyze something like this real quick.
– nkorai
11 hours ago
Right, definitely slower. Just wanted to illustrate to them how they can break down the problem in the future if they want to analyze something like this real quick.
– nkorai
11 hours ago
1
1
I get your point, but I wasn't so much asking how the language itself worked. I think adding the console.log (and making it an actual function body) would make it harder to optimize, and create the same amount of operation even after optimization.
– loctrice
10 hours ago
I get your point, but I wasn't so much asking how the language itself worked. I think adding the console.log (and making it an actual function body) would make it harder to optimize, and create the same amount of operation even after optimization.
– loctrice
10 hours ago
add a comment |
The right answer is "it really doesn't matter". Some previously posted answer states that the second approach is O(n+m), but I beg to differ. The same exact "m" operations will also run in the first approach. In the worst case, even if you consider the second batch of operations as "m" (which doesn't really make much sense - we're talking about the same n elements given as input - that's not how complexity analysis work), in the worst case m==n and the complexity will be O(2n), which is just O(n) in the end anyway.
To directly answer your question, yes, the second approach will iterate over the collection twice while the first one will do it only once. But that probably won't make any difference to you. In cases like these, you probably want to improve readability over efficiency. How many items does your collection have? 10? 100? It's better to write code that will be easier to maintain over time than to strive for maximum efficiency all the time - because most of the time it just doesn't make any difference.
Moreover, iterating more than once doesn't mean your code runs slower. It's all about what's inside each loop. For instance:
for (const item of arr) {
// do A
// do B
}
Is virtually the same as:
for (const item of arr) {
// do A
}
for (const item of arr) {
// do B
}
The for loop itself doesn't add any significant overhead to the CPU. Although you would probably want to write a single loop anyway, if your code readability is improved when you do two loops, go ahead and do it.
If you really need to be efficient, you don't want to iterate through the whole collection, not even once. You want some smarter way to do it: either divide and conquer (O(log n)) or use hash maps (O(1)). A hash map a day keeps the inefficiency away :-)
Now, back to your example, if I find myself iterating over and over and doing the same operation every time, I'd just run the filtering operation only once, at the beginning:
const things = ;
const notThings = ;
for (const item of arr) {
item.thing ? things.push(item) : notThings.push(item);
}
And then you can freely iterate over notThings
, knowing that unwanted items were already filtered out. Makes sense?
It's not multiple times for the same operation. Your example is just manually implementing a filter method. I don't see how that is different than just calling filter.
– loctrice
9 hours ago
You could just go ahead and call filter() (and I would probably do that myself too). My point is that if you're doing the check over and over for the same collection, you should probably filter it out from the start.
– Lucio Paiva
9 hours ago
1
If we follow your assumption, then I would agree with you.
– loctrice
9 hours ago
On the other hand, if your code does that O(n) only once during the whole program execution, then go ahead and do what you're doing already - that's perfectly fine and it doesn't matter if you use filter()+forEach() orfor of
.
– Lucio Paiva
9 hours ago
Not sure if I made myself clear enough, so I edited my answer and added the part "Moreover, iterating more than once...". Please check it out and see if it makes sense to you.
– Lucio Paiva
9 hours ago
add a comment |
The right answer is "it really doesn't matter". Some previously posted answer states that the second approach is O(n+m), but I beg to differ. The same exact "m" operations will also run in the first approach. In the worst case, even if you consider the second batch of operations as "m" (which doesn't really make much sense - we're talking about the same n elements given as input - that's not how complexity analysis work), in the worst case m==n and the complexity will be O(2n), which is just O(n) in the end anyway.
To directly answer your question, yes, the second approach will iterate over the collection twice while the first one will do it only once. But that probably won't make any difference to you. In cases like these, you probably want to improve readability over efficiency. How many items does your collection have? 10? 100? It's better to write code that will be easier to maintain over time than to strive for maximum efficiency all the time - because most of the time it just doesn't make any difference.
Moreover, iterating more than once doesn't mean your code runs slower. It's all about what's inside each loop. For instance:
for (const item of arr) {
// do A
// do B
}
Is virtually the same as:
for (const item of arr) {
// do A
}
for (const item of arr) {
// do B
}
The for loop itself doesn't add any significant overhead to the CPU. Although you would probably want to write a single loop anyway, if your code readability is improved when you do two loops, go ahead and do it.
If you really need to be efficient, you don't want to iterate through the whole collection, not even once. You want some smarter way to do it: either divide and conquer (O(log n)) or use hash maps (O(1)). A hash map a day keeps the inefficiency away :-)
Now, back to your example, if I find myself iterating over and over and doing the same operation every time, I'd just run the filtering operation only once, at the beginning:
const things = ;
const notThings = ;
for (const item of arr) {
item.thing ? things.push(item) : notThings.push(item);
}
And then you can freely iterate over notThings
, knowing that unwanted items were already filtered out. Makes sense?
It's not multiple times for the same operation. Your example is just manually implementing a filter method. I don't see how that is different than just calling filter.
– loctrice
9 hours ago
You could just go ahead and call filter() (and I would probably do that myself too). My point is that if you're doing the check over and over for the same collection, you should probably filter it out from the start.
– Lucio Paiva
9 hours ago
1
If we follow your assumption, then I would agree with you.
– loctrice
9 hours ago
On the other hand, if your code does that O(n) only once during the whole program execution, then go ahead and do what you're doing already - that's perfectly fine and it doesn't matter if you use filter()+forEach() orfor of
.
– Lucio Paiva
9 hours ago
Not sure if I made myself clear enough, so I edited my answer and added the part "Moreover, iterating more than once...". Please check it out and see if it makes sense to you.
– Lucio Paiva
9 hours ago
add a comment |
The right answer is "it really doesn't matter". Some previously posted answer states that the second approach is O(n+m), but I beg to differ. The same exact "m" operations will also run in the first approach. In the worst case, even if you consider the second batch of operations as "m" (which doesn't really make much sense - we're talking about the same n elements given as input - that's not how complexity analysis work), in the worst case m==n and the complexity will be O(2n), which is just O(n) in the end anyway.
To directly answer your question, yes, the second approach will iterate over the collection twice while the first one will do it only once. But that probably won't make any difference to you. In cases like these, you probably want to improve readability over efficiency. How many items does your collection have? 10? 100? It's better to write code that will be easier to maintain over time than to strive for maximum efficiency all the time - because most of the time it just doesn't make any difference.
Moreover, iterating more than once doesn't mean your code runs slower. It's all about what's inside each loop. For instance:
for (const item of arr) {
// do A
// do B
}
Is virtually the same as:
for (const item of arr) {
// do A
}
for (const item of arr) {
// do B
}
The for loop itself doesn't add any significant overhead to the CPU. Although you would probably want to write a single loop anyway, if your code readability is improved when you do two loops, go ahead and do it.
If you really need to be efficient, you don't want to iterate through the whole collection, not even once. You want some smarter way to do it: either divide and conquer (O(log n)) or use hash maps (O(1)). A hash map a day keeps the inefficiency away :-)
Now, back to your example, if I find myself iterating over and over and doing the same operation every time, I'd just run the filtering operation only once, at the beginning:
const things = ;
const notThings = ;
for (const item of arr) {
item.thing ? things.push(item) : notThings.push(item);
}
And then you can freely iterate over notThings
, knowing that unwanted items were already filtered out. Makes sense?
The right answer is "it really doesn't matter". Some previously posted answer states that the second approach is O(n+m), but I beg to differ. The same exact "m" operations will also run in the first approach. In the worst case, even if you consider the second batch of operations as "m" (which doesn't really make much sense - we're talking about the same n elements given as input - that's not how complexity analysis work), in the worst case m==n and the complexity will be O(2n), which is just O(n) in the end anyway.
To directly answer your question, yes, the second approach will iterate over the collection twice while the first one will do it only once. But that probably won't make any difference to you. In cases like these, you probably want to improve readability over efficiency. How many items does your collection have? 10? 100? It's better to write code that will be easier to maintain over time than to strive for maximum efficiency all the time - because most of the time it just doesn't make any difference.
Moreover, iterating more than once doesn't mean your code runs slower. It's all about what's inside each loop. For instance:
for (const item of arr) {
// do A
// do B
}
Is virtually the same as:
for (const item of arr) {
// do A
}
for (const item of arr) {
// do B
}
The for loop itself doesn't add any significant overhead to the CPU. Although you would probably want to write a single loop anyway, if your code readability is improved when you do two loops, go ahead and do it.
If you really need to be efficient, you don't want to iterate through the whole collection, not even once. You want some smarter way to do it: either divide and conquer (O(log n)) or use hash maps (O(1)). A hash map a day keeps the inefficiency away :-)
Now, back to your example, if I find myself iterating over and over and doing the same operation every time, I'd just run the filtering operation only once, at the beginning:
const things = ;
const notThings = ;
for (const item of arr) {
item.thing ? things.push(item) : notThings.push(item);
}
And then you can freely iterate over notThings
, knowing that unwanted items were already filtered out. Makes sense?
edited 9 hours ago
answered 10 hours ago
Lucio Paiva
6,23023553
6,23023553
It's not multiple times for the same operation. Your example is just manually implementing a filter method. I don't see how that is different than just calling filter.
– loctrice
9 hours ago
You could just go ahead and call filter() (and I would probably do that myself too). My point is that if you're doing the check over and over for the same collection, you should probably filter it out from the start.
– Lucio Paiva
9 hours ago
1
If we follow your assumption, then I would agree with you.
– loctrice
9 hours ago
On the other hand, if your code does that O(n) only once during the whole program execution, then go ahead and do what you're doing already - that's perfectly fine and it doesn't matter if you use filter()+forEach() orfor of
.
– Lucio Paiva
9 hours ago
Not sure if I made myself clear enough, so I edited my answer and added the part "Moreover, iterating more than once...". Please check it out and see if it makes sense to you.
– Lucio Paiva
9 hours ago
add a comment |
It's not multiple times for the same operation. Your example is just manually implementing a filter method. I don't see how that is different than just calling filter.
– loctrice
9 hours ago
You could just go ahead and call filter() (and I would probably do that myself too). My point is that if you're doing the check over and over for the same collection, you should probably filter it out from the start.
– Lucio Paiva
9 hours ago
1
If we follow your assumption, then I would agree with you.
– loctrice
9 hours ago
On the other hand, if your code does that O(n) only once during the whole program execution, then go ahead and do what you're doing already - that's perfectly fine and it doesn't matter if you use filter()+forEach() orfor of
.
– Lucio Paiva
9 hours ago
Not sure if I made myself clear enough, so I edited my answer and added the part "Moreover, iterating more than once...". Please check it out and see if it makes sense to you.
– Lucio Paiva
9 hours ago
It's not multiple times for the same operation. Your example is just manually implementing a filter method. I don't see how that is different than just calling filter.
– loctrice
9 hours ago
It's not multiple times for the same operation. Your example is just manually implementing a filter method. I don't see how that is different than just calling filter.
– loctrice
9 hours ago
You could just go ahead and call filter() (and I would probably do that myself too). My point is that if you're doing the check over and over for the same collection, you should probably filter it out from the start.
– Lucio Paiva
9 hours ago
You could just go ahead and call filter() (and I would probably do that myself too). My point is that if you're doing the check over and over for the same collection, you should probably filter it out from the start.
– Lucio Paiva
9 hours ago
1
1
If we follow your assumption, then I would agree with you.
– loctrice
9 hours ago
If we follow your assumption, then I would agree with you.
– loctrice
9 hours ago
On the other hand, if your code does that O(n) only once during the whole program execution, then go ahead and do what you're doing already - that's perfectly fine and it doesn't matter if you use filter()+forEach() or
for of
.– Lucio Paiva
9 hours ago
On the other hand, if your code does that O(n) only once during the whole program execution, then go ahead and do what you're doing already - that's perfectly fine and it doesn't matter if you use filter()+forEach() or
for of
.– Lucio Paiva
9 hours ago
Not sure if I made myself clear enough, so I edited my answer and added the part "Moreover, iterating more than once...". Please check it out and see if it makes sense to you.
– Lucio Paiva
9 hours ago
Not sure if I made myself clear enough, so I edited my answer and added the part "Moreover, iterating more than once...". Please check it out and see if it makes sense to you.
– Lucio Paiva
9 hours ago
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53990895%2fdoes-this-for-loop-iterate-multiple-times%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
The second snippet definitely loops twice, while the first one would loop only once.
– Taplar
11 hours ago
2
btw, the first approach is faster, even if you take a single
forEach
.– Nina Scholz
11 hours ago
1
Good question! Just as a side note I would use
for of
instead offor in
to iterate over an array.– kev
11 hours ago
2
By the way, these two snippets are not functionally identical.
filter
predicates are written in the positive sense, in other words,filter
will return entries which match, rather than discarding matches. So you should remove the!
.– Brandon
9 hours ago
1
@Brandon Actually, the
!
is required in this case, since the action is supposed to happen ifa.thing
is falsy (the code hitscontinue
whena.thing
is truthy in the first example). The code was correct before.– Brian McCutchon
7 hours ago