Clear/Verbose -
def maxWidthOfVerticalArea(self, points: List[List[int]]) -> int:
x_coords = sorted(x for x, _ in points)
greatest_diff_so_far = 0
for i in range(len(x_coords)-1):
diff = x_coords[i+1] - x_coords[i]
if diff > greatest_diff_so_far:
greatest_diff_so_far = diff
return greatest_diff_so_far
Pythonic - def maxWidthOfVerticalArea(self, points: List[List[int]]) -> int:
xs = sorted(x for x, _ in points)
return max(x2-x1 for x1, x2 in zip(xs, xs[1:]))
I'm curious which approach is bad/worse? On the one hand, the "pythonic" approach seems brief and kind of neat and I feel clever writing it. On the other hand, it is also ~20% slower per my testing, it's less obvious what it's doing, and it would be harder to make some change to the logic (or add logging/metrics) without completely rewriting it.On the one hand, just from the reasons I wrote above, it seems to me like the first way is better and the second worse, but on the other hand, the first way is what I would write while knowing zero python features. Curious if anyone else has thoughts on this.
1 - https://leetcode.com/problems/widest-vertical-area-between-t...
def maxWidthOfVerticalArea(self, points: List[List[int]]) -> int:
xs = sorted(x for x, _ in points)
neighboring_coordinates = zip(xs, xs[1:]))
return max(right-left for left, right in neighboring_coordinates)
or even naming the width generator: def maxWidthOfVerticalArea(self, points: List[List[int]]) -> int:
xs = sorted(x for x, _ in points)
neighboring_coordinates = zip(xs, xs[1:]))
widths = (right-left for left, right in neighboring_coordinates)
return max(widths)
Same thing, just with more built in explanation. It's easy to go overboard, of course, but it's a useful go-to to make stuff more obvious. from operator import itemgetter
def maxWidthOfVerticalArea(self, points: List[List[int]]) -> int:
xs = sorted(points, key=itemgetter(0))
x_pairs = zip(xs, xs[1:])
widths = (right-left for left, right in x_pairs)
return max(widths)
NumPy also has stuff to solve this sort of thing built-in: from numpy import diff
def maxWidthOfVerticalArea(self, points: List[List[int]]) -> int:
xs = sorted(points, key=itemgetter(0))
widths = diff(xs)
return widths.max()But it doesn't do the same thing. The original code extracted the xs from the coordinate pairs, sorted; your code sorts the coordinate pairs by their x values, but doesn't extract the xs from them.
I save clever for the real mechanics of problems, for data structures, and occasionally optimization of running time if that is a factor (it often is not as much as you would think).
I eschew clever if it seems like it is just a way to pack a lot of stuff into fewer lines of code. One of the reasons that Perl attained a reputation of "write once, read never" unmaintainable code (whether or not it was justified) is a culture that cultivates "one-liners" and packing everything down into a minimum space. Our filenames in Windows no longer have to be 8.3. We are not constrained to write our code in a few kilobytes.
I programmed in the days where you might have two kilobytes in which to program. In those days, extreme terseness was a value. It is not any longer. "Cat" and "man" and "mv" are holdovers from those days. This is not a telegram where you are charged by the word. I program for total obviousness and sometimes that makes my code seem "dumb." I might do only one or two things per line. That's really fine.
Just as an example, I wrote this obnoxious web app in Perl as part of my job. At some point, some students were to take it over, but they didn't know Perl. I received an excited phone call from them at some point: they were able to understand and port my application to a language they were familiar with because I commented, I was not clever in packing, and I kept it to "one or two things per line."
I personally think either function is fine. The max finding code pattern in the long function should be familiar to most. For the Pythonic function, I don't think it's less obvious enough to matter. You have a list of stuffs and you want to find the max so you wrap it in the max function. Maybe zip(xs, xs[1:]) can take a bit to recognize but I think it's eh. For the Pythonic one, one can also write
all_diffs = [x2-x1 for x1, x2 in zip(xs, xs[1:])]
return max(all_diffs)
Using list is also markedly faster than the Pythonic generator for me, which is to be expected: ~16% faster than both.[0] https://gist.github.com/squaresmile/8dd08898851a4e19359cdb30...
This is a very personal opinion, but I do like the second one more. I find it easier to read, but maybe that's just because I'm used to that style. And maybe you're more likely to have to rewrite it for a change, but that's kind of okay since there's less code there.
On readability, here's how I'd read it out loud:
Sort all the Xes from points. Return the max of the difference between two points that are next to each other.
I find that quicker to put in my head than the equivalent parsing of the first solution, and exactly the intention of the code.
def maxWidthOfVerticalArea(self, points: List[List[int]]) -> int:
x_coords = sorted(x for x, _ in points)
max_diff = 0
for x1, x2 in zip(x_coords, x_coords[1:]):
max_diff = max(max_diff, x2 - x1)
return max_diffIn my vscode setup with mypy strict, pylance basic and python 3.9 though, type hints all days for me. I personally forget the arguments types like 1 second after I write the function. Maybe I'm conditioned by typed python.
Leetcode codes usually have simpler data structure and without 3rd party libraries so that's even easier to add types for.
Edit: Oh looks like Leetcode python code template has type hints
when you paste this code, it's infinitely more readable out of its original context with those hints.
def max_width_of_vertical_area(points):
xs = sorted(x for x, _ in points)
return max(x2-x1 for x1, x2 in zip(xs, xs[1:]))
Just seems easier...Though I spend most my time writing Swift code, which definitely influences my preference.
Would you say this is "less readable substantially" than your example?
def max_width_of_vertical_area(points):
# type points: List of List of int
xs = sorted(x for x, _ in points)
return max(x2-x1 for x1, x2 in zip(xs, xs[1:]))Are Python type hints able to specify types like that?
My experience even before that was mostly with PyDev in eclipse. The trick back then was identifying good spots to add type-hints, afterwhich the "IDE" would take over and propagate them through the different variables and calls using static code analysis. It was surprisingly good and helped immensely with very few actual instances of types being hinted. Another one was PyCharm that would "save" or "cache" the types during debug/test runs and use them as type-hints without you explicitly having to define them.
Fancy example:
List[Dict[str, MyCustomTypeGeneric[str]]]
Which gives you a list of dictionaries with keys of type string, holding values MyCustomTypeGeneric with generic type str.
Yes, Iterable[Tuple[Number, Number]] is a valid type, though 2D points are probably Tuple[Real, Real] or just Complex, not Tuple[Number, Number].
Snake case.
this-is-called “kebab case”
ThisIsCalled “Pascal Case”
Particularly the for loop into the range of the length of the coords minus one, is difficult to wrap my head around and easy place for mistakes to sneak in.
With that being said, I have had my difficulties wrapping my head around zip, and don't have difficulties anymore, which might actually be in favor of parents point.
return max(xs[1:]-xs[:-1])
...using something like numpy. I wonder if APL/J has successive element operator. <./ ([&}. - }:) xs
[0] https://code.jsoftware.com/wiki/Vocabulary/curlyrtdot
[1] https://code.jsoftware.com/wiki/Vocabulary/curlyrtco >./-2-/\/:~ xs def maxWidthOfVerticalArea(self, points: List[List[int]]) -> int:
return np.diff(sorted(x for x, _ in points)).max()I’m fine with most of the rest of Python’s functional-style iterable helpers. But something about zip just doesn’t work for me.
[1,2,3,4]
[5,6,7,8]
# go down each column, yielding these in order
[1,5]
[2,6]
[3,7]
[4,8]
And then you drop ones with no pair. So zipping [1] and [2,3] makes just [1,2]. The same is true if you reverse the arguments, so [1,2] and [3] => [1,3]The example is sorta obtuse at a glance[1], but this: `zip(xs, xs[1:])` equates to this:
[1,2,3,4]
[2,3,4] # copy and drop the first element
# outputs columns
[1,2]
[2,3]
[3,4]
So it's a one-liner to make a list of of each consecutive pair of items in a list. All `zip(x, x[1:])` do the same thing regardless of the ultimate use of such a construct.But yeah, I have seen some very confusing uses of zip before sitting and thinking for a bit. TBH I think much of it is just familiarity though - after seeing a million `for (i=0;i<thing.len();i++) { ... }` I can pattern-match and figure this out easily:
for (i=0, l=thing.len(); i<l-1; i++) {
list.push([thing[i], thing[i+1]])
}
(though `i<l-1` ease may depend on font) but similarly-common functional idioms just don't slide into position as easily. Yet.[1]: I'm guessing it's the equivalent of finding the largest "gap" between a bunch of sizes of things.
xs = [1,2,3,4,5,6,...
ys = [a,b,c,d,e,f,...
[ (1,a),
(2,b),
(3,c),
(4,d),
(5,e),
|6| |f|
|7| |g|
|8| |h|
|9| |i|
|0| |j|
. .
. .
. .
]
https://kezan.eu/wp-content/uploads/2016/05/Zipper_Thumb_144... zip(xs[:-1], xs[1:]) # explicit
zip(xs, xs[1:]) # implicitDepends on your team. If you wrote stuff in the second way you would be obstructing, adding friction to less python experienced developers but you would be minimizing the amount of code written.
It's hard to say, which is right or wrong here but the opacity of the second approach as you mention adds unpredictability.
I honestly think Dart is a very good solution from the culmination of various classical languages we've been using so far with Java and Javascript but there just doesn't seem to be any major FANG company investing in creating something more "modern" with Python.
I think it is a testament to Python's accessbility and also the depth it provides for experts as well. I guess the forced indentation is what gets to some developers but its a minor inconvenience.
And I think most Python developers that produce bad code do so because they try to use too many of the whiz-bang features - or overuse bad design patterns.
You can do almost anything with functions namespaced in modules - KISS.
People say scheme is hard but it is by far the easiest to teach, especially compared to Python.
I would suggest something small, that you can realistically fit all in your head. I honestly believe Pascal is still an excellent choice. For something a little less low-level and more scripting-like, Lua seems nice. The JavaScript ecosystem is awkward, and there are things to criticize (no integers?) but by itself, it's a nice small language as well.
This is such a fantastic analogy. Thank you!
C++ tries to be all the paradigms, even if they're bolted on. Python is similar in that regard. Complex OO, functional, exceptions, decorators, lots of container types, iterators, generators, operator overloads... The only thing it's missing is a macro language.
Python's "batteries included" standard library stance takes it even beyond the C++ analogy in terms of sheer volume of out of the box capabilities you get.
And then you have macros like in macros that traverse and transform code.
If they would do that, then they would realize Python is as powerful as Common Lisp, Ada, C++,.... with similar doc size in pages.
I never experienced the "I hate Python" phase. I think I went straight from "So exciting" to "productive" phase with Python.
I reencountered Python at a data science course that was somehow mandatory for my masters in ODE numerics. It has a HUGE availability of standard and third-party packages, so you can be INSANELY productive. I'd write in Brainfuck if it was the only way to have like Pytorch and giotto-tda and networkx and fastapi in the same room.
My first CS professor insisted the first year be done all on the whiteboard. And this wasn't back in the dark ages. It was long after every student had a personal computer on their desk. Everything was done in a low-level pseudocode somewhere between Algol and C.
She felt that computers and actual programming languages would get in the way of learning the principles. I'm not quite that hardcore. But she was on to something. I learned more from that class than any other.
https://www.python.org/dev/peps/pep-0636/
The above tutorial is based on user input but to my mind this mechanism seems equally good for teaching recursion on data structures as you would in say, ML.
Sorry, but Perl still wins there.
When i tried to learn python i got caught up in trying to learn imports and classes and other things before i actually fully understood how stuff like variables, arrays and functions worked.
Its tiny standard library, which kind of makes it limiting, I think is a boon for learning. It forces you to remake the wheel at times. Despite not being an OOP language, I learned about OOP by trying to write my own class system in lua using metatables, hitting the limits of what I could do, and looking into languages with built in classes that had all the 'things I was missing' in my own hackish OOP attempts in lua.
As far as learning goes, not having the tools baked in forces you to learn about how they actually work when you have to remake them. It might not be efficient for people who just want to get the job done, but for learning it's definitely helpful.
Lua's also used in a bunch of 'fun' things. It's used as a scripting language in tons of games, half the time when I look up lua stuff these days it's all about roblox...which I think is big with the kids or something...it's used in fantasy consoles like the pico8 and the tic80, it can be used to script whole games with things like LOVE and the solarus engine.
Personally, I found it to be a good language overall for inspiring self-motivated learning. There's always something fun that can be accomplished with lua relatively easily.
I am very happy that I started with C. It has surprisingly few features compared to most languages and it gave me a good appreciation for how memory is managed at a relatively low level. The biggest downside was that it took me a long time to learn how to make anything I thought was "cool" at first (like GUIs, graphics, sounds, or networking). Some people might struggle with that.
I rarely write C code now. It's not as practical for the kinds of things I make and I have nitpicks with it just like any other language. But I don't think that's the point. Your first programming language is an instrument for learning first and foremost. Once you've become adapt at one language, it's relatively easy to pick up more.
Do you have an opinion about Common Lisp? I know that Scheme is more minimal and cleaner but since you recommend Scheme I want to know what your opinion about Common Lisp is.