Zigzag 5k
October 25, 2025 at 2:43 PM by Dr. Drang
I did the Fall Color 5k Run and Walk at the Morton Arboretum this morning. I say “did” to downplay the fact that I don’t run races like this, I walk them. I got terrible shin splints 20+ years ago and haven’t run more than a few hundred meters at a time since then. But I do try to walk at a decent clip. My goal is to do these races in under 45 minutes, and I managed that this morning. Here’s a screenshot of my watch as I crossed the finish line.

The time of 43:38 matched my official time to the second, so I’m confident the start/stop I did on my watch was accurate. What’s off, though, is the distance. My watch says I went 70 meters more than 5k. How can that be?
The obvious answers are that either my watch is slightly off through an accumulation of small GPS errors or the course distance is slightly off because it wasn’t measured perfectly. Could also be some combination of both.
But these answers—although probably right—are dull and don’t let me try out any calculations. Because I started the race late (very long traffic backup at the entrance; also today was the first time I slept past 6:30 for as long as I can remember), I had to pass a lot of people on the route. Could it be that my zigzag path along the road through the Arboretum added 70 meters to my walk?
Let’s simplify the problem to make it tractable. Imagine a straight road 5 m wide and 5000 m long. I take a zigzag path down the road, with each zig and zag of the same length and the full width of the road. How many of these zigzags are needed to end up with a path length of 5070 m, and is that answer reasonable?

We’ll take as the number of zigs and zags, so
The total length of my walk is
With , , and , what is ?
Since
we can solve for :
Now, when you’re taking the difference of two numbers that are relatively close to one another, you’re supposed to be careful about losing significant digits. Here, and differ by enough that we really don’t have to worry about that, but let’s be good boys and girls anyway.
Rewrite as
Then
and
so
and we won’t lose any significant digits because we’re adding, not subtracting.
Plugging in the numbers, we get
Is it reasonable to think I zigged and zagged 168 times? No. That’s one full crossing of the road every 30 meters or so, which is ridiculous.
So we’re left with the duller but more reasonable answer that my 5.07 km distance is some combination of measurement errors. Disappointing.
LLM proofreading update
October 23, 2025 at 10:20 AM by Dr. Drang
I’m still experimenting with ChatGPT and Claude as proofreaders for my blog posts. Because I’ve been writing more posts lately, I’ve been learning the good and the bad more rapidly than I would have expected when I started this. Time for a preliminary assessment.
Overall, ChatGPT does a better job than Claude, but they both have found errors that the other has missed. So far, this has cost me time but not money, as I’m still using the free versions of both. If I decide to subscribe, it will be to only one of them, so I see this as a sort of playoff round.
(I haven’t given Apple’s Writing Tools a serious test yet, mainly because I doubt they can navigate the mixture of text and code that my posts usually have. But I suppose it’s time to start and see if my doubts are justified.)
Both LLMs are pretty good at finding my most common mistakes, which are missing words (I often think I’ve typed a word that I haven’t) and inconsistent tense (which usually crops up as I rewrite part of a sentence). By “pretty good,” I mean they tend to find both real errors and hallucinatory errors. So far, the number of false positives has been acceptably small.
The two main problems I’ve had have been stopping them from giving me stylistic advice and getting them to give me good directions on where the errors are. My proofreading instructions have evolved over the past couple of weeks to address these deficiencies. Here’s my current set of instructions, which I insert via Typinator:
Find typographical errors and grammatical mistakes in the following Markdown text. Do not make any changes, just tell me what you think should be changed. Ignore all embedded URLs, quoted text, and programming code. Only report actual errors, not style suggestions. I am using the linefeed character to end each line and have put line numbers at the beginning of each line; use those line numbers to report where the errors are.
Claude suggested some of this prompt. In particular, the redundancy of including “Only report actual errors, not style suggestions” in addition to “Find typographical errors and grammatical mistakes” was Claude’s doing.
The wordy final sentence is my latest attempt to solve the navigation problem. I write my posts in BBEdit with line numbers along the left side of the window.

As you can see, line numbers in BBEdit are based not on how the lines appear as wrapped but on where the linefeed characters (ASCII decimal 10) are. This is how I want the location of errors reported to me, and it’s been surprisingly difficult to get both ChatGPT and Claude to do this right.
I started by telling them to report the line numbers of the errors. That gave me nonsensical and inconsistent line numbers. Then I told them to use the linefeed character as the line separator, and they still came back with nonsense. I had to repeat the instruction and tell them to do the analysis again. That second instruction in the same session managed to get both of them to give me correct line numbers, but ChatGPT spent a lot of time—maybe as long as a minute—doing the second analysis.
I asked ChatGPT why it took so long to process the text with proper line numbers in the analysis. Here’s what it said:
Good question — it’s because I have to process the text exactly as you specify (counting literal linefeed characters, not wrapped lines), which means I can’t rely on my usual tokenization or formatting tools. I have to manually parse and number every line in the order they appear, skipping over things like embedded HTML or math markup but still counting their linefeeds. That’s slower and more error-prone than my normal text analysis, but it’s the only way to follow your instruction precisely.
Apparently ChatGPT doesn’t know how to use wc -l. I asked it for the best way to handle this problem. It suggested four options:
- I could add section markers to the text. It would then report errors as “section 2, paragraph 3.” This was a nonstarter. The point is to analyze the text as written.
- It could report character offsets like “character 1,085.” Yeah, that’s very helpful.
- It could present to me a version of the text with line numbers at the beginning of each line. After I confirm the line numbering, it could then report errors using those numbers. This was tempting, but I didn’t really want my text spit back at me for confirmation. And what if I disagree with the line numbers it generated?
- I could give it the text with line numbers already in place at the start of each line. I was going to reject this suggestion until I realized how easily I could add line numbers to what I paste into ChatGPT without changing the text in BBEdit.
So I chose the fourth option and made this simple Keyboard Maestro macro to insert line numbers before pasting:

The macro is available from the Keyboard Maestro menu only when ChatGPT or Claude is the active application. The single-line shell script that adds the line numbers is
nl -ba | sed -E 's/^ +//'
The nl command adds the line numbers. The -ba option tells it to number all the lines instead of just the nonblank lines, which makes the line numbering consistent with BBEdit’s. The sed command then strips the leading space characters, which I thought might confuse ChatGPT, leaving just the line number and a tab character before each line proper.
Because the first action puts the line-numbered text on the clipboard, I added the second action to remove it, leaving the clipboard the way it was before the macro was called.
Although I wrote this macro based on ChatGPT’s responses, it works with Claude, too. Both LLMs now give me the line numbers I want.
Does it seem right that computer systems that use billions of dollars of hardware, electricity, and clean water need my help to add line numbers to a few kilobytes of plain text? No, it does not. But at least this gives me deterministic results for one part of the processing.
I still have my Mac recite my posts to me after passing them through the LLMs. This isn’t much help in finding mistakes anymore, but it still leads me to rewrite paragraphs here and there.
Ties and percentages
October 22, 2025 at 11:25 AM by Dr. Drang
I was looking at the NFC standings yesterday, particularly the NFC North, the Chicago Bears’ division. The hated Packers are at the top of the division with a 4-1-1 record and a winning percentage of .750.

(Screenshot taken this morning from ESPN.)
How does a 4-1-1 record turn into .750? I guess the obvious answer is that the tie is treated as half a win and
But for some reason, my initial thought was more complicated. I thought of it as the weighted average of the winning percentage of non-ties with the winning percentage of ties:
(There may be a philosophical argument as to whether a tie truly constitutes a .500 winning percentage, but I’m just going to accept it as a given.)
The calculation works out the same, of course, because
I think this says something about me and my tendency to overcomplicate things. On the other hand, it’s always nice to be able to work out a solution in more than one way.
If you find that referring to numbers like .750, .800, and .500 as “percentages” a tiny bit off-putting, you’re my kind of people. But it’s common in sports to divide one number by another and call it a percentage—a tribute to the elasticity of language.
Two other common features of sports percentages:
- They are always (?) written to three decimal places. I think this was arrived at through experience—enough digits to distinguish close races but not enough to waste space.
- They are always spoken as if the decimal point isn’t there. The examples above are “seven-fifty,” “eight hundred,” and “five hundred.”
Baseball also uses “average” to express one number divided by another: batting average and earned run average come to mind. But it also has the slugging percentage, which isn’t remotely like a percentage.
I’m a big fan of “modulus,” mainly because of its many uses in engineering mechanics, e.g., Young’s modulus, bulk modulus, and section modulus. But I don’t expect to see it on ESPN anytime soon.
Clean Amazon links
October 18, 2025 at 10:06 AM by Dr. Drang
A footnote in last night’s post mentioned that the Amazon links therein had been cleaned of cruft. Today, I’ll show the simple automated way I do that.
When I say “cruft,” I mean the unnecessary additions to the URL that are presumably used by Amazon to benefit Amazon rather than you. For example, I just went to Amazon, searched for “homepod,” and clicked the link for a refurbished HomePod mini. This is the URL:
https://www.amazon.com/Apple-HomePod-mini-Black-Renewed/dp/
B0FK8JL74Q/ref=mpsa12?dib=eyJ2IjoiMSJ9.
okyLqpx2YKuwgWlCBKI2sx5DMA0QRhm1CnQPmA4KRzU8prbSTnXdAbE97fG0
tEwplRuboB2VvY5O6X6RZlvBK-MDyqaDNPwuqLUtqglS2gVwxITbCrE-
A5JKngZ9MmPHgucGCJSduRY0Ap3RUqVJy42OrYI9eh-
GcIQn52Fj7NgpN2pv3jyeZBvnCXsUcbFhTREmb2NV66tKI0ccT4OUg.4yYkD
-bBpGy0yyNVWMQnH8ZirFh3NhnFov0B0BZDIQ&dib_tag=se&keywords=
homepod&qid=1760794983&sr=8-2
I’ve added line breaks to make it easier (sort of) to read. I got this using Vivaldi, a browser I downloaded to my phone a while ago but haven’t used to visit Amazon before. It was definitely not signed into my Amazon account. Even so, I wouldn’t be surprised to learn that some part of this monstrous string is tied to me and my current session at Amazon.
Regardless, virtually none of this URL is needed. The key features are the dp at the end of the first line and the alphanumeric string that starts the second line. That string is the product’s ASIN. This simplified URL,
https://www.amazon.com/dp/B0FK8JL74Q
is enough to get to the product page. To go from the long URL to the shorter one, I use this Keyboard Maestro macro:

There’s not much to it. It grabs the URL of the current Safari page1 and puts it on the clipboard. It then does a search-and-replace on the clipboard using this regular expression for searching:
^.*/(?:dp|gp/product)/([^/?]+).*$
Over the years, I’ve noticed that some URLs have the ASIN after gp/product instead of dp, which is why you see the alternative there in the first set of parentheses. The ?: that follows the opening parenthesis means that this is a non-capturing group. What is captured is the ASIN that follows, which is why you see $1 at the end of the replacement regex:
https://www.amazon.com/dp/$1
The last part of the macro is a simple bit of error handling. I’ve noticed that I sometimes run this macro when I’m not on an Amazon page, which would put a URL I don’t want on the clipboard. So this checks the clipboard for the proper form. If it’s right, the Glass sound plays; if it’s wrong, the Basso sound plays and the clipboard goes back to what it was before.
If you download the macro (right click the link so you can save it with a .kmmacros extension) and import it into Keyboard Maestro, it will appear in the Global Macro Group and will be activated. I have it in the Global group because I sometimes use it in Messages or Mail to keep the URL I’m sending short. It’s not just for writing posts.
I should mention that I wrote about a very similar TextExpander abbreviation a decade ago. In that post, I had TextExpander run JavaScript for Automation to get and simplify the URL. I was also somewhat dismissive of using Keyboard Maestro—which I didn’t own back then—for a task like this. Times have certainly changed.
-
Yes, the long URL is from Vivaldi, not Safari, but I had the same product page open in Safari. The short URL is the same for both because the
dp/B0FK8JL74Qpart of the long URL is the same for both. ↩