List comprehension in Haskell, Python and Ruby


Question

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)
1
14
4/27/2016 7:35:48 PM

Accepted Answer

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.

7
4/27/2016 7:30:45 PM

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 }

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon