News for hackers. Stuff that matters.
Re:Performance comparison: counting words in Python, C/C++, ... by codeflo on Sunday July 24, 2022 @01:21PM (#32215585)
For those curious about Rust optimization, the “optimized” version makes three changes compared to the “idiomatic” version:

* It uses byte strings instead of UTF-8 strings. In my opinion, that’s not an optimization, that’s changing the problem. Depending on the question you’re asking, only one of the two can be correct.

* It uses a faster hash algorithm. It’s not the first time this came up in a benchmark article. Rust’s decision to use a DOS-safe hash by default (and not provide a fast algorithm in the std, like other languages do) really seems to hurt it in that kind of microbenchmark.

* It uses get_mut+insert instead of the more convenient HashMap::entry method, because the latter would require redundantly allocating the key even in the repeat case. I’ve hit this problem in the past as well. Maybe the upcoming HashMap::raw_entry_mut will make this kind of optimization cleaner.

Re:Performance comparison: counting words in Python, C/C++, ... by tialaramex on Sunday July 24, 2022 @01:55PM (#32215909)
The problem specified declares the words we're counting are ASCII:

> ASCII: it’s okay to only support ASCII for the whitespace handling and lowercase operation

UTF-8 (quite deliberately) is a superset of ASCII. So a UTF-8 solution is correct for ASCII, but a bytes-as-ASCII solution works fine in Rust if you only need ASCII.

This is why Rust provides ASCII variants of a lot of functions on strings, and the same functions are available on byte slices [u8] where ASCII could be what you have (whereas their Unicode cousins are not available on byte slices).

Re:Performance comparison: counting words in Python, C/C++, ... by codeflo on Sunday July 24, 2022 @02:31PM (#32216256)
>> ASCII: it’s okay to only support ASCII for the whitespace handling and lowercase operation

That's a somewhat specific list -- at least I didn't read that as a general "the program can assume that the input is only ASCII".

But then, the author seems to have accepted solutions that crash on non-UTF8 sequences and ones that byte-compare them, so probably either behavior was meant to be fine. I just don't get that from this rule.

Re:Performance comparison: counting words in Python, C/C++, ... by anonymoushn on Sunday July 24, 2022 @03:16PM (#32216655)
> That's a somewhat specific list -- at least I didn't read that as a general "the program can assume that the input is only ASCII".

I don't think it assumes the input is only ASCII. If the problem is "given UTF-8 text, split on ASCII whitespace and convert ASCII uppercase letters to lowercase," you can do that correctly (and produce correct UTF-8 output) without really being UTF-8 aware. For why, see here: https://en.wikipedia.org/wiki/UTF-8#Encoding

> But then, the author seems to have accepted solutions that crash on non-UTF8 sequences and ones that byte-compare them, so probably either behavior was meant to be fine. I just don't get that from this rule.

That's a separate concern right? The rules are only about the behavior when the program is given UTF-8 input.

Re:Performance comparison: counting words in Python, C/C++, ... by nerdponx on Monday July 25, 2022 @08:09AM (#32223243)
The issue is that this is a weird requirement. I have seen real world data sets that had all manner of exotic whitespace like nonbreaking spaces and vertical tabs peppered throughout, so I am sympathetic. But this situation isn't common.

That said, for a CLI program like this, usually approximate results are good enough anyway. And realistically, most text should use ASCII whitespace for pretty much all text.

Re:Performance comparison: counting words in Python, C/C++, ... by anonymoushn on Monday July 25, 2022 @09:20AM (#32223852)
It is a context-specific requirement, but "fully general lowercasing" would be an impossible requirement.

For e.g. Japanese text, I think you'd only have to add 1 or 2 characters to the set of whitespace characters. You also have to solve Japanese text segmentation, which is hard-to-impossible. If you want to canonicalize the words by transforming half-width katakana to full-width, transforming full-width romaji to ascii, etc., that's a lot of work, and which of those transformations are desired will be specific to the actual use of the program. If you want to canonicalize the text such that the same word written using kanji or using only hiragana end up in the same bucket, or that words that are written the same way in hiragana but written differently when using kanji end up in different buckets, or that names that are written the same way in kanji but written differently in hiragana end up in different buckets, or that loanwords incorrectly written using hiragana are bucketed with the katakana loanword, or that words written using katakana for emphasis are bucketed with the hiragana word (but katakana loanwords are not converted to hiragana and bucketed with the non-loanword that is made up of the same moras), well, that all sounds even more challenging than the hard-to-impossible problem you already had to solve to decide where words begin and end :)

Edit: One of the first concerns I mentioned, about full width romaji and half width katakana, and additionally concerns about diacritics, can be addressed using unicode normalization, so these things are pretty easy[0]. An issue you may still face after normalizing is that you may receive inputs that have incorrectly substituted tsu ツ for sokuon ッ (these are pronounced differently), because for example Japanese banking software commonly transmits people's names using a set of characters that does not include sokuon.

My point is that this is not just a hard problem but many different, incompatible problems, many of which are hard, and because of the incompatibilities you have to pick one and give up on the others. An English-speaking end user may not want their wordcount to perform full width romaji -> ascii conversion.

[0]: https://towardsdatascience.com/difference-between-nfd-nfc-nf...

Re:Performance comparison: counting words in Python, C/C++, ... by nerdponx on Monday July 25, 2022 @09:53PM (#32233361)
Great write up! However I should've clarified, I wasn't talking about word segmentation in general, only about expanding the universe of valid "whitespace" grapheme clusters (or even just codepoints) to include the various uncommon, exotic, and non-Western items.
Re:Performance comparison: counting words in Python, C/C++, ... by saghm on Sunday July 24, 2022 @02:13PM (#32216089)
> It uses byte strings instead of UTF-8 strings. In my opinion, that’s not an optimization, that’s changing the problem. Depending on the question you’re asking, only one of the two can be correct.

Sure, but is it changing the problem to something easier than what the other languages are already doing, or to something more similar? I'd imagine the C code is basically just using byte arrays as well, for instance.

Re:Performance comparison: counting words in Python, C/C++, ... by olalonde on Sunday July 24, 2022 @03:51PM (#32217011)
> * It uses byte strings instead of UTF-8 strings. In my opinion, that’s not an optimization, that’s changing the problem. Depending on the question you’re asking, only one of the two can be correct.

To be fair, that's what the C version does as well.

Re:Performance comparison: counting words in Python, C/C++, ... by burntsushi on Monday July 25, 2022 @07:25PM (#32232302)
> It uses byte strings instead of UTF-8 strings. In my opinion, that’s not an optimization, that’s changing the problem. Depending on the question you’re asking, only one of the two can be correct.

No, that's very wrong. ripgrep has rich Unicode support for example, but represents file contents as byte strings. UTF-8 strings vs byte strings is an implementation detail.

I think you might benefit from reading the "bonus" Rust submission: https://github.com/benhoyt/countwords/blob/8553c8f600c40a462...

IMO, Ben kind of glossed over the bonus submission. But I personally think it was the biggest point in favor of Rust for a real world version of this task.

Re:Performance comparison: counting words in Python, C/C++, ... by SAI_Peregrinus on Sunday July 24, 2022 @02:33PM (#32216277)
The question as posed only cares about ASCII.
Re:Performance comparison: counting words in Python, C/C++, ... by coldtea on Sunday July 24, 2022 @01:40PM (#32215756)
>It uses byte strings instead of UTF-8 strings. In my opinion, that’s not an optimization, that’s changing the problem. Depending on the question you’re asking, only one of the two can be correct.

For tons of questions, both can be correct.

Re:Performance comparison: counting words in Python, C/C++, ... by stewbrew on Sunday July 24, 2022 @04:18PM (#32217277)
Ad uft8: it would change the problem only if the definition of word breaks changed. E.g., if a word break is defined by whitespace, then it probably doesn't.
Re:Performance comparison: counting words in Python, C/C++, ... by Conlectus on Sunday July 24, 2022 @04:32PM (#32217403)
What happens when the ASCII character for space is embedded within a multibyte UTF-8 codepoint?
Re:Performance comparison: counting words in Python, C/C++, ... by kd5bjo on Sunday July 24, 2022 @04:50PM (#32217571)
UTF-8 is designed so that this doesn’t happen: All bytes in a multibyte codepoint have the high bit set, placing them outside of the ASCII range.
Re:Performance comparison: counting words in Python, C/C++, ... by bitwize on Sunday July 24, 2022 @04:50PM (#32217578)
This never happens. In UTF-8, bit 7 is set in all valid multibyte representations; ASCII characters cannot appear in them. Now, you can have an ASCII character in the middle of a multibyte sequence, but this isn't valid UTF-8 (and should be treated as an error by the decoder). "Words" generated by a splitter would also have invalid UTF-8 if they were split on a space coming in between bytes in a multibyte sequence, but these can detected. Word splitting will not generate invalid words from valid input, or vice versa; and if you know you have valid input, splitting on space, \t, \n, \r, etc. is perfectly cromulent in UTF-8.

Now if you want to also split on U+200B (ZERO WIDTH SPACE), U+202F (NARROW NO-BREAK SPACE), etc... hoo boy.

Re:Performance comparison: counting words in Python, C/C++, ... by anyfoo on Sunday July 24, 2022 @04:38PM (#32217466)
If UTF-8 allowed that, then a solution would split words wrong according to UTF-8 rules, but it still would not matter, since the problem statement says that "it's okay to only support ASCII for the whitespace handling and lowercase operation".

So if your solution gets word splitting (or lowercasing) wrong for non-ASCII input, it still gets a pass according to my reading.

Re:Performance comparison: counting words in Python, C/C++, ... by Kiwikwi on Sunday July 24, 2022 @05:01PM (#32217675)
Such "overlong" encodings are not valid UTF-8.
Re:Performance comparison: counting words in Python, C/C++, ... by tialaramex on Sunday July 24, 2022 @07:33PM (#32218889)
This was down-voted, which isn't really the best way to handle things that are wrong

Your parent was wondering about a hypothetical UTF-8 sequence [all sequences in this post are hexadecimal bytes] XX 20 XX in which the ASCII space character encoded 20 is actually somehow part of a UTF-8 character. That's not a thing. UTF-8 is deliberately designed so that nothing like this can happen, along with several other clever properties, such properties are why UTF-8 took over the world.

Overlong sequences are like E0 80 A0 which naively looks like it's a UTF-8 encoding of U+0020 the space character from ASCII but it's not, because U+0020 is encoded as just 20. These encodings are called "overlong" because they're unnecessarily long, they're transporting a bunch of leading zero bits for the Unicode code point, the UTF-8 design says to reject these.

[ If you are writing a decoder you have two sane choices. If your decoder can fail, report an error, etc. you should do that. If it's not allowed to fail (or perhaps there's a flag telling you to press on anyway) each such error should produce the Unicode code point U+FFFD. U+FFFD ("The Replacement Character") is: Visibly obvious (often a white question mark on a black diamond); Not an ASCII character, not a letter, number, punctuation, white space, a magic escape character that might have meaning to some older system, filesystem separator, placeholder or wildcard, or any such thing that could be a problem. Carry on decoding the rest of the supposed UTF-8 after emitting U+FFFD. ]

Re:Performance comparison: counting words in Python, C/C++, ... by anonymoushn on Monday July 25, 2022 @09:38AM (#32224086)
I would expect a wordcount to pass these through as-is, along with any other invalid sequences of bytes with the high bits set. Performing unexpected conversions just seems like a way to make my future self have a bad time debugging.