views:

192

answers:

3

I've started working on some Project Euler problems, and have solved number 4 with a simple brute force solution:

def mprods(a,b):
 c = range(a,b)
 f = []
 for d in c:
  for e in c:
   f.append(d*e)
 return f

max([z for z in mprods(100,1000) if str(z)==(''.join([str(z)[-i] for i in range(1,len(str(z))+1)]))])

After solving, I tried to make it as compact as possible, and came up with that horrible bottom line!

Not to leave something half-done, I am trying to condense the mprods function into a list comprehension. So far, I've come up with these attempts:

  • [d*e for d,e in (range(a,b), range(a,b))]
    Obviously completely on the wrong track. :-)
  • [d*e for x in [e for e in range(1,5)] for d in range(1,5)]
    This gives me [4, 8, 12, 16, 4, 8, 12, 16, 4, 8, 12, 16, 4, 8, 12, 16], where I expect [1, 2, 3, 4, 2, 4, 6, 8, 3, 6, 9, 12, 4, 8, 12, 16] or similar.

Any Pythonistas out there that can help? :)

+1  A: 
c = range(a, b)
print [d * e for d in c for e in c]
Ignacio Vazquez-Abrams
Thanks! I didn't know you could do that... :)
Lucas Jones
and then you can put it all together to get [d*e for d in range(a,b) for e in range(a,b). Also, I would recommend using xrange rather than range for speed.
Justin Peel
Just be careful of the order when iterating nested structures. The outer layer needs to be iterated first: `[x for y in N for x in y]` Otherwise the name for the inner structure won't exist when you go to iterate over it (`y` in this case).
Ignacio Vazquez-Abrams
Thanks for the tips. That's a couple more Python nuggets learned today!
Lucas Jones
@Justin: In Python 3.0, there is no xrange function, and range returns an iterator like xrange once did. To simulate range from python 2, you'd call list(range(x)). Lucas did not specify what version of python he was using.
Brian
@Brian Thanks, good to know.
Justin Peel
+2  A: 

I think you'll like this one-liner (formatted for readability):

max(z for z in (d*e
                for d in xrange(100, 1000)
                for e in xrange(100, 1000))
            if str(z) == str(z)[::-1])

Or slightly changed:

c = range(100, 1000)
max(z for z in (d*e for d in c for e in c) if str(z) == str(z)[::-1])

Wonder how many parens that would be in Lisp...

AndiDog
`::-1`? That is cool! :) Python is turning into NetHack - TDTTOE. Thanks.
Lucas Jones
+1  A: 
from itertools import product

def palindrome(i):
  return str(i) == str(i)[::-1]

x = xrange(900,1000)

max(a*b for (a,b) in (product(x,x)) if palindrome(a*b))
  • xrange(900,1000) is like range(900,1000) but instead of returning a list it returns an object that generates the numbers in the range on demand. For looping, this is slightly faster than range() and more memory efficient.

  • product(xrange(900,1000),xrange(900,1000)) gives the Cartesian product of the input iterables. It is equivalent to nested for-loops. For example, product(A, B) returns the same as: ((x,y) for x in A for y in B). The leftmost iterators are in the outermost for-loop, so the output tuples cycle in a manner similar to an odometer (with the rightmost element changing on every iteration).

    product('ab', range(3)) --> ('a',0) ('a',1) ('a',2) ('b',0) ('b',1) ('b',2) product((0,1), (0,1), (0,1)) --> (0,0,0) (0,0,1) (0,1,0) (0,1,1) (1,0,0) ...

  • str(i)[::-1] is list slicing shorthand to reverse a list.

  • Note how everything is wrapped in a generator expression, a high performance, memory efficient generalization of list comprehensions and generators.

  • Also note that the largest palindrome made from the product of two 2-digit numbers is made from the numbers 91 99, two numbers in the range(90,100). Extrapolating to 3-digit numbers you can use range(900,1000).

BioGeek
That is *extremely* cool... yet another new Python feature learned - generator expressions. Neat :)
Lucas Jones