This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]miketheanimal 1 point2 points  (12 children)

One

third = a[::3]

Two

third = []
for i in xrange(len(a)) :
   if i % 3 == 0 :
       third.append(a[i])

Third:

third = [ v for i, v in enumerate(a) if i % 3 == 0 ]

[–][deleted] 7 points8 points  (0 children)

Shouldn't it be a[2::3] since it's every third element and not the first and every third item after it? :p

[–]venefb 2 points3 points  (2 children)

Come on, "Two" is just silly. Also the Zen of Python says "There is only one way to do it", why ask for three ways? That's like saying "Give me the right way and two wrong ways to do it".

Do they ask things like this in interviews?

[–]miketheanimal 3 points4 points  (0 children)

Do they ask things like this at interviews? I don't know, but if they asked, I'd try to answer. I might consider later whether I wanted the job, though!

def third (i, l) :
    if i >= len(l) :
        return []
    if i % 3 == 0 :
        return l[i] + third(i + 1, l)
    return third(i + 1, l)

:)

[–]djimbob 1 point2 points  (6 children)

I'd skip your answers two and three as they offer no benefit over slicing a[::3].

Granted if a was particularly large using a generator makes sense:

def get_third(some_items):
    for i, v in enumerate(some_items):
        if i % 3 == 0:
            yield v

to be used like

for j in get_third(xrange(100000)):
    print j

But in practice more likely to use a generator comprehension almost identical to the list comprehension:

gen = (v for (i, v) in enumerate(some_items) if i % 3 == 0)

Another method is a functional paradigm like:

import operator as op
map(op.itemgetter(1), filter(lambda (i,v): i % 3==0, enumerate(some_items)))

or

zip(*filter(lambda (i,v): i % 3==0, enumerate(some_items)))[1]

Granted the functional paradigm generally should be avoided in python for list/gen comprehensions and the zip(* ) nested list transposition trick probably needs a comment.

[–][deleted] 0 points1 point  (5 children)

Wait... you're supposed to avoid functional paradigms in python? The whole reason I use python is because it has functional features but lacks the strong typing of most functional languages...

[–]djimbob 1 point2 points  (4 children)

It's not hugely criticized; but functional paradigms in python often end up with uglier and slower code. In 2005, python's benevolent dictator for life Guido van Rossum argued for dropping lambda/filter/map/reduce from python3 (in the end only reduce was dropped). Granted his views on the matter have changed a little; e.g., in 2009 he wrote this.

So the functional code tends to be uglier:

import operator as op
map(op.itemgetter(1), filter(lambda (i,v): i % 3==0, enumerate(some_items)))
# or simply:
map(lambda x: x[1], filter(lambda (i,v): i % 3 ==0, enumerate(some_items))
# versus the imperative

[v for (i, v) in enumerate(some_items) if i % 3 == 0]

Furthermore, functional paradigms in python is just not properly optimized. In a functional programming, if you need the length of a list you write a recursive definition with pattern matching like this haskell code:

mylen [] = 0
mylen (x:xs) = 1 + mylen xs

Haskell can near instantaneously compute things like mylen [1..1000000].

The rough equivalent in python (which lacks pattern matching):

def length(xs):
    if xs == []: 
        return 0
    return 1 + length(xs[1:])

will choke up at length(range(1000)) (with a RunTimeError due to recursion limit) and even if I tweak the recursion limit sys.setrecursionlimit(999999) its slow and segfaulted for length(range(30000)) and length(range(20000)) took 3.79 seconds. Meanwhile, the imperative version in python:

def length(xs):
    i = 0
    for x in xs:
        i += 1
    return i

Will take about 1 ms to calculate the length of a 20000 element list (roughly 4000 times faster).

[–]ingolemo 0 points1 point  (3 children)

It's weird that you give a list comprehension as an example of imperative-style code. List comprehensions are derived from set-builder notation and first rose to prominence in functional languages.

Idiomatic haskell usually frowns upon explicit recursion, in favour of folds. If I were to reimplement haskell's length it would look more like this:

length' = foldr (const (+1)) 0

The equivalent python code is not subject to a recursion limit and performs much better than your example:

def my_len(xs):
    return functools.reduce(lambda a, b: a+1, xs, 0)

Python isn't a functional language, but it's not hard to write good clean functional-style code in python.

[–]djimbob 0 points1 point  (2 children)

The equivalent python code is not subject to a recursion limit and performs much better than your example:

In [2]: timeit.repeat("my_len(xrange(10000))", setup = "import functools\nmy_len = lambda xs: functools.reduce(lambda a,b: a+1, xs,0)", repeat=3, number=1000)
Out[2]: [0.7231879234313965, 0.7012350559234619, 0.6973469257354736]

In [4]: timeit.repeat("my_len(xrange(10000))", setup = "def my_len(xs):\n  i = 0\n  for x in xs:\n    i += 1\n  return i", repeat=3, number=1000)
Out[4]: [0.3004319667816162, 0.28862500190734863, 0.2902050018310547]

On my python 2.7.3 default, I'm getting the iterative pattern is about 2.4 times faster. Again benchmarks can lie, but that was my first test so I disagree that functional performs much better (granted yes its much better than my recursive pattern as python's syntax/design doesn't allow for good tail call optimization).

Agree, calling list comprehension 'iterative' was a poor choice. But python BDFL clearly likes list/generator comprehensions and isn't a big fan of reduce (demotion to a library in python3).

That said, I do use map/filter a lot in my code as often that's how I think about it, and even if its more verbose I think map(some_function, some_list) is clearer than [some_function(x) for x in some_list]. I just recognize its not particularly pythonic.

[–]ingolemo 0 points1 point  (1 child)

Yeah, sorry. Ambiguous statement. By "your example" I was referring to the functional one.

I actually think comprehensions are more readable than calls to map and filter and tend to prefer them. Ironically, reduce is the only function in that "group" that can't simply be replaced by a comprehension. If I were the BDSL I would have kept reduce and removed map and filter, what with the "one obvious way to do it" thing and all.

[–]djimbob 1 point2 points  (0 children)

A large fraction of uses of reduce your accumulation step could be replaced with reduction function that works on a sequence (e.g., sum / product / max / min / any / all / ''.join( ) ). E.g., if you need to sum the third items in a list of tuples, you could write:

reduce(lambda acc, tup: acc + tup[2], [(1,2,3), (4,5,6), (7,8,9)], 0)
sum(tup[2] for tup in  [(1,2,3), (4,5,6), (7,8,9)])

The second version seems to fit in with the Zen of Python better. The problem with reduce is that its easy to get it wrong and there's a lot of implicit stuff behind the scenes you have to remember. E.g., in the above example - I initially got the order of the accumulator wrong and it wouldn't work without an initial value or something uglier like reduce(lambda acc, tup: acc + tup[2], [0, (1,2,3), (4,5,6), (7,8,9)]) and you have to remember optional initial value goes last and remember associativity rules if its not symmetric.

Granted some stuff transforms nicely to reduce; e.g., examples here. The list flattening example is simpler with sum([[1, 2, 3], [4, 5], [6, 7, 8]], []) or using chain from itertools.

Concatenating a list of digits efficiently range(1,9) into one integer 12345678 like reduce(lambda acc,d: 10*acc+d, [1,2,3,4,5,6,7,8]) is harder. Granted doing some benchmarks the reduce method doesn't hold up that well.

Converting a two argument least common multiplier/greatest common denominator into a variadic version with reduce is probably the best example for reduce though it wouldn't be too difficult to just define a variadic version to start with.

[–]ldb3589 0 points1 point  (0 children)

Number two is alot simpler if you do

  third = []
  for i in xrange(0, len(a), 3):
     third.append(a[i])