Nope, this is not about the alphabet song at all. It’s about how fast a Python code can generate 'abcdefghijklmnopqrstuvwxyz'.
The answer is already there, 'abcdefghijklmnopqrstuvwxyz'. Still don’t see it? Alright, let me give you a full Python code:
#!/usr/bin/env python # A program demonstrates how to generate an 'abcdefghijklmnopqrstuvwxyz' QUICK. atoz = 'abcdefghijklmnopqrstuvwxyz'
That’s the complete code, get it?
You might ask “Seriously, you are writing a post about this?” Yes, I’m.
I found many people have been trying to generate such string in different ways, more Pythonic, looks like smart or genius, or looks like hell. But sometimes, the direct approach is the most simple way and easy way to understand, not only by the programmer but also by people who don’t know about programming. The following list is what I saw from Internet, there must be more.
atoz = 'abcdefghijklmnopqrstuvwxyz' from string import ascii_lowercase atoz = map(chr, xrange(97, 123)) atoz = map(chr, xrange(ord('a'), ord('z') + 1)) atoz = map(chr, range(97, 123)) atoz = [chr(i) for i in xrange(97, 123)]
Which one is the best in your mind?
1 Runtime Profiling
I did some profiling, yep, I did. Here is the code for profiling:
#!/usr/bin/env python import sys import timeit def main(): m = [ ("atoz = 'abcdefghijklmnopqrstuvwxyz'", '', 0), ("from string import ascii_lowercase", '', 0), ("atoz = map(chr, xrange(97, 123))", '', 2), ("atoz = map(chr, xrange(ord('a'), ord('z') + 1))", '', 2), ("atoz = list(map(chr, range(97, 123)))", '', 3), ("atoz = list(map(chr, range(ord('a'), ord('z') + 1)))", '', 3), ("atoz = [chr(i) for i in xrange(97, 123)]", '', 2), ("atoz = [chr(i) for i in range(97, 123)]", '', 3), ('atoz[13]', "atoz = 'abcdefghijklmnopqrstuvwxyz'", 0), ('atoz[13]', "atoz = map(chr, xrange(97, 123))", 2), ('atoz[13]', "atoz = list(map(chr, range(97, 123)))", 3), ] max_len = max(map(lambda x: len(x[0]), m)) for i in range(len(m)): stat, setup, v = m[i] if v != 0 and v != sys.version_info[0]: continue if setup: sys.stdout.write('%s\n' % setup) else: setup = 'pass' sys.stdout.write(('%%-%ds -> ' % max_len) % stat) sys.stdout.write('%12.6f us\n\n' % (min(timeit.Timer(stat, setup).repeat(10000, 100)) * 1000000)) if __name__ == '__main__': main()
At first, I used cProfile, but it only shows to milliseconds level, so I switched to timeit. Each one is ran for 10,000 sessions and each session accumulate 10 runs of the code, then pick up the smallest runtime as result. Which represent how fast the code might be able to be executed. But there is a catch, it might actually be run faster than that just timeit could not tell because timer’s precision.
Here is the results:
CODE Python 2.5.4 Python 2.6.5 Python 3.1.2 atoz = 'abcdefghijklmnopqrstuvwxyz' -> 4.768372 us 4.768372 us 3.814697 us from string import ascii_lowercase -> 186.920166 us 177.860260 us 258.922577 us atoz = map(chr, xrange(97, 123)) -> 584.840775 us 560.998917 us vvvvvvvvvvvvv atoz = map(chr, range(97, 123)) -> 618.934631 us 577.926636 us vvvvvvvvvvvvv atoz = list(map(chr, range(97, 123))) -> ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ 775.814056 us atoz = map(chr, xrange(ord('a'), ord('z') + 1)) -> 609.874725 us 594.854355 us vvvvvvvvvvvvv atoz = list(map(chr, range(ord('a'), ord('z') + 1))) -> ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ 811.100006 us atoz = [chr(i) for i in xrange(97, 123)] -> 942.945480 us 875.949860 us vvvvvvvvvvvvv atoz = [chr(i) for i in range(97, 123)] -> ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ 946.044922 us atoz = 'abcdefghijklmnopqrstuvwxyz' atoz[13] -> 12.874603 us 9.775162 us 7.867813 us atoz = map(chr, xrange(97, 123)) atoz[13] -> 10.967255 us 7.867813 us vvvvvvvvvvv atoz = list(map(chr, range(97, 123))) atoz[13] -> ^^^^^^^^^^^^ ^^^^^^^^^^^ 7.867813 us
As you can see, using constant from string module, map, and list comprehensions are very slow. Yes, it’s one-time setup but the direct approach is also the one-time setup. And those, except the ascii_lowercase, would take everyone sometime to read it and understand.
2 Memory Profiling
I also did a memory profiling using this code with Guppy-PE:
#!/usr/bin/env python from guppy import hpy hp = hpy() hp.setrelheap() atoz = 'abcdefghijklmnopqrstuvwxyz' print hp.heap() print hp.setrelheap() from string import ascii_lowercase print hp.heap() print hp.setrelheap() print hp.heap() atoz = map(chr, xrange(97, 123)) print hp.setrelheap() atoz = map(chr, xrange(ord('a'), ord('z') + 1)) print hp.heap() print hp.setrelheap() atoz = map(chr, range(97, 123)) print hp.heap() print hp.setrelheap() atoz = [chr(i) for i in xrange(97, 123)] print hp.heap() print
The results:
=== Python 2.5.4 === Partition of a set of 1 object. Total size = 656 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 1 100 656 100 656 100 types.FrameType Partition of a set of 1 object. Total size = 560 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 1 100 560 100 560 100 types.FrameType Partition of a set of 1 object. Total size = 656 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 1 100 656 100 656 100 types.FrameType Partition of a set of 2 objects. Total size = 888 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 1 50 560 63 560 63 types.FrameType 1 1 50 328 37 888 100 list Partition of a set of 2 objects. Total size = 984 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 1 50 656 67 656 67 types.FrameType 1 1 50 328 33 984 100 list Partition of a set of 2 objects. Total size = 888 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 1 50 560 63 560 63 types.FrameType 1 1 50 328 37 888 100 list === Python 2.6.5 === Partition of a set of 1 object. Total size = 448 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 1 100 448 100 448 100 types.FrameType Partition of a set of 1 object. Total size = 448 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 1 100 448 100 448 100 types.FrameType Partition of a set of 1 object. Total size = 448 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 1 100 448 100 448 100 types.FrameType Partition of a set of 2 objects. Total size = 776 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 1 50 448 58 448 58 types.FrameType 1 1 50 328 42 776 100 list Partition of a set of 2 objects. Total size = 776 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 1 50 448 58 448 58 types.FrameType 1 1 50 328 42 776 100 list Partition of a set of 2 objects. Total size = 776 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 1 50 448 58 448 58 types.FrameType 1 1 50 328 42 776 100 list
Guppy isn’t compatible with Python 3, hence there is no result of Python 3.1.2.
Those use map or list comprehension would result a list and required more memory as expected, it doesn’t really affect how you use it. Usually you just access it like atoz[10], it works for list and string types. But memory use tells you that string type uses less memory, however, if you notice the runtime result above, you would have seen accessing list element is faster than substring of a string.
3 Conclusion
My conclusion is wgasa.
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.