Premature optimization is the root of all evil. It's also the root of this article.
I like programming puzzles. I also like to go fast. We're goin...
For further actions, you may consider blocking this person and/or reporting abuse
For the fibonacci puzzle, here is a solution that runs in a constant time using little math π
This solution is faster for large N, slower for small N.
jsben.ch/mOl4l
Using precomputed values for constants and using matrix expo for calculating ab (which is log(n))
Thanks for sharing this cool solution! I actually found this was slower on LeetCode than the postβs iterative solution. Crazy right? They must use low values of
N
in the test cases. I wonder if itβs quicker in other languages π€.Hmm, I've run some benchmark tests (I ran them online on Chrome so I don't know how reliable they are) and I got two different results on two different sites. On the first site (jsben.ch/) it seems that for smaller numbers, the iterative way is faster, the real power of the pure math function is when the N >= 200. While on the other site (perf.zone/), the math function is way faster even for smaller numbers. I got this results on the perf.zone/quick:
But anyway I wonder what time complexity does Javascript Math functions have π€.
The variance is very interesting. I wonder if Chrome throttles certain calculations.
I think it does.
Came here to say this! Did you also learn this from a discrete mathematics class? π
I did, long time ago and it was forgotten until I saw this function somewhere (might be on dev.to) and that stuck in my brain, so every time I see a fibonacci I get a flash of this solution.π
Nice article. Could you please elaborate more on this sentence: "...Looking up a Set in JavaScript is constant time, O(1)...".
As far as I know if you implement
Set
usinghash-table
you could reduce most of the operations to theO(1)
in an average-case butO(n)
in the worst-case. Moreover in ECMAScript2015 specification for theSet.prototype.has
it says:So I'm not sure that we could say that
Set.prototype.has
is an O(1) time complexity.Thanks π. Yes, in the worst case the look-up time can be
O(N)
. I was using Big O notation, where a Set look-up can be reduced down toO(1)
. I assumed that Chromium would be using an optimal data structure even though the spec only mandatessublinear access time
.I know I'm late to the party, but thanks for this reply, as you provide some links that make me understand why it "should be around O(1)" ^
If you reach this reply, how do you know/learn how to use the Set instead of doing a nested loop ?
Edit : what if in the first exemple; instead of the nested tab, we use jewels.indexOf(S[i]) !== -1 ? Exactly like using jewels.has(S[i]) ?
(Last time I checked)
indexOf
uses linear search to check for an element. So it's like doing a for loop and checking every element until it's found. It doesn't benefit from the fast look-up time of a hashtable.I learned the benefits and use cases of different data-structures by reading a book called Grokking Algorithms and watching Introduction to Algorithms on the MIT website (MIT Course Number 6.006). Textbooks can help as well as programming puzzle websites like hackerrank and leetcode where you can test the performance of your code and read peoples' discussions of solutions. Looking up (and learning) what 'Big O' is helps too!
Possible idea with the jewels thing:
This makes heavy use of CPU caching, so should be quite fast for longer strings.
Could also use A and B types
This can be done entirely on the stack and registers, so avoids other possible slowdowns related to allocation. Only issue is that of shifting...
Another, even faster:
This is basically just the fast ASM.js compatible form I guess. Not too sure if it will work though...
Edit: leetcode.com/submissions/detail/23...
tl;dr not as fast
A similar thing works perfectly fine in rust, though.
leetcode.com/submissions/detail/23...
These are all so cool! Iβm going to have to pick through them later π
This is probably because all the optimisations you may do in JS land may be thrown to the bin once it touches native code. I Rust you have more control over the memory layout.
Great post!
For the second problem, i'm wondering if "caching" the char helps:
Rather than accessing
s[front]
for each test, especially when the char is not a vowel.Maybe the js engine already optimizes your code π€.
Sure there's an extra affectation which cost time, but
s[front]
has to be "computed" each time. It's a direct memory access, but first an addition is in order to add front index to the s array adress, right ?So what is faster:
Difference is probably marginal, but wondering how much the assembler code changes. And if the jit optimizes that.
Interesting proposal. I ran it ten times on LeetCode and didn't get a faster answer (not that that means anything!).
I ran both functions one million times on a 445-char lorem ipsum text in Chrome latest. It seems that caching the variable is marginally faster π
Fun article, thank-you!
I wrote a few solutions of my own before reading over yours just to do a comparison, and while I didn't hit 100% on any of them, they were fairly performant. However, something that was really interesting is just how inconsistent leetcode's solution runner is. My
reverseVowels
solution would swing between the 98th and 50th percentile with no changes to the code itself.Did you run your solutions multiple times and experience a similar variation, or were they consistently in the 100th percentile?
Yes, I experienced a lot of variation! Since the only way I could compare my answers to existing answers was the recorded 100th percentile, thatβs what I chose to measure my own.
Thanks for the illustration how to solve problems iteratively: from the basic solution to the most performant.
Before reading this post I'd solve the "jewels and stones" problem by creating a js object and populating it with "jewels" keys. But using a Set collection with
has
method is more elegant and straightforward solution, thanks πGlad you found it helpful! I always appreciate when articles build up to the optimal solution π
Sets are also very cool π
you used
myJewels
as the count of how many jewels... that is kind of fuzzy... when 10 other programmer read other programmers' code, when they see...myJewels
... is it an array? Is it a string, or it is a number. Sure, you can imply it from the context, but sometimes when the code expand from 10 lines to 20 and to 30, it becomes difficult to track down.I'd suggest using
count
orcountJewels
to exactly denote what it means. Otherwise Peter has amyJewels
and is an array, Michael has amyJewels
and it is a string...count
has very little chance of confusion."close-down-your-IDE-and-talk-a-walk gross"
Are you kidding? It was wonderful! Simple, readable, all cases covered wonderful! Loved your gross solution. Gross optimization for the masses!
Jokes aside, thanks for your solutions! You implemented some interesting new ways to approach some problems, which made me realize there are things I'm not yet aware of! In short: Thanks!
Thanks for the kind words, Yuri!
Hm... with LeetCode, this time it could be 96% faster, and then I tried again, it became 42% faster, and then 10 seconds later, I tried again, it was 98% faster...
using a ASCII map... what if the program is subject to 8-bit ISO-8859-1 characters... and then to unicode?
What about this solution for jewels problem?
function findTotal(J, S) {
return Array.from(S).filter(item => J.indexOf(item) !== -1).length;
}
That's a great solution π. However, as we see here,
indexOf
has a complexity ofO(N)
. This means that for every stone, we might have to check the whole jewels string rather than a data structure we've constructed.For example, say we are passed 100 stones and 1000 jewels. With
indexOf
, the rough number of operations isstones * jewels
, or100 * 1000
. What we really want isstones * Set#has
, or100 * 1
. Set#has cost.