Python Python Collections (2016, retired 2019) Dungeon Game Dungeon Entrance

Angelica Hart Lindh
Angelica Hart Lindh
19,025 Points

Programmatically Generate CELLS

Hi Guys!

Something that may help... a less convoluted way to generate the grid of cells manually when using a programming language that has many built in functions and methods to do the heavy lifting for you.

CELLS = list((x, y) for x in range(5) for y in range(5))

# outputs: [
#    (0, 0), (0, 1), (0, 2), (0, 3), (0, 4), 
#    (1, 0), (1, 1), (1, 2), (1, 3), (1, 4), 
#    (2, 0), (2, 1), (2, 2), (2, 3), (2, 4), 
#    (3, 0), (3, 1), (3, 2), (3, 3), (3, 4), 
#    (4, 0), (4, 1), (4, 2), (4, 3), (4, 4)
# ]
jassim Al-Hatem
jassim Al-Hatem
3,133 Points

Geoshepherds HB,

can I do it in more than a line? because I'm actually surprised that you didn't use any commas... and I find it very confusing. so can I?

2 Answers

Chris Freeman
MOD
Chris Freeman
Treehouse Moderator 58,896 Points

Nice use of list comprehension!! It certainly accomplishes the goal of building the correct CELLS list.

Keep in mind when using an alternative approach it's good to evaluate the perceived saving against code readability and performance.

On readability, It wouldn't be obvious what the list comprehension produces without adequate comments. Many times the comments come so close to what the static assign would look like that it might be better just to use the static definition. In the case of the comment code, the Output comment is near the same as a static assignment.

I was surprised at the performance difference (which prompted me to chime in on this post). Let compare these two statements by running both 1,000,000 times.

PS C:\Users\Chris> python
Python 3.5.3 (v3.5.3:1880cb95a742, Jan 16 2017, 16:02:32) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import timeit
>>> help(timeit.timeit)
Help on function timeit in module timeit:

timeit(stmt='pass', setup='pass', timer=<built-in function perf_counter>, number=1000000, globals=None)
    Convenience function to create Timer object and call timeit method.

>>> stmt1 = "CELLS = list((x, y) for x in range(5) for y in range(5))"
>>> stmt2 = """CELLS2 = [
...     (0, 0), (0, 1), (0, 2), (0, 3), (0, 4),
...     (1, 0), (1, 1), (1, 2), (1, 3), (1, 4),
...     (2, 0), (2, 1), (2, 2), (2, 3), (2, 4),
...     (3, 0), (3, 1), (3, 2), (3, 3), (3, 4),
...     (4, 0), (4, 1), (4, 2), (4, 3), (4, 4)
... ]"""

# timing the list comprehension
>>> timeit.timeit(stmt=stmt1)
5.311569389775664
>>> timeit.timeit(stmt=stmt1)
5.330057834874792
>>> timeit.timeit(stmt=stmt1)
5.28356716814373

# 5.3084 ave

# timing the static definition
>>> timeit.timeit(stmt=stmt2)
0.22980729757793483
>>> timeit.timeit(stmt=stmt2)
0.22875172055734083
>>> timeit.timeit(stmt=stmt2)
0.21883134915842106

# 0.2258 ave  (23.5 times faster)

Edit: fixed "stmt1" typo

Angelica Hart Lindh
Angelica Hart Lindh
19,025 Points

Interesting insight into the topic, and thanks for taking the time to perform some performance tests!

With regard to readability, I would agree that naturally static definition will be more readable as it is entirely declarative. However it is cumbersome to write for the given example. Along with that reusability and customization of the code becomes much more labour intensive. I'm personally quite lazy so I like letting the language do the work for me ;) (the comments I had put in the example are just for illustration within the example, I had omitted the comments in my actual code).

Now to performance, static definition is going to be faster, as it is statically written, and thus there are no additional functions that need to be invoked. Again though for me personally it feels really inflexible compared to the dynamic definition. Performance of the code is important and there is probably another way to create the cells programmatically that isn't O(n2) that would improve performance somewhat.

Just curious, shouldn't (stmt=stmt) in your list comprehension tests be (stmt=stmt1)? As a side-note, not that it really matters a whole lot, but if you change the list((x, y) for x in range(5) for y in range(5)) in stmt1 to just use [(x, y) for x in range(5) for y in range(5)] you can get the same results and shave off about one second (about 17%) performance-wise. It seems trivial to point out unless you're messing with a monster list, but since it was in regarding performance...well, you know..

Chris Freeman
Chris Freeman
Treehouse Moderator 58,896 Points

Nice feedback Mike Wagner! Yep, the stmt v. stmt1 is a cut and paste error from my session window. Thanks for adding the info on using brackets over list(). I was using "lazy_compare" against strictly the OP code.

I totally agree with Geoshepherds HB. I came to the comments section to talk about it, and saw your post. Kenneth sure must be glad his grid isn't 1000x1000. I really find it weird that he chose to do it this way in a programming lesson.

Trace Harris
Trace Harris
Python Web Development Techdegree Student 18,825 Points

also feel that programmatically building the game grid makes sense in this scenario, This also opens the door for an additional feature adding user input, and a function call can allow the player to define his/her own dungeon, which I would be a nice use-case for a programmatically generated grid.