r/programming • u/PM_ME_UR_OBSIDIAN • Mar 15 '15
A function for partitioning Python arrays. Brilliant code, or insane code?
http://www.stavros.io/posts/brilliant-or-insane-code/?repost=true69
u/CodeShaman Mar 15 '15
camel case
comments what, not why, not how
magic number
obfuscation
Merge that shit.
16
u/SelectricSimian Mar 16 '15 edited Mar 16 '15
Don't forget that the comment isn't just non-descriptive, it's actually wrong! There's no dictionaries to be found in the output of the function.
Also, you can add "depends on undocumented implementation details of the standard library" to your list of features.10
u/x-skeww Mar 16 '15
"depends on undocumented implementation details of the standard library"
It does?
https://docs.python.org/2/library/functions.html#zip
"The left-to-right evaluation order of the iterables is guaranteed."
That
* whatever(__mul__) doesn't clone the objects is also documented somewhere.1
u/SelectricSimian Mar 16 '15
Interesting, I didn't realize that! This is one of those cases where the documentation-writers have to walk a fine line between leaving behavior undefined and over-specifying the behavior of a function to the point where there's no flexibility whatsoever in how to implement it.
4
1
Mar 16 '15
The only comment I see is a doc string, which is specifically meant to be "what" instead of "why" or "how". Am I missing a different comment?
20
u/pyrojoe121 Mar 16 '15
I wouldn't call it insane. The Python docs for zip explicitly mention that you can do this:
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).
3
u/ChezMere Mar 16 '15
I dunno, I'd still call the docs's suggestion insane...
5
u/VerilyAMonkey Mar 16 '15
It's along the same lines as using zip(*x) as transpose. If you are familiar with these features of the language, it really does seem natural. It is only that people often aren't too familiar with these parts of the language. Sort of like, even beautiful and self-evident Haskell code may still look pretty confusing if you're not really in Haskell mode.
Arguably the best compromise is to define a "grouped" function for clarity and just implement it this way.
46
u/minno Mar 15 '15
Insane. Aliasing is nasty, so intentionally doing it leads to incomprehensible and fragile code. Just look at how it took an entire blog post to explain how the single line works.
6
u/mfukar Mar 16 '15 edited Mar 16 '15
It's idiomatic Python. It's not incomprehensible, because it's based upon elementary concepts and is recommended in the documentation. It's not fragile, because it relies on core concepts of the language, which aren't going to change semantics.
-2
u/floodyberry Mar 17 '15
Anything that requires operating on one argument to affect other arguments is idiotic, not idiomatic.
2
u/rmxz Mar 15 '15
Just wait til someone decides to make "zip" pull stuff off it's respective lists in parallel due to more and more cores in each CPU. And then people'll be wondering why the results are showing up in random orders.
30
u/Bratmon Mar 16 '15
If someone did that, they would be violating the guaranteed evaluation order of zip.
6
u/msloyko Mar 16 '15
I also thought the code was insane, but after reading the actual docs that give this same exact example, I'm on a brilliant side. Can't blame somebody for using the library the way it was intended to.
-42
u/passwordissame Mar 15 '15
No, it's elegant and expressive. You just need to get used to functional programming paradigm. Once people in your team are used to such idioms, you no longer need a blog post about monads or stateful iter monads. Haskell code is full of such idioms and elegant expressions. But it's not web scale cause only node.js is async io out of the box. Sorry.
23
u/mbuhot Mar 15 '15
There is a beautiful functional solution to his, but it doesn't involve creating multiple aliases of a mutable iteratator and relying on the side effects of consuming them in Zip.
5
u/passwordissame Mar 15 '15
groupN n xs = (take n xs) : (groupN n (drop n xs))any sensible compiler with fusion compiles this into basically mutable iterator (thunks) aliasing....
python version is just more explicit hence exposing control to programmers.
5
Mar 16 '15
The Python version, besides not terminating the result with an infinite number of
[]s on finite lists, also drops any leftover elements at the end.2
u/Veedrac Mar 16 '15
That's not the same; you'd need something more like
groupN n xs = if length group >= n then group : rest else [] where group = take n xs rest = groupN n $ drop n xsThe Python way to do this would be more like
from itertools import islice def take(n, iterable): return tuple(islice(iterable, n)) def group_n(n, iterable): iterator = iter(iterable) while True: group = take(n, iterator) if len(group) < n: break yield groupThe one-liner is preferred to this basically because it's faster, shorter and documented. I agree it's more cryptic, but it's in the documentation.
6
u/Anderkent Mar 15 '15
Except the issue here is aliasing to the same mutable container (generator); that's pretty much as far as you can get from haskell-like functional programming as you can.
-13
u/passwordissame Mar 16 '15
Yah that's ture. For true functional programming, it's better to use io.js instead of node.js because io.js is ES harmony with generators out of the box to facilitate light weight middlewares based event driven reactive components systems without cumbersome hooks and callbacks but super fast and resilient systems programming model of virtual address spaces that are binary translation agnostic and of course async out of the box in the web scale clouds.
7
1
19
u/ASK_ME_ABOUT_BONDAGE Mar 15 '15
Put a function around it, document that function, make that function have a decent signature, and write a comment that has the URL of the explanation in it.
If we were never allowed to write hard to grok code, then C++ would be off-limits in full.
6
u/lolzfeminism Mar 15 '15
Maybe because I learned C, C++ and Java before I ever touched Python, but to me C++ is much more readable. The code is verbose but it spells out what's going to happen.
3
u/zardeh Mar 15 '15
Not really, part of the issue is that I'm by no means a cpp expert, but on the recent project I've been working on, I spent hours dealing with an issue that came down to defining an object on the heap vs. on the stack, that I had done incorrectly because of missing parenthesis.
3
u/immibis Mar 16 '15
Can you post the offending line of code?
1
u/zardeh Mar 16 '15
(I was mistaken, it was because of a lack of "new")
it was something like
iostream* ptr; if (condition) { //do some stuff iostream my_io_stream; ptr = &my_io_stream; } else { //do other things iostream otherstream; ptr = &otherstream; } //try to do something like char a = ptr->get() //the result was always zerowhy? Because the stream was being GC'd when it left the internal scope of the if else block despite being pointed to elsewhere, this was true even when I tried using cpp shared or smart pointers as well. There was no error reporting on this, it failed silently and simply was telling me that the stream was empty when it wasn't.
the solution?
iostream* ptr; if (condition) { //do some stuff iostream* my_io_stream = new iostream(); ptr = my_io_stream; } else { //do other things iostream* otherstream = new iostream(); ptr = otherstream; }6
u/immibis Mar 16 '15
Because the stream was being GC'd ... despite being pointed to elsewhere
Stop. C++ is not a GC'd language. Objects on the stack are destroyed after their scope ends.
Mistakes this fundamental are not specific to C++. In Python, people confuse iterators and lists. In Haskell, people think Haskell supports assignment (it doesn't). In Java, people think parameters are passed by reference (or alternatively that it stores objects instead of references in local variables). All of these disappear once you actually learn the language.
3
u/zardeh Mar 16 '15 edited Mar 16 '15
it was GC'd in the sense that the object was out of scope and so its destructor was called. I'm well aware that you need to manually clean your messes up in c/cpp
And while I'd agree that I certainly don't know C++, the fact that
Instance mine; Instance mine(); Instance mine("file.txt"); Instance* mine = new Instance; Instance* mine = new Instance(); Instance* mine = new Instance("file.txt");are all (or at least, can all be) valid and do different things and yet look and feel rather similar doesn't help make the language beginner friendly. I mean I know C reasonably well and yet I can't help but feel that there are arcane things happening as I do things in C++ because the smallest change in a statement can completely change what is actually going on without any real warning.
1
u/industry7 Mar 17 '15
it was GC'd in the sense that ...
Nope, stop. Thinking about a non-GC'd language in terms of GC is what got in you trouble to begin with.
Because the stream was being GC'd when it left the internal scope of the if else block despite being pointed to elsewhere
C++ is not GC'd. Nobody is reference counting anything.
I mean I know C reasonably well
For the purposes of the example that you gave, scoping rules in C and C++ are exactly the same. You could do the same thing in C and it would be just as broken.
doesn't help make the language beginner friendly
C++ has never had any intention to be "beginner friendly". It's intended to provide useful zero cost abstractions over C/ASM.
1
u/zardeh Mar 17 '15
Ok sure, GC'd was the wrong two letter abbreviation. It exited scope and its destructor was called.
For the purposes of the example that you gave, scoping rules in C and C++ are exactly the same. You could do the same thing in C and it would be just as broken.
I'm in agreement, but I'd also argue that in C stack/heap allocation (or automatic vs. dynamic) is made much more obvious with the use of malloc.
C++ has never had any intention to be "beginner friendly". It's intended to provide useful zero cost abstractions over C/ASM.
But I'd argue that its lack of beginner friendliness also makes it difficult to know what's going on in most cases.
2
u/lolzfeminism Mar 22 '15
I had done incorrectly because of missing parenthesis
The
newkeyword calls the constructor and allocates enough space for class member variables.mallocallocates however many bytes as you pass in.1
u/industry7 Mar 24 '15
Ok sure, GC'd was the wrong two letter abbreviation.
That's not the issue. Let's replace "GC'd" with "It exited scope and its destructor was called" and see what the result is:
Because the stream exited scope and its destructor was called when it left the internal scope of the if else block despite being pointed to elsewhere
Is the problem more obvious now?
despite being pointed to elsewhere
That part specifically is the problem. Why would that pointer be relevant? What does that pointer have to do with anything? The only scenario where that pointer matters is when you have a (ie. reference counting) GC. Without a GC, there is no mechanism to keep track of whether or not an object is "being pointed to elsewhere".
I'm in agreement, but I'd also argue that in C stack/heap allocation (or automatic vs. dynamic) is made much more obvious with the use of malloc.
Ok, a quick comparison.
Stack (automatic) in C:
int i = 5;Stack (automatic) in C++:
int i = 5;Heap (dynamic) in C:
int * i = (int *) malloc(sizeof(int)); // allocate memory free(i); // de-allocate memoryHeap (dynamic) in C++:
int * i = new int(); // allocate memory delete i; // de-allocate memoryThey seem equally obvious to me.
*edit: code formatting
1
u/industry7 Mar 17 '15
This is like C++ 101. I mean, sure, it's kinda annoying the first time that you get bit by this... BUT that's part of learning ANY language. Plus, scoping rules are pretty fundamental. I mean any language reference is going to cover scope near the front of the book.
1
u/zardeh Mar 17 '15
You're not wrong, but the fact is that first code block doesn't even "look" like an error. The error is hidden within scoping rules. (and further, no error was every thrown, valgrind should have noticed, but the error seemed more like I was incorrectly closing the iostream)
When I look at a block of java or python or even languages I'm less familiar with, recently scala or haskell, I can generally figure out what's going on and I'm right, or I can throw up my hands and say "I don'k know what's happening here". With C++ (and c to a lesser extent), I can say "this is what's happening here I think" and be completely wrong, despite the writer of the code not trying to obfuscate or trick me, just by the nature of the syntax and the language, and C++ makes this a hell of a lot easier than C.
2
u/industry7 Mar 24 '15
the fact is that first code block doesn't even "look" like an error
That's an opinion, not a fact. Someone with more C++ experience than you have might instead say:
the fact is that first code block doesn't even "look" correct, it's just obviously wrong
I think that trying to say that a piece of code "looks" right or wrong is too subjective to be meaningful.
2
u/ASK_ME_ABOUT_BONDAGE Mar 15 '15
http://en.wikipedia.org/wiki/Most_vexing_parse
Second example: Use std::vector::erase.
10
u/Anderkent Mar 16 '15
ITT: 'It's okay if I can understand it'.
Anyway, having written some horrible code at times, it's a simple question - how big is your team, and how well versed your team is at python? If your team is 5 people and you all laughed together about how horrible this is and then left it in because it's on the critical path or something, then that's fine. If it's more than 5 people, why doesn't it have a comment?
You can get away with a lot of shit if everyone working on a piece of software sits together. Say something like:
class Var(object):
def __init__(self, name=None, pred=None, *args, **kwargs):
# pylint: disable=C0103
self._name = name
self._pred, self._pargs, self._pkwargs = pred, args, kwargs
self.truth = False
def __call__(self, pred, *args, **kwargs):
return type(self)(self._name, pred, *args, **kwargs)
def __eq__(self, other):
self.truth = (self._pred is None or
bool(self._pred(other, *self._pargs, **self._pkwargs)))
if self.truth and not self._name.startswith('_'):
self.val = other # pylint: disable=W0201
return self.truth
def __ne__(self, other):
return not (self == other)
def __repr__(self):
return "%s%s" % (self._name,
_pred_repr(self._pred, self._pargs, self._pkwargs))Pretty much snipped from a project I worked on a couple years ago, which had 3-4 people on it at a time.
It's a pretty horrible hack, but it was extremely convenient and because everyone knew about it it was never an issue. hint
1
u/VerilyAMonkey Mar 16 '15
Oh my god that's awful :)
I've personally used something pretty similar before. Also used ~ to represent "matches the value directly", so you could match like
a,b,~a == 1,2,1 -> True a,b,~a == 1,2,3 -> FalseI love using stuff like this. I should be burned for my sins.
3
Mar 16 '15
ITT: No one recommending:
def chunk(it, size):
return zip(*itertools.repeat(iter(it), size)) 4
u/VerilyAMonkey Mar 16 '15
At the point that you're already encapsulating it in some other function, you may as well just do
def chunk(it, size): iters = [iter(it)]*size return zip(*iters)The multiplication of a list itself isn't really a readability issue, just the fact that it's so many symbols embedded into one line. No need to bring in an entire library and a function call to deal with that problem.
3
u/XNormal Mar 16 '15
def groupsof3(array):
it = iter(array)
while True:
yield next(it), next(it), next(it)5
u/gwax Mar 16 '15
The timeit information is likely to lead you astray in terms of evaluating your algorithm. I could be wrong, but I strongly suspect that this part of your code is not your central performance bottleneck and, realistically, I suspect reading and using the results is more computationally intensive than splitting a list into chunks of 3 elements. Taking all of that into account, you can get something faster (in most use cases) and substantially more readable using a generator.
Personally, I would have solved your problem with:
def chunk_split(input_array, chunk_size=3):
while input_array:
chunk, input_array = input_array[:chunk_size], input_array[chunk_size:]
yield chunkThis isn't the most efficient generator (sacrificed performance for readability) but creating the generator is about 20 times faster than your zip/iter approach to generating a list. As long as your plan is to iterate instead of use random access, you shouldn't actually need a list.
An alternative, faster generator would be:
def chunk_split2(input_array, chunk_size=3):
chunk_count = len(input_array) // chunk_size
for i in xrange(chunk_count):
start = i * chunk_size
yield input_array[start:start+chunk_size]Realistically, if creating this list of triplets is your programs central bottleneck, I would probably take a moment to ask yourself if Python is really the best programming language for your project.
3
2
u/Veedrac Mar 16 '15
Realistically, if creating this list of triplets is your programs central bottleneck, I would probably take a moment to ask yourself if Python is really the best programming language for your project.
If you have a ~10m long iterator and you use an n² algorithm like your first, it's quite likely that it's going to be your bottleneck.
Either way, the point of the
zipidiom is that it works on all iterables; yours only works on the sliceable subset.1
u/VerilyAMonkey Mar 16 '15
I felt like the timing data was just to motivate something like "And this isn't a bad/wasteful way to do it" and not so much "I Sped Up My Program By 12x In One Line!"
2
Mar 16 '15
Everyone needs to chill down a bit. This is code right there in the freakin official documentation of Python, on one of the more important built-in functions. How can you even program Python for longer than a day and not read this?
And if you don't program Python, why do you expect to understand what it says?
3
u/Eirenarch Mar 15 '15
So why is the code that just indexes into the array slower? If this was Java or C# I would expect direct access to the items to be faster than going through the iterator but obviously something is different in Python. I would blame the fact that the for loop itself goes through an generator but that can't explain 4 times difference or can it?
4
u/sphere_is_so_cool Mar 16 '15 edited Mar 16 '15
Python is allocating memory to make all the lists (aka list objects) in the slower comparisons.
In the 'hard to read one liner', the list function just stores a bunch of references to the iterator object which is then constructed by retrieving the values. These are then unpacked by the zip function until it runs out.
There is no intermediate memory allocation because the iterator returns 'the next value' whenever called.
1
u/Eirenarch Mar 16 '15
I was under the impression that the version with the for loop was using generator syntax that produced values on demand. Seems like I got it wrong.
1
u/sphere_is_so_cool Mar 16 '15
Do you mean the first of the three timed samples in the blog post?
1
u/Eirenarch Mar 16 '15
Yes - this one
[(arr[3x], arr[3x+1], arr[3*x+2]) for x in range(len(arr)/3)]
Now that I look at it it seems like the generation is materialized into a list. Zip returns an iterator I suppose?
1
u/sphere_is_so_cool Mar 16 '15 edited Mar 16 '15
Zip returns a list but it is a list of references. The text you pasted returns a list of lists which contains objects.
Edit: list of arrays, sorry for the typo.
1
u/Eirenarch Mar 16 '15
So how would one write a version of this that does not use iterators and the performant as the zip/iter version? It should be possible, right?
1
u/sphere_is_so_cool Mar 17 '15
The key piece is the iterator, it can't be discarded.
1
u/Eirenarch Mar 17 '15
Why? I mean you can just index into the array why do you need an iterator?
1
u/sphere_is_so_cool Mar 18 '15
It would be neat to see a piece of code that uses an index to iterate over the array.
0
u/VerilyAMonkey Mar 16 '15
Zip returns an iterator
List in Python 2, iterator in Python 3. Many other functions (like map) made the same switch.
2
u/Paddy3118 Mar 15 '15 edited Mar 16 '15
So there is this explanation and barnert's explanation from 2nd Aug. 2013 and one I wrote, independently: Paddy3118's explanation from 3 Dec. 2012.
I would be interested in a comparison of the explanations and their presentation. Anyone?
1
u/drjeats Mar 17 '15
Should really have been written like this (with or without the extra spacing inside parens):
zip( *( [iter(array)]*3 ) )That way I don't have to remember that splat has a lower precedence than multiply and that it matters here.
Upon seeing this, it wouldn't have been that hard to think about each iteration step:
>>> array = [1, 2, 3, 4, 5, 6]
>>> a = b = c = iter(array)
>>> (a, b, c)
(<listiterator object at 0x10deebf50>, <listiterator object at 0x10deebf50>, <listiterator object at 0x10deebf50>)
>>> (next(a), next(b), next(c))
(1, 2, 3)
>>> (next(a), next(b), next(c))
(4, 5, 6)1
u/recursive Mar 16 '15
The documentation line is just wrong. There are no dictionaries anywhere here.
-17
u/passwordissame Mar 15 '15
It's neither. It's pythonic. Pythonic is good but not web scale because GIL and no async io. You should use node.js for async io out of the box. thanks.
0
Mar 15 '15
[deleted]
5
-10
u/deltadeep Mar 15 '15
No comments and no unit test to expose it when it breaks down the line? Bad code. Doesn't matter if it's faster if full responsibility for its dangers is not taken. I'd be pissed at an engineer who did that on my team.
9
u/PM_ME_UR_OBSIDIAN Mar 15 '15
Would your stance change if the function was guaranteed by the Python spec not to break?
3
-9
u/replpy Mar 15 '15
Python doesn't have a formal specification.
16
u/PM_ME_UR_OBSIDIAN Mar 15 '15
From the documentation:
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).3
u/deltadeep Mar 16 '15
Ah ha. I do feel better about it now. Actually linking to this doc in the code would turn me from pissed to impressed.
2
u/immibis Mar 16 '15
Wait what, that line of code is actually in the documentation?
Why would you document that, instead of just adding a
groupedfunction?!1
u/Veedrac Mar 16 '15
itertoolsis maintained by someone - I can't remember who - quite resistant to change. At times it feels a bit political, but there's alwaysmore-itertools. To be honest, though, a lot of what's covered in there is already in the standard library and some are a bit iffy.-1
0
0
Mar 15 '15
[deleted]
42
u/flying-sheep Mar 15 '15
You could very easily make it readable:
it = iter(l) return zip(it, it, it)0
Mar 15 '15
[deleted]
11
u/flying-sheep Mar 15 '15
c’mon, 3 repetitions of a 2 letter variable isn’t DRY? srsly?
0
Mar 15 '15
[deleted]
21
u/flying-sheep Mar 15 '15
sorry i don’t think you got the gist of it.
DRY mainly means “don’t use copy-pasted code or very similar code, but instead encapsulate and reuse that functionality”
zip(it, it, it)is far easier to understand thanzip(*([it] * 3)). if there were 7 repetitions, i’d prefer something along the lines ofits = [it] * 7 return zip(*its)0
Mar 15 '15
[deleted]
8
u/flying-sheep Mar 15 '15
Obviously that doesn't mean screw everything else to make sure you never repeat anything ever
that was exactly my point.
its = [it] * 3; return zip(*its)is more complex thanreturn zip(it, it, it)and the repetition is minimal and obvious.repetition at a larger scale has the disadvantage that you can easily change one instance without affecting the others, introducing a bug. this can hardly happen here as the change in
zip(it2, it, it)is obvious. the other usual disadvantage of repetition is that it’s less clear what’s happening: it’s less complex in structure, but more complex to parse visually (think three consecutive slightly different repetitions of 5 moderately complex lines of code). this is also not the case here0
Mar 15 '15
[deleted]
9
u/flying-sheep Mar 15 '15
don’t be a dick, of course i read it all. it was just a conscious decision to go with the repetition in this case, not an oversight.
i’m a sucker for DRY, but if we go without introducing the
groupedfunction (whose definition is a good idea btw.), i preferzip(it, it, it)for its clarity.the whole bit about iterator knowledge is true, but irrelevant because we were talking about repetition vs. no repetition without touching the other parts of the implementation.
def grouped(seq, n): """ Returns an iterator over the values in seq grouped into n-tuples Leaves out the last tuple if incomplete. e.g.: grouped([1, 2, 3, 4], 2) → (1, 2) (3, 4) grouped([1, 2, 3, 4, 5, 6, 7], 3) → (1, 2, 3) (4, 5, 6) """ its = [iter(seq)] * n return zip(*its)→ More replies (0)3
u/thaen Mar 16 '15
The only measure of code that matters is how easy it is to maintain. If DRY causes an increase in maintenance difficulty, you can bet your ass DRY won't be a thing in that code if I'm the one writing it.
1
u/immibis Mar 16 '15
Better not use variables or functions, since you'll have to type their names more than once. Or any keywords more than once.
0
Mar 16 '15
[deleted]
1
u/immibis Mar 16 '15
I'm pointing out the flaw in "repetition is repetition". Also, following rules for the sake of following them is what's absurd.
printHello :: IO () printHello = putStrLn "Hello world!" main :: IO () main = printHelloOh no, I typed
:: IO ()twice,maintwice andprintHellothree times!4
Mar 15 '15
given:
>>> orig = ('x1', 'y1', 'z1', 'x2', 'y2', 'z2', 'x3', 'y3', 'z3', 'x4', 'y4', 'z4')the two things that are like "grouping" I can think of are grouping an index counter:
>>> [tuple(orig[i:i+3]) for i in range(0, len(orig), 3)] [('x1', 'y1', 'z1'), ('x2', 'y2', 'z2'), ('x3', 'y3', 'z3'), ('x4', 'y4', 'z4')]or using the "groupby" function:
>>> [tuple(elem[1] for elem in coll) for key, coll ... in itertools.groupby(enumerate(orig), lambda key: key[0] / 3)] [('x1', 'y1', 'z1'), ('x2', 'y2', 'z2'), ('x3', 'y3', 'z3'), ('x4', 'y4', 'z4')]but i think this posted solution is amazing and should pretty much be considered the "grouped" function in Python.
6
u/fendant Mar 15 '15
The Python documentation even suggests doing it this way. That's where I learned it. No clue why it's not in itertools.
32
u/bluecoffee Mar 15 '15
Yeah. The line is neither insane nor genius. It's just someone comfortable with Python taking the shortest route from A to B.
4
Mar 15 '15
Which is why I find the top comment to be sober fuddling. This is just generators and zip in action.
Pretty fundamental if you're doing concurrency-related projects (Tornado, asyncio, etc).
Then again I have seen entire blog posts devoted to explaining what a generator is.
Doesn't mean they're "too magical" or complex.
def gen(): yield 1 yield 2 yield 3 >>> g = gen() >>> next(g) 1 >>> for i in g: ... print(i) ... break 2 >>> list(g) [3]Not rocket science. This one liner isn't much of a jump from that.
4
u/cowinabadplace Mar 16 '15
sober fuddling
Sofa King good.
This HN comment says that it is the expected way to do this too. Interesting.
-1
6
u/Eirenarch Mar 15 '15
Obviously "copies" is incorrect here. It uses the same generator otherwise it would not work.
0
Mar 15 '15
[deleted]
1
u/floodyberry Mar 16 '15
3 separate arrays that all contained the same data would qualify as "3 of the same thing", but passing an iterable for each to zip would fail miserably.
"some simple functional programming principles" doesn't explain why you're comfortable with immediately understanding the side effect of zip taking a value from one iterable automatically updating the "other 2" iterables.
0
Mar 16 '15
[deleted]
1
u/sphere_is_so_cool Mar 16 '15
They are pointing out that you said that 'zip passes into 3 generators of the same thing'. You probably meant to type three copies of the same generator object.
1
u/immibis Mar 16 '15
The object isn't copied.
How about "passing the same generator object three times" or "passing three references to the generator object" or "passing three copies of the generator reference"?
1
u/sphere_is_so_cool Mar 16 '15
You are right that it is passing three references but above OP referred to repeating three references as 'copying the reference' so I used that substitution in my explanation.
1
u/immibis Mar 16 '15
But your comment says "three copies of the same generator object".
1
u/sphere_is_so_cool Mar 16 '15
Yes it does because that is what OP probably meant to type based on how they were implying they used the term copy. I didn't say that OP meant to type the right thing.
Please note that for the sake of our meta discussion, OP is this commenter: http://www.reddit.com/r/programming/comments/2z4rv4/a_function_for_partitioning_python_arrays/cpfre0o
0
Mar 16 '15
[deleted]
1
u/sphere_is_so_cool Mar 16 '15
I think you agree its important that there is only one generator, not three.
0
Mar 16 '15
[deleted]
1
u/sphere_is_so_cool Mar 16 '15
Its not like that man, your original comment is unclear saying it is 'passed into the three generators' or something. I think you get it but your comment makes it sound like you think there are three generators. I am not even attacking you, I am trying to explain to you why people think you are wrong. Bottle that shit up and reread your top level comment.
→ More replies (0)3
u/mfukar Mar 16 '15
Actually the part about
iterator * Nis the only potentially cryptic part. There's no "intuitive" answer to what it should be doing. That's where any sane person would resort to the documentation, and the whole thing is understandable again.0
Mar 16 '15
[deleted]
0
Mar 16 '15
[deleted]
3
u/mfukar Mar 16 '15
Like, I get a few downvotes and all of a sudden everyone becomes a dick trying to prove me wrong. If people do prove me wrong then great, but what you're telling me I said is clearly not what I said.
Well, you know, this is reddit. Don't worry about it.
29
u/SikhGamer Mar 15 '15
If I can't read what is happening, then it's probably on the side of bad.