Wednesday, November 3, 2010

List comprehension ruby vs python

Been exploring a bit of list comprehension in Python so today I thought I peeked over the fence to see a bit of what is happening at ruby's end..

I really like how they are doing their list comprehension over there, check this example out:

[1,2,3,4,5].select(&:even?).map(|x| x*3)

That is almost readable code even in English! I would read it as something like this.."From the list 1,2,3,4,5 select even numbers and map it to number times 3". Really nice!

This vs the clunkier Python code ...

[x*3 for x in [1,2,3,4,5] if x%2 == 0]

I mean it both works just that it seems more elegant in ruby. Would be nice if we had that even or odd function in Python. Yeah I know it's trivial to write ... but then following that logic wouldn't it also be trivial to include it in the standard library. Probably my way of emulating this example ain't exactly the best and there are other more succint ways of doing it, if so be great to drop me a line here, but for now hang on while I peek over the fence more :)

15 comments:

KageSenshi said...

This is how i read the python code

[x*3 for x in [1,2,3,4,5] if x%2 == 0]

"give me a list of x*3 for each x in [1,2,3,4,5] where x mod 2 is 0"

or

"give me a list of number*3 for each number in [1,2,3,4,5] where number is even"

Jack Diederich said...

[x*3 for x in range(2,6,2)]

The difference in python is that the left hand side tells you what you are going to get and the right hand side is the source. "I am making a list of multiples of three using a list of numbers..." The Ruby example starts with the source (the list of numbers) and ends with the sink (multiples of 3).

Pete said...

I may be biased, but my eyes favor the Python version. I think because of fewer random-ish symbols.

Anyways, I've been doing a bit of C# now, and think the LINQ statements present an interesting alternative. They are a SQL inspired version of the same thing.

(Forgive me if this isn't quite valid C#, noob)
From i in numbersVar Where i%2==0 Select i*3

I'm pretty sure I like this the best, but have only just begun looking into the language that offers it.

regebro said...

Python has map() as well. The difference to the Python version is that it's not needed. Telling someone to map times three onto list x is not more clearer than the python version.

The "odd" and "even" properties on integers makes this particular example clearer though, but that's an unusual test in real life.

Taking every second item sounds more useful:


[x*3 for x in [1,2,3,4,5][::2]]

Raja said...

I think this is a very subjective assessment. The python version seems more readable to me without the 'line noise' in the ruby version.

Paul Sargent said...

(Saw this through Planet Python)

1. I can't see how the ruby version is easier to read, but beauty is in the eye of the beholder.

2. The ruby version isn't really a list comprehension (LC). An LC is a language structure, whereas the ruby you've posted is applying functions to a list.

It's easy to see the difference when both written in the same language. I've used Haskell as that's where Python borrowed LCs from.

Prelude> [x*3 | x <- [1,2,3,4,5], even x]
[6,12]

Prelude> map (*3) (filter even [1,2,3,4,5])
[6,12]

Python put a few more keywords in the LC to make it read better, and Ruby uses a different calling style for functions. You can see how they compare though.

... and python has map and filter

>>> map(lambda x: x*3, filter(even, [1,2,3,4,5]))
[6, 12]

(I predefined the even function. This is in 2.7, in 3.x it returns an iterator. Wrapping it in list() gets you a list)

So really the only differences between Ruby and Python are:
1) The calling style of functions. f(x) vs x.f
2) The lamba style. lambda x vs |x|

Other than that you're comparing LCs to functional style.

stefan-schwarzburg said...

I don't like the even and odd thing. Apart from having English words, there is nothing special about them: why is there no specific function for mod 3 or mod 4 or mod 5 and so on?
In my whole life as a programmer, I never needed 'even', except in simple programming examples.

In clojure for example there is 'when', 'when-not', 'true?', and 'if-not' and I really don't like that.
It is good that python has no 'if-not' 'while-not' and so on. It makes a language clearer. If there is 'not' and 'while' you don't need a special 'while-not'.

Argumenten voor kernenergie said...
This comment has been removed by the author.
Jeremy said...

I'm surprised how many people claim they never need to check for even/oddness. It's a pretty common task in web development, for example.

As for LCs, one reason I prefer the Python way is that the expression can be read like a for loop:

newlist = []
for x in [1, 2, 3, 4, 5]:
if x % 2 ==0:
newlist.append(x)

The right-hand side of the LC ("for x in ...") translates perfectly into how you'd do the task in a regular loop.

Jeremy said...

Er, sorry about the formatting. No way to make that proper Python, I guess.

MoMo said...

the ruby code is incorrect

The map function passed a block {|x| x*3}

So it should be:
[1,2,3,4,5].select(&:even?).map{|x| x*3}

I guess this makes it more difficult to read.

Could also do it this way if u dont like symbols:

[1,2,3,4,5].select{|x| x if x%2 == 0}.map{|x| x*3

Peter Cooper said...

I think the issue is in the mental model rather than how nice one looks over the other.

The Ruby one is more logical in a step by step sense. You have your list, you select some items, you then multiply them. You can break it up into pieces. This is a big deal for many people's mental models.

The Python one is harder to "parse" unless you're used to the style but is more elegant in a mathematical and syntactical sense. It's a more complete singular identity.

I'm a Rubyist but I appreciate Python's list comprehensions (though Haskell's approach is even nicer). I'm not convinced they would work well in Ruby, though, since they don't fit into the Lego-like "blocks of code" model that Ruby is very consistent with. Python can enjoy more left-field syntax because it's a less consistent language in terms of syntax already.

CLM said...

I also find the python form easier to read. It may be my background in math that does it. It's very well defined and concise. {y | y=x^3 for x element of {1,2,3,4,5} and x mod 2 = 0}

paulC said...

I suppose I'm exposed as an old hacker for pointing out that for odd numbers x&1 is true?

[ x*3 for x in [1,2,3,4,5] if x&1 ]

illume said...

Hi,


#the numpy way
evens = numbers[(numbers%2) == 0]

Numpy can use index results to index into arrays. So the (numbers % 2) == 0 part returns an array of True, False... etc. The part inside [] is just an index after all :)

# How about the Using more than one line in python to make it readable way ;)

def is_even(b):
return b % 2 == 0

even_numbers = [x*3 for x in [1,2,3,4,5] if is_even(x)]

I think the python one is better for reading because if you know how to use for loops you can read list comprehensions.

I do like the method chaining methods way of ruby though. You can just keep adding to the filtering of results as you go. I wish the python API was designed to allow chaining.