-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Problem 1 [Algorithm]: Multiples of 3 and 5 #5
Comments
The very simple solution, just use the Scala collections API to create a Range, filter and sum. |
Hi guys to vs. until You can use either the keyword to or until when creating a Range object. The difference is, that to includes the last value in the range, whereas until leaves it out. Here are two examples: for(i <- 1 to 10) { for(i <- 1 until 10) { The second loop iterates 9 times, from 1 to 9, excluding the upper boundary value 10. |
Just added a version using Set. This is what I demonstrated as an alternative during the hacking session on Monday. Not claiming to be efficient. Just to show an alternative way to solve this problem |
This does not teach you anything about scala but should probably be really fast. Some explanation: let's decompose the sum of the suite of multiples of 3 below 1000: ```3 + 6 + 9 + 12 + ... + 999``` If we divide everything by 3, this can be rewritten as: ```3 * (1 + 2 + 3 + 4 + ... + 333)``` The sum of such an integer suite is simple to compute: ```sum(1..n) = n * n+1 / 2``` Now we can rewrite the suite of multiple of 3 below 1000 as: ```333 * (333 + 1) /2``` Simple no ? Let's call this value 'sum3' Now, we can do the same with multiples of 5. Let's call that result 'sum5'. If you add sum3 and sum5, you will not find the expected value because multiples of 3 and 5 (ie multiples of 15) are taken two times into account. So, let's compute the sum of multiples of 15 below 1000 and call that variable sum15. Our final result will be: sum3 + sum5 - sum15.
…eger extensions. This is a simple 'implicit class' showcase.
Added my math based solutions. |
So next I present you a tail recursive with accumulator solution. An often used technique in Functional Programming is to write it as a recursive function. In a tail-recursive function, the very last expression/statement that gets executed is the recursive call. After that the function just returns. Such a tail recursive function can and will be optimised by the compiler, effectively generating byte code of a while-loop. In Scala we add the @tailrec annotation as a warranty that the function is tail recursive. (Just like we add an @OverRide annotation in Java to indicate that a method overrides an inherited method on purpose.) The sum parameter accumulates the result built during the recursion and is returned at the very end. We call this an accumulator. |
I pushed a solution using foldLeft with pattern matching guards |
👍 for @tdeconin, my favourite so far, a lot going on here though:
and an overal complexity of O(n) |
Cheers @samdebacker ! One thing I was wondering about my solution: does the scala runtime materialise an actual sequence in memory of 1000 elements? In theory this is not needed, since we only iterate over the elements and the result is a number. |
my favourite remains the simple one from Sam (I had the same, in fact). (1 until 1000).filter(l => l % 3 == 0 || l % 5 == 0).sum |
It's without doubt the most efficient and comprehensive solution. And I think everybody (I hope) can agree with that. I see the other implementations are more exploratory of Scala features and functional concepts. I think it's a good idea to explore other implementations because others can always learn a little bit more of Scala. However, as mentioned before, we must not forget that the goal of this initiative is to teach/learn Scala from each other. Therefore, whenever someone comes with an alternative, less obvious, implementation, it's better go give a short explanation of the concepts. |
The Scala runtime will not hold everything in memory because you are using a Stream. Each iteration will produce one new item until you reach the stop condition. Items that are behind the Stream cursor are eligible for GC. However, it'll produce items that are not multiples of 3 or 5. But those items will be discarded. |
@rcavalcanti Thanks for clarifying. I wasn't sure about the behaviour of takeWhile. I tested it with 10 billion elements in the stream and memory consumption remained pretty much constant :) |
I'm not very sure of your point @rcavalcanti, in the Stream ScalaDoc I read: Towards performance the Stream seems absolutely worst (3ms) compared to the others in the µs range. But I liked the Stream because it feels 'reactive event-based elastic ... ' I had a Stream solution as well, a bit different, but I didn't posted it (yet) because of the memoization. The takeWhile and filter on a Stream return new Streams and hence you avoid 'materialising' the Stream and keep the nice readability of the very first solution posted here. On the other hand, a Range Integers.positiveIntegers.takeWhile(_ < 1000).filter(l => l % 3 == 0 || l % 5 == 0).sum |
@samdebacker I came across this blog post regarding Streams. It explains the memoization does not always take place: http://blog.dmitryleskov.com/programming/scala/stream-hygiene-i-avoiding-memory-leaks/ |
👍 for @tdeconin as well |
for @tdeconin https://github.com/tdeconin as well
|
If we list all the natural numbers below 10 that are multiples of 3 or 5 we get 3, 5, 6 and 9. The sum of these multiples is 23.
Find the sum of all the multiples of 3 or 5 below 1000.
The text was updated successfully, but these errors were encountered: