Reputation: 5759
I have started looking at the project Euler site as a way to learn Haskell, and improve my Python and Ruby. I think the Haskell and Python versions are ok, but I'm sure there must be a cleaner way for Ruby.
This is not about how can I make one language look like another one.
This is Problem 1:
Q: Add all the natural numbers below one thousand that are multiples of 3 or 5.
Haskell:
sum [ x | x <- [1..999], mod x 3 == 0 || mod x 5 == 0 ]
Python:
sum ( [ x for x in range(1,1000) if x % 3 == 0 or x % 5 == 0 ] )
Ruby:
(1..999) . map {|x| x if x % 3 == 0 || x % 5 == 0 } . compact . inject(:+)
They all give the same answer.
OK, so Python can become:
sum ( x for x in range(1,1000) if x % 3 == 0 or x % 5 == 0 )
it is now a generator (a good thing as we are not storing the list)
but even more fun is:
sum( set(range(0,1000,3)) | set(range(0,1000,5)) )
For some reason I was looking at this again and tried a summation approach which should be constant time. In Python 3:
def step_sum(mn,mx,step):
amax = mx - (mx - mn) % step
return (mn + amax) * ((1 + ((amax - mn) / step)) / 2)
step_sum(3,999,3) + step_sum(5,999,5) - step_sum(15,999,15)
Ruby can become:
(1..999) . select {|x| x % 3 == 0 || x % 5 == 0} . inject(:+)
or
(1..999) . select {|x| x % 3 == 0 or x % 5 == 0} . reduce(:+)
I am presuming as unlike map
, select
doesn't produce 'nul' and therefore there is no need to call compact
. nice.
Haskell can also be:
let ƒ n = sum [0,n..999] in ƒ 3 + ƒ 5 - ƒ 15
or to be clearer:
let ƒ n = sum [ 0 , n .. 999 ] in ƒ 3 + ƒ 5 - ƒ (lcm 3 5)
as a function that lets us provide the two numbers ourselves:
ƒ :: (Integral a) => a -> a -> a
ƒ x y = let ƒ n = sum [0,n..999] in ƒ x + ƒ y - ƒ (lcm x y)
Upvotes: 14
Views: 3710
Reputation: 54574
For Haskell I like
let s n = sum [0,n..999] in s 3 + s 5 - s 15
or
sum $ filter ((>1).(gcd 15)) [0..999]
For fun the Rube-Goldberg version:
import Data.Bits
sum $ zipWith (*) [1..999] $ zipWith (.|.) (cycle [0,0,1]) (cycle [0,0,0,0,1])
Okay, explanation time.
The first version defines a little function s that sums up all multiples of n up to 999. If we sum all multiples of 3 and all multiples of 5, we included all multiples of 15 twice (once in every list), hence we need to subtract them one time.
The second version uses the fact that 3 and 5 are primes. If a number contains one or both of the factors 3 and 5, the gcd of this number and 15 will be 3, 5 or 15, so in every case the gcd will be bigger than one. For other numbers without a common factor with 15 the gcd becomes 1. This is a nice trick to test both conditions in one step. But be careful, it won't work for arbitrary numbers, e.g. when we had 4 and 9, the test gdc x 36 > 1
won't work, as gcd 6 36 == 6
, but neither mod 6 4 == 0
nor mod 6 9 == 0
.
The third version is quite funny. cycle
repeats a list over and over. cycle [0,0,1]
codes the "divisibility pattern" for 3, and cycle [0,0,0,0,1]
does the same for 5. Then we "or" both lists together using zipWith
, which gives us [0,0,1,0,1,1,0,0,1,1,0,1...]
. Now we use zipWith
again to multiply this with the actual numbers, resulting in [0,0,3,0,5,6,0,0,9,10,0,12...]
. Then we just add it up.
Knowing different ways to do the same thing might be wasteful for other languages, but for Haskell it is essential. You need to spot patterns, pick up tricks and idioms, and play around a lot in order to gain the mental flexibility to use this language effectively. Challenges like the project Euler problems are a good opportunity to do so.
Upvotes: 8
Reputation: 15975
Not a list comprehension, I know, but to solve that I would use:
3*((999/3)**2+999/3)/2+5*((999/5)**2+999/5)/2-15*((999/15)**2+999/15)/2
Faster then any list comprehension one might come up with, and works in any language ;)
Only posting to show another way of looking at the same problem using http://en.wikipedia.org/wiki/Summation.
Upvotes: 2
Reputation: 118671
Try this for Ruby:
(1..999).select {|x| x % 3 == 0 or x % 5 == 0}.reduce(:+)
Or a little different approach:
(1..999).reduce(0) {|m, x| (x % 3 == 0 or x % 5 == 0) ? m+x : m }
Upvotes: 4
Reputation: 3634
Try something like this:
(1...1000).inject(0) do |sum, i|
if (i % 3 == 0) or (i % 5 == 0)
sum + i
else
sum
end
Upvotes: 1
Reputation: 37507
I think the following is a better Ruby one:
(1..999).select{|x| x % 3 == 0 || x % 5 == 0}.reduce(:+)
Upvotes: 1