Brilliant or insane code?
stavros.io
stavros.io
The left-to-right evaluation order of the iterables is guaranteed. This makes
possible an idiom for clustering a data series into n-length groups using
zip(*[iter(s)]*n).
http://docs.python.org/2/library/functions.html#zipI've updated the post with this, another commenter pointed it out. Thanks!
(echo -e "one\ntwo\nthree\nfour") | paste -d, - -
to get result of: one,two
three,four
by exploiting a similar trick, i.e. reading two times ('- -') from the same iterator (STDIN of 'paste') $ seq 1 9 | paste - - -
1 2 3
4 5 6
7 8 9 echo -e "one\ntwo\nthree\nfour" | paste -d, - -
works just fineReading this post brought back that feeling.
If people don't understand a completely valid and terse way of coding something, sometimes instead of bothering to understand it, they will bash it. Sometimes, this is a totally valid way to vent frustration, and then they learn something new, and all is good. But sometimes, it just gets left as "this is wrong" and then someone else thinks it is wrong, and so on. That is wrong, and tech leads or architects that enforce such crap will bug the living shit out of good developers and lose them.
I appreciate clarity. But, terse one-liners can be just as clear if not clearer than code that unnecessarily adds more methods/functions/names/local vars and claims to be "more testable", etc.
You shouldn't have to sacrifice the ability to be terse and clear at the same time. Testing is no excuse for code bloat. You can likely write a test that executes the behavior without having to atomize it. Assess the amount of production and test code you are writing. How much more code are you actually having to write in order to test, both in the tests themselves and in the code which you are having to test?
It happened here with Python and it happens in many languages. It even happens with laws and regulations in government. If someone gets the same thing done just as ethically but without the bureaucracy, just appreciate it as another perhaps better way of doing something. Don't bash it publically because you don't understand it.
w/r to Python in particular, it has a history of ending up with idioms that are "tricky" and not particularly more or less terse than other techniques, but are able to exploit the standard library functions to get a faster-running result.
This is, of course, at odds with the motto of "there should be only one (obvious) way to do it," so every experienced Python programmer has to internalize a small dictionary of idiomatic one-liners for these exceptional cases. (Fortunately, it's not that big. I can only think of three or four off the top of my head.)
Errors per LOC is allegedly constant, across all languages.
I also consider the cost of change when designing things.
I once created an HL7 wrapper that was a marvelous thing of beauty. Fluent API, clever use of the type system. But no one could maintain it, including me. It had too much magic. So I scrapped it, went with a dumber implementation.
If this is true, then a more verbose style will have a higher error rate. (more LOC to do the same task -> more errors)
This implies that more expressive languages(more expressions per line) are less prone to errors.
You conclusion would be correct if he had stated that error per expression was constant between languages.
I think that's what he means.
Well, there is some truth to that. Anecdotally, most lines of code will be read many times before they are changed/discarded, and most of this reading will be skimming, where the reader is either: 1) trying to understand the structure of the code, or 2) trying to figure out where to make modifications.
Code that's hard to understand quickly (e.g. by skimming) is technical debt. I think a good litmus test would include not just the effect on error rate, but also the effect on the time it takes to understand the code and to make changes to it.
I write specialist-o-matic code. Lotsa DRY, composition, iterators, "fluent" APIs.
Makes me an unapologetically poor general purpose pair programming partner.
terse and clear at the same time
Concision is a virtue.
Can be.
Or do you mean there's some value in redundancy? I'd be interested in an example. Even java added the <> to avoid the repeated template parameter,
Foo<Things> x = new Foo<Things>();
becomes Foo<Things> x = new Foo<>(); val x = new Foo<Things>();At least, that is my experience in the world of web-based programming. Scripts and other single-purpose code implementations are another case entirely.
This is one of those cases where Ruby does it better (s.each_slice(3).to_a).
i = iter(array)
return zip(i, i, i)
There you go. All but neceessary magic gone with just one line more.(and the original article is dealing with coords in graphics, which is "maths-related code" in my book, but perhaps not in everyone's)
It's OK here, but may bite you with a different function.
The left-to-right evaluation order of the
iterables is guaranteed.
http://docs.python.org/2/library/functions.html#zip user=> (partition 3 [1 2 3 4 5 6])
((1 2 3) (4 5 6))
user=> (partition 3 [1 2 3 4 5 6 7])
((1 2 3) (4 5 6))
user=> (partition-all 3 [1 2 3 4 5 6 7])
((1 2 3) (4 5 6) (7))
user=> (partition 3 3 (repeat 0) [1 2 3 4 5 6 7])
((1 2 3) (4 5 6) (7 0 0))λ> chunksOf 3 [1..12]
[[1,2,3],[4,5,6],[7,8,9],[10,11,12]]
def grouper(iterable, n, fillvalue=None):
"Collect data into fixed-length chunks or blocks"
# grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
args = [iter(iterable)] * n
return zip_longest(*args, fillvalue=fillvalue)
- What is the most “pythonic” way to iterate over a list in chunks? [1]- Idiomatic way to take groups of n items from a list in Python? [2]
- Python “Every Other Element” Idiom [3]
- Iterate an iterator by chunks (of n) in Python? [4]
- How do you split a list into evenly sized chunks in Python? [5]
[1]: http://stackoverflow.com/questions/434287/what-is-the-most-p...
[2]: http://stackoverflow.com/questions/2461484/idiomatic-way-to-...
[3]: http://stackoverflow.com/questions/2631189/python-every-othe...
[4]: http://stackoverflow.com/questions/8991506/iterate-an-iterat...
[5]: http://stackoverflow.com/questions/312443/how-do-you-split-a...
[6]: http://docs.python.org/3/library/itertools.html#itertools-re...
I should mention that I ended up using the fourth version (seemingly the slowest) but it is actually the fastest depending on your input -- as the length of the elements gets larger, the fourth method tends to vastly outperform the others.
[7] http://stackoverflow.com/questions/16685545/elegantly-iterat...
def chunks(seq, n):
"groups the elements of the seq into a list of n-sized chunks."
return zip(*[iter(seq)]*n)"The left-to-right evaluation order of the iterables is guaranteed. This makes possible an idiom for clustering a data series into n-length groups using zip([iter(s)]n)."
zip(arr[::3], arr[1::3], arr[2::3])
which is nearly as fast but doesn't work with iterators.
If you want to use iterators you could also do zip(islice(arr, 0, None, 3), islice(arr, 1, None, 3), islice(arr, 2, None, 3))
which is a tad slower.This accomplishes the same thing without being hard to understand:
from itertools import islice
iterator = iter(array)
try:
while True:
yield list(islice(iterator, 3))
except StopIteration:
passThe OP's question of is this genius or bad is clear in that regard: it is bad, due to not being the proper optimization direction, but it is interesting.
Maybe I'm doing crazy stuff, though!
http://docs.python.org/2/library/itertools.html#itertools.iz...
EDIT: See update above.
Turns out that islice doesn't raise an IterationError, it just returns an empty list.
Fixing the problems, it runs in 237 μsec per loop, around 23 times more than the zip version.
while True:
result = list(islice(iterator, 3))
if not result:
break
yield result n = iter(array).next
[(n(), n(), n()) for _ in xrange(len(array) / 3)]may save a few keystrokes some rainy day. good post.
In [3]: ar = [1, 2, 3, 2, 4, 6, 3, 5 ,7, 3, 5, 8]
In [4]: %timeit zip([iter(ar)]3) 100000 loops, best of 3: 2.02 us per loop
In [5]: %timeit zip(ar[0::3], ar[1::3], ar[2::3]) 1000000 loops, best of 3: 1.37 us per loop
In [6]: %timeit zip((iter(ar),)3) 1000000 loops, best of 3: 1.34 us per loop
From which I conclude: - zipping slices is even more efficient, and arguably easier to grok - but you get about the same runtime by multiplying a singleton tuple rather than a list
However if you want to generalize the chunk size, multiplication seems to win out over slicing (with tuples still being more efficient than lists):
In [7]: chunk1 = lambda n, it: zip([iter(it)]n)
In [8]: chunk2 = lambda n, it: zip((iter(it),)n)
In [9]: chunk3 = lambda n, seq: zip(*(seq[i::n] for i in xrange(n)))
In [10]: %timeit chunk1(3, ar) 100000 loops, best of 3: 2.32 us per loop
In [11]: %timeit chunk2(3, ar) 1000000 loops, best of 3: 1.83 us per loop
In [12]: %timeit chunk3(3, ar) 100000 loops, best of 3: 3.55 us per loop
"""Parses an array of xyz points and returns a array of point dictionaries."""
'Only, it doesn’t really. It takes an iterable of points...and returns an iterable of 3-tuples of groupped points'
The wrongness of it would cause me to double-take, because even if I were familiar with this usage, it isn't what the comment suggests is happening. A docstring more like: """Parses an iterable of values [x,y,z,x,y,z...] and returns an iterable of 3-tuples: [(x,y,z),(x,y,z)...]"""
Would be a lot more clear by simple virtue of truth, even if it didn't explain the code step by step.Test 1: Boring, small array of integers
In [28]: arr = range(0, 300)
In [29]: %timeit [(arr[3*x], arr[3*x+1], arr[3*x+2]) for x in range(len(arr)/3)]
10000 loops, best of 3: 27.2 us per loop
In [30]: %timeit numpy.reshape(arr, (-1, 3))
10000 loops, best of 3: 45.2 us per loop
In [31]: %timeit zip(*([iter(arr)]*3))
100000 loops, best of 3: 6.25 us per loop
This roughly matches the article's timing ratios, so far so good.Test 2: Use numpy's random number generation to get a small array of floats
In [32]: arr = numpy.random.ranf(300)
In [33]: %timeit [(arr[3*x], arr[3*x+1], arr[3*x+2]) for x in range(len(arr)/3)]
10000 loops, best of 3: 54 us per loop
In [34]: %timeit numpy.reshape(arr, (-1, 3))
1000000 loops, best of 3: 1.06 us per loop
In [35]: %timeit zip(*([iter(arr)]*3))
10000 loops, best of 3: 39.7 us per loop
numpy is two orders of magnitude faster here; it's evidently using a highly optimized internal codepath for random sequence generation, which I'd guess is a common thing to do in numeric analysis. I assume it's using a generator, so there's no actual array being created, blowing up the CPU cache lines etc.Test 3: Verify that analysis by interfering with numpy
In [36]: arr = [x for x in numpy.random.ranf(300)]
In [37]: %timeit [(arr[3*x], arr[3*x+1], arr[3*x+2]) for x in range(len(arr)/3)]
10000 loops, best of 3: 26.2 us per loop
In [38]: %timeit numpy.reshape(arr, (-1, 3))
10000 loops, best of 3: 48.5 us per loop
In [39]: %timeit zip(*([iter(arr)]*3))
100000 loops, best of 3: 6.55 us per loop
Yep.Test 4: Larger data set, no interference
In [40]: arr = numpy.random.ranf(3000000)
In [41]: %timeit [(arr[3*x], arr[3*x+1], arr[3*x+2]) for x in range(len(arr)/3)]
1 loops, best of 3: 624 ms per loop
In [42]: %timeit numpy.reshape(arr, (-1, 3))
1000000 loops, best of 3: 1.06 us per loop
In [43]: %timeit zip(*([iter(arr)]*3))
1 loops, best of 3: 335 ms per loop
The numpy time doesn't change at all from test 2 despite the larger size, but the others suffer. Again, I suspect numpy is being intelligent here; my guess is that it doesn't actually apply the function and generate the real output, it just wraps the random generator in another one.Test 5: Larger data set, interfering with numpy
In [44]: arr = [x for x in numpy.random.ranf(3000000)]
In [45]: %timeit [(arr[3*x], arr[3*x+1], arr[3*x+2]) for x in range(len(arr)/3)]
1 loops, best of 3: 321 ms per loop
In [46]: %timeit numpy.reshape(arr, (-1, 3))
1 loops, best of 3: 354 ms per loop
In [47]: %timeit zip(*([iter(arr)]*3))
10 loops, best of 3: 83.6 ms per loop
There we go; we're back to roughly the original timing ratios.So, surprise! You always have to measure. Measure, measure measure. My bias is to write code first for legibility and modifiability, and then optimize hot spots if needed (and add comments, please, when you do so).
Without doing deeper analysis I'd say one moral of the Python story is, this shows the potential power of generators. But in real-world data sets this isn't always ideal -- is it faster to load up the whole data set in memory and blast through it, or load it from disk on demand with a generator? In really high performance scenarios, is it faster to preprocess the data to fit into the CPU's cache lines? You can't tell without measuring, and you have to measure in the environment you're deploying to, since the answer may be different on a machine with 1GB RAM vs. one with 128GB RAM, or 32KB L1 cache vs. 8KB.
and then just %timeit numpy.array(arr), you'll see that the reshape takes no time at all. Type conversion from python list to numpy array is what kills the performance.
Which is exactly what the parent comment was all about - the author figured that the reason numpy was significantly faster was because it was accessing / working with the data in a different fashion.
So, in order to test that theory, he converted the numpy.array into a normal python array before he proceeded to do any timed operations with zip vs. numpy.reshape, etc.
This is a more realistic playing field if you're considering data that was created outside of the numpy environment. At some point, if you're going to work with numpy.reshape, it will need to be type converted / "imported" into numpy data types.
For the purposes of this test, it's much more "fair" to include both the time numpy spent on splitting the array as well as that conversion time. The reshape process in numpy had essentially O(1) time with native data types indicating that it had done some behind the scenes work that allowed for such speed. The parent example is much more realistic in capturing the time of the behind the scenes work by forcing each method to start from the same exact same data objects.
To clarify my points a bit, the optimizations I alluded to (in "highly optimized internal codepath") were meant to include things like using a generator, i.e. at no point is there an actual array of input random numbers. The fact that in numpy the 300-element "array" and the 3,000,000-element "array" had identical timings suggests exactly that; I disagree that it's an issue of internal representation, unless the concept of a numpy array subsumes the concept of a generator, in which case I think we're all saying the same thing.
That kind of optimization is only possible in this case because by the definition of randomness nobody could know what the values were until they were enumerated, so it's 100% transparent to use a generator. That's not how real-world data works, hence my forced-native-array measurement and pudquick's reply.
>>> some_boolean = False
>>> ["Thing 1", "Thing 2"][some_boolean]
"Thing 1" "Thing 1" if some_boolean else "Thing 2"
is also almost twice as fast(775 vs 1340 ns, on my machine).In particular, if you want the side effects of both operations. Trying to write multiple things while checking if any of them failed, for instance.
by the implementation you mean.
See: http://stackoverflow.com/questions/1094961/is-there-a-python...
http://docs.python.org/2/library/itertools.html
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return izip(a, b) def paired(t, size=2, default=None):
it = iter(t)
return itertools.izip_longest(*[it]*size, fillvalue=default)
I use it in a formatter which outputs alphabetized data in columns, where the order should run down the columns instead rowwise.Oh. Well. OK.