I couldn't reply on Wildmoose's blog entry directly due to a 2000 character limit, so I decided to post here, until I can fix his blog

Reply to Wildmoose's euler 6 solution

Thnx for the credits :-)

Anyway, Before I start throwing alternate solutions, I want to teach you a small thing about lists and sequences (returned by generator functions).
I'm going to assume you're using Python 2.x (preferably 2.7) and not Python 3.x

In your code, you're using the range() function:

for i in range (1,101):

Some people might confuse this to be like a standard for-loop in other languages:

for (int i = 1; i < 101; i++)

But that is not the case here. To show you what it does, let's create our own variation of the range function:

def GetNumbersList(min, exclusiveMax):
    numbers = []
    i = min
    while i < exclusiveMax:
    	numbers.append(i)
    	i += 1
    return numbers

Like the range() function, this function takes the minimum value and the exclusive max value.

  • The first thing it does is create an empty List called "numbers".
  • It then loops, adding all the numbers to the list (min to exclusiveMax - 1).
  • Finally it returns the list of numbers.

The point here is that this list is created, in its entirety, in memory.
(This is fine for a list of integers of course, as they take up nearly no memory, but let's just assume for a minute they take up a lot of memory)

There's a way to avoid this, instead of writing a function that returns a list, you can write a generator function. (The Python shortcut for interator functions).

Consider the following example:

def GetNumbersGenerator(min, exclusiveMax):
    i = min
    while i < exclusiveMax:
    	yield i
    	i += 1

The first thing you'll notice (hopefully) is that there's no List involved and there is no "return" statement.
Instead there's a "yield" statement which indicates this function is a generator function.

The "yield" statement is a strange beast and I'm not going to try and explain it in full detail here,
as it's not important for what I'm trying to show, but for more information read this.

Basically this function will return a generator, which can be interated over and the numbers will be created when required.
(Internally a Sequence is used instead of a List, but ignore that for now)

To proof what I'm saying, use the functions:

numbersList = GetNumbersList(1, 4)
numbersGenerator = GetNumbersGenerator(1, 4)

print numbersList
print numbersGenerator

The output will look something like this:

[1, 2, 3]
<generator object GetNumbersGenerator at 0x00000000029D0750>

At this time, numbersList is a List stored in memory along with its content,
while numbersGenerator is a generator stored in memory and none of the numbers have been created yet. (They will be generated when required).

When execution the following code,

print sum(numbersList)
print sum(numbersGenerator)

The output will look like this:

6
6

The thing to note here is that numbersList is still a List of numbers in memory and numbersGenerator is still a generator in memory.
The generated numbers are not retained. So if you were to run the code again, they would have to be generated again.
If you plan on using the numbersGenerator more than once, perhaps a List would have been a better choice after all.

Assuming integer generation has a very large impact on memory, the following code might be a very bad idea:

x = sum(xrange(9999999))
y = max(xrange(9999999))

The numbers would have to be generated twice, a better solution would be:

numbers = xrange(9999999))
x = sum(numbers)
y = max(numbers)

While not entirely correct, you could say the range() function returns a List whereas the xrange() function is a generator function which returns a Sequence. Values in a List are stored in memory, while values in a Sequence are lazily-evaluated.

Keeping a List of integers in memory is of course not expensive at all, so why use a generator instead?
Short answer: generators are great when you know that not all of the elements in the sequence might be used.

The following is an example of such a scenario, although it should never be used in real life code, Python has much better built-in support for this.

import itertools

#Generates an endless sequence of even numbers
def evenNumbers():
    i = 0
    while(True):
        i += 2
        yield i

def take(count, iterator):
    i = 0
    while(i < count):
        i += 1
        yield iterator.next()

#Create an iterator over the evenNumbers generator function      
iterator = iter(evenNumbers())

results = take(10, iterator)

for i in results:
    print i

This will generate the following output:

2  
4 
6
8
10
12
14
16
18
20

If you really wanted to do this in Python you'd use a list comprehension or a generator expression:

resultsList = [2 * n for n in range(2,11)]
resultsGenerator = (2 * n for n in range(2,11))

The output will look like this:

[4, 6, 8, 10, 12, 14, 16, 18, 20]
<generator object <genexpr> at 0x0000000002AB07E0>

So, while not providing any real feedback on your code,
I hope this will help you learn yet another concept about programming, as the same theory applies to other languages as well.

As an excercise though, you could try and replace your "imperative" for-loop
with a more "functional" list comprehension solution. (More on imperative vs functional later)

programming
Last revised: 23 Feb, 2012 12:08 AM History

Discussion

CoeloAmorry
CoeloAmorry
10 Dec, 2011 06:54 PM

Where to celebrate Christmas advise?

Your Comments

Used for your gravatar. Not required. Will not be public.
Posting code? Indent it by four spaces to make it look nice. Learn more about Markdown.

Preview