Hacker News new | ask | show | jobs
by mmillin 1382 days ago
One of my favorites is Peter Norvig's spelling corrector in less than 30 lines of python

https://norvig.com/spell-correct.html

1 comments

It may just be me, but I find Python nearly unreadable at times. Everything is just so ... slippery? Is this well written?

    w for w in words if w in WORDS
There's just two concepts there, w and words, doing a whole lot. I mean, that's literally a filter, but Python refuses to embrace these things because they're supposedly confusing or hard. In F#, that would be:

    List.filter (fun word -> List.contains word WORDS) words
I would of course rename WORDS and words to be something distinct rather than just differ by capitalization. The Python is confusing because the way to trace this comprehension is by starting with the w in the middle, then the w at the end, and then the w at the beginning (i.e., 2-3-1 sequence).

Also, I highly dislike using lines of code as some sort of ultimate measurement. The program is what it is, and then requires a page 5 times the length of the program to explain the program. The program should be clearer or more comments added to it or both.

The program also uses Python's "feature" of evaluating default arguments once when the function is defined as a sort of global variable, because the P function is never used by passing in a value for N. Seems clever, which is the problem with it. Lastly, function names should be more clear. "known", "P", and honestly the others are not good function names.

I much prefer the Python code here. The only confusing part is `words` and `WORDS` being used as identifiers, with different semantics. Other than that, the list comprehension syntax looks both clean and clear.
python has a built-in filter() that works exactly as your List.filter does. It's possible Peter Norvig just prefers list comprehensions, but it's not a weakness of the language or whether the Python community embraces filters per se.

     filter(lambda x: x in WORDS, words)
would I think do pretty much the same as what he wrote, and then depending on how you use the output, you may want to wrap that in list() or something. If you are going to iterate over it, it's gtg as-is.

There are much more idiomatic and clear (imo) ways of definining a function-static block (which gets evaluated just the first-time a function is called) than that default arg trick.

Yea, I know about Python's filter, but it seems not really used much (because it needs piping to be really useful). I used F# because I didn't want to bother looking up the syntax for Python.

Although, F# has list comprehensions as well.

    [for word in words do if List.contains word WORDS then word]
That's verbose because I spelled out word, but I feel it reads well while the Python version is weird to me. Mathematical set definitions follow the form of {e <- set | <condition on e>} which is read as "e in set such that e satisfies condition" or "for e in set and e satisfies condition". The Python version kind of reads weird to me as it's more like {e | e <- set | <condition on e>}. (I note that Counter is some iterable object and not a list, I suppose.) I really like comprehensions, but I always find Python's to be a bit strange. I think it's because the "in" in the posted comprehension has two semantic meanings, if I understand correctly. It's a nitpick for sure, but slippery is the best way I can describe Python.

> then depending on how you use the output, you may want to wrap that in list() or something

Shudder. Haha.

Yea, the default argument trick threw me off, which is why I don't think it's a good example of the "best" code. The best code doesn't have tricks.

It's a neat domain program, but not the "best" code.

That filter statement gets you an iterator, not a list. The reality is that map and filter in python aren't terribly ergonomic and thus are avoided even given a near-perfect use case, e.g. point-free application.
I prefer to use for loop instead of list compressions. List comprehensions are more compact but I find for loops more readable.
maybe off-topic, but I think the Swift version looks nice:

    words.filter { WORDS.contains($0) }.forEach { w in
        ...
    }
And then php:

    array_map(fn($match) => print($match), array_filter($words, fn($_) => in_array($_, WORDS)));
or less verbosely:

    array_map(fn($match) => print($match), array_intersect($words, WORDS));