Steve, Monty, and Julia
November 11, 2026 at 2:34 PM by Dr. Drang
I don’t make a habit of reading obituaries, but I’m glad I clicked the link to this one in yesterday’s New York Times (that should be a gift link). Steve Selvin was a statistician who got his Ph.D. at the University of California, Berkeley, and taught there for many years. He got into the Times because of a probability puzzle he came up with in 1975: the Monty Hall Problem.
I won’t explain the problem here. Most of you already know it, and those who don’t can read the setup in the Wikipedia article. I do want to praise the Times for the cute little interactive game they included in Selvin’s obituary. It allows you to be a Let’s Make a Deal contestant and run through the problem again and again (with your computer playing the Monty Hall role) until you’re convinced that the “switching my choice” strategy is distinctly better than the “sticking with my original choice” strategy. I doubt you have the patience to play it enough times to confirm that switching wins 2/3 of the time and sticking wins 1/3 of the time.
I was less pleased with the explanation the Times gave for the probabilities:
After the first reveal, you have two doors, so you should have a 50-50 chance. Right? But you don’t. The host hasn’t randomly eliminated a door; he has deliberately revealed a goat, using his knowledge of where the car is. Your original door still has a one-in-three chance of hiding the car, while switching gives you a two-in-three chance of winning.
It’s right, of course, but it doesn’t strike me as an actual explanation—it sounds more like a restatement of the answer. To me, the way to think of it is to consider the winning strategy:
If you chose the door with a car behind it on your first guess—which will happen 1/3 of the time—you will win by sticking with that door. If you chose one of the doors with a goat behind it on your first guess—which will happen 2/3 of the time—you will win by switching to the door that Monty didn’t open. Therefore, switching gives you a 2/3 chance of winning.
Everyone’s different, so maybe you don’t need those extra words and the Times explanation is sufficient. Not for me, though.
I mentioned above that although you could play the game in the obituary enough times to get a decent estimate of the probabilities, most of us don’t have that kind of patience. The better way to run lots of simulations in a short time is to use a random number generator to have the computer do all the selecting. I’ve been meaning to try out the Julia programming language for quite a while; this seemed like a good problem to start with.
Here’s my script, switch.jl, for running 10,000 simulations using the “switch” strategy:
1: #!/usr/bin/env julia
2:
3: # Parameters
4: N = 10_000 # Number of trials
5: m = 0 # Number of wins
6:
7: # Loop through N trials, counting the number of wins
8: for i in 1:N
9: # Behind-the-scenes preparation
10: carDoor = rand(1:3)
11: goatDoors = [j for j in 1:3 if j != carDoor]
12:
13: # Player makes initial choice
14: playerDoor = rand(1:3)
15:
16: # Monty chooses an available goat door
17: if playerDoor == carDoor
18: # Pick either of the two available
19: montyDoor = goatDoors[rand(1:2)]
20: else
21: # Pick the one available
22: montyDoor = [j for j in goatDoors if j != playerDoor][1]
23: end
24:
25: # Player switches to the other door
26: playerDoor = [j for j in 1:3 if (j != montyDoor) && (j != playerDoor)][1]
27:
28: # Increment m if the player wins
29: if playerDoor == carDoor
30: global m += 1
31: end
32: end
33:
34: # Print the winning fraction for this strategy
35: println(m/N)
Because I’m new to Julia, I put in lots of comments, so I doubt there’s much need for more explanation. I will say, though, that I suspect a real Julia programmer wouldn’t use list comprehensions as often as I did. There’s probably a function for removing items from a list that’s more elegant than what I did in Lines 11, 22, and 26. I just don’t know what that is yet.
My script for running 10,000 simulations of the “stick” strategy, cleverly named stick.jl, is basically the same:
1: #!/usr/bin/env julia
2:
3: # Parameters
4: N = 10_000 # Number of trials
5: m = 0 # Number of wins
6:
7: # Loop through the N trials, counting the number of wins
8: for i in 1:N
9: # Behind-the-scenes preparation
10: carDoor = rand(1:3)
11: goatDoors = [j for j in 1:3 if j != carDoor]
12:
13: # Player makes initial choice
14: playerDoor = rand(1:3)
15:
16: # Monty chooses an available goat door
17: if playerDoor == carDoor
18: # Pick either of the two available
19: montyDoor = goatDoors[rand(1:2)]
20: else
21: # Pick the one available
22: montyDoor = [j for j in goatDoors if j != playerDoor][1]
23: end
24:
25: # Player sticks with original choice; do nothing
26: playerDoor = playerDoor
27:
28: # Increment m if the player wins
29: if playerDoor == carDoor
30: global m += 1
31: end
32: end
33:
34: # Print the winning fraction for this strategy
35: println(m/N)
The only difference is in Lines 25–26, where I’m basically doing a no-op.
These scripts give different answers each time they’re run because the random number generator starts with a different seed, but the answers are always close to 0.67 for switch.jl and 0.33 for stick.jl. As expected.
Are 10,000 simulations enough? Well, the 95% confidence interval can be calculated through this formula:
which works out to ±0.009 for both strategies. So 10,000 simulations is fine as long as we’re willing to accept the second decimal place to be off by one every now and then.
Images, the clipboard, and AppleScript
November 9, 2026 at 6:18 PM by Dr. Drang
Last week, my SnapClip macro crapped out with an error message. In the process of fixing it, I learned something about AppleScript that probably won’t ever help me in the future.
SnapClip is a Keyboard Maestro macro that puts a graphical user interface on a Python script, process-screenshot. The problem that led to the error was a particular branch of process-screenshot: the branch that puts the screenshot onto the clipboard. I’m not going to rehash the design of SnapClip or process-screenshot here—you can read the old post about them if you want the details.
The branch of process-screenshot that failed runs the impbcopy command-line utility written by Alec Jacobson over a decade ago. I’ve had impbcopy running successfully on my Macs for many years, but last week it failed with the error message
Bad CPU type in executable
I assumed the problem was that impbcopy had been compiled back when I was using an Intel Mac, and it had continued to work because of Rosetta. I thought that because I had Rosetta installed before, it would be updated along with macOS 27, but it wasn’t. Running
softwareupdate --install-rosetta
as described on the Rosetta page got SnapClip working again.
But macOS 27 is the end of the road for Rosetta, so it seemed like a change was due. I installed the Xcode Command Line Tools and tried to recompile impbcopy for Apple Silicon, but that failed with error messages that I couldn’t interpret because I’m not an Objective-C programmer. So I had four options:
- Learn enough about
gcc/Objective-C/clangto get the compilation to work. Even if I could do this, I assumed it would be a lot of work with no future value for me. - See if I could rewrite the SnapClip macro to use Keyboard Maestro features to put the image on the clipboard. This was more appealing, but I really liked the way the macro was structured and preferred to leave it as a GUI wrapper.
- Look into using Shortcuts to put the image on the clipboard and—if it can—replace the call to
impbcopywith an equivalent call to theshortcutsutility. But I hate Shortcuts and would rather avoid it if possible. - Work out an AppleScript solution and put a call to
osascriptintoprocess-screenshot. I knew that AppleScript has commands for reading files and putting things on the clipboard, so I went with this, even though AppleScript may be more of a dead end than Objective-C.
Apple’s online documentation for AppleScript sucks, but I have three old AppleScript books that collectively weigh over nine pounds:
- AppleScript: The Definitive Guide by Matt Neuburg.
- AppleScript 1-2-3 by Sal Soghoian and Bill Cheeseman.
- Learn AppleScript by Hamish Sanderson and Hanaan Rosenthal.
The answer (more or less) was in Learn AppleScript and led to just a couple of changes to process-screenshot. Here’s the new version:
python:
1: #!/usr/bin/env python3
2:
3: import tempfile
4: from PIL import Image
5: import os
6: import subprocess
7: import shutil
8: from datetime import datetime
9: import urllib.parse
10: import argparse
11:
12: # Handle the arguments
13: desc = 'Process a screenshot saved to a file.'
14: ep = '''If a title is given, it saves the image to a file on the
15: Desktop with a filename of the form yyyymmdd-title.png.
16: If no title is given, the image is put on the clipboard and the
17: upload option is ignored.'''
18: parser = argparse.ArgumentParser(description=desc, epilog=ep,
19: formatter_class=argparse.RawDescriptionHelpFormatter)
20: parser.add_argument('-b', '--background', help='add desktop background border', action='store_true')
21: parser.add_argument('-u', '--upload', help='upload to images directory and print URL', action='store_true')
22: parser.add_argument('-t', '--title', help='image title', type=str)
23: parser.add_argument('ssfile', help='path to screenshot file')
24: args = parser.parse_args()
25:
26: # Parameters
27: type = "png"
28: bgcolor = (85, 111, 137)
29: border = 32
30: optimizer = '/Applications/ImageOptim.app/Contents/MacOS/ImageOptim'
31: ascript = f'set the clipboard to (read POSIX file "{args.ssfile}" as picture)'
32:
33: # Add a desktop background border if asked for
34: if args.background:
35: snap = Image.open(args.ssfile)
36: # Make a solid-colored background bigger than the screenshot.
37: snapsize = tuple([ x + 2*border for x in snap.size ])
38: bg = Image.new('RGBA', snapsize, bgcolor)
39: bg.alpha_composite(snap, dest=(border, border))
40: bg.save(args.ssfile)
41:
42: # Optimize the file
43: subprocess.run([optimizer, args.ssfile], stderr=subprocess.DEVNULL)
44:
45: # Save it to a Desktop file if a title was given; otherwise,
46: # save it to the clipboard
47: if args.title:
48: sdate = datetime.now().strftime("%Y%m%d")
49: desktop = os.environ['HOME'] + "/Desktop/"
50: fname = f'{desktop}{sdate}-{args.title}.{type}'
51: shutil.copyfile(args.ssfile, fname)
52: bname = os.path.basename(fname)
53:
54: # Upload the file and print the URL if asked
55: if args.upload:
56: year = datetime.now().strftime("%Y")
57: server = f'user@server.com:path/to/images{year}/'
58: port = '123456789'
59: subprocess.run(['scp', '-P', port, fname, server])
60: bname = urllib.parse.quote(bname)
61: print(f'https://leancrew.com/all-this/images{year}/{bname}')
62: else:
63: subprocess.run(['osascript', '-e', ascript])
64:
65: # Delete the temporary file
66: os.remove(args.ssfile)
The changes are:
- Line 31, which is an added line that defines the AppleScript command that does what
impbcopyused to do; and - Line 63, which runs the
ascriptcommand viaosascript.
The AppleScript that does the work is just one line:
set the clipboard to (read POSIX file "<name of file>" as picture)
Very simple, and I suspect it will work for quite a while. I feel certain that Apple would like to kill AppleScript, but I don’t think it can—too many valuable customers (a group that doesn’t include me) would get very angry at having their longstanding automations die.
One thing that kind of surprised me was the as picture part of the command. Learn AppleScript says that you typically have to include an image type in the command: as GIF picture or as JPEG picture or as TIFF picture. The book suggests that a bare as picture is supposed to work only on PICT images. Never heard of a PICT image? That’s probably because it was the native Mac image format back in the classic days. It was one of Bill Atkinson’s contributions to the Macintosh Toolbox. Nobody saves image files as PICTs anymore.
And yet the unadorned as picture works. I created this test image in Acorn,

and saved it in several formats: BMP, GIF, HEIC, JPEG, JPEG 2000, PNG, and TIFF. I put the files in a folder named clipboard-test on my Desktop,

and ran this AppleScript for each of the different file extensions:
applescript:
set filePath to path to desktop folder as text
set imageFile to (filePath & "clipboard-test:Test image.bmp") as alias
set the clipboard to (read file imageFile as picture)
Each time I ran the script, the clipboard was loaded with the image, no matter which file was being read. I didn’t have to specify the format.
I suspect that if I were using AppleScript to write image data to a file, it wouldn’t be as forgiving. But process-screenshot uses Python and Pillow to save image files, so I don’t need to worry about AppleScript’s idiosyncrasies for that aspect of the script.
XKCD and Fourier
October 29, 2026 at 8:45 PM by Dr. Drang
If you’ve done Fourier series approximations of functions, you had three reactions to today’s XKCD comic:

First, of course, was a smile of recognition. Then came a “wait a minute” as you realized that the Gibbs phenomenon doesn’t get smaller as you add more terms to the Fourier expansion; it just concentrates itself at the corners:

That ringing never goes away. Here, for example, is the plot for

Finally, you realized that thinking like this ruins the joke.
Squaring the triangle with help
October 26, 2026 at 4:24 PM by Dr. Drang
Yesterday, Numberphile released a delightful video with James Grime (redundant, I know) in which he walks us through a geometry problem first posed by Henry Dudeney in 1902. It’s known as the “Haberdasher’s Puzzle” and is reproduced in Dudeney’s 1907 book The Canterbury Puzzles. The problem was solved by C.W. McElroy after Dudeney published it in a periodical called the Weekly Dispatch.
The problem is to cut up an equilateral triangle into pieces that can be translated and rotated (not flipped) to fit together into a square. The minimum number of pieces that allow you to do this is four, and here’s the arrangement shown in the video:

What James doesn’t do in the video is explain how you position the cuts. Dudeney shows a straightedge-and-compass construction in his book, but I wanted an algebraic solution. This turned out to be more complicated than I anticipated, but I had Mathematica to keep track of the messy algebra.
Let’s start with the equilateral triangle and its component pieces. The points that define the vertices of the triangle and cut lines are given the labels A through J (to avoid confusion, I is omitted).

The lines GJ and DH are perpendicular to Line EF.
To make the explanation simpler, let’s jump to the end and show how to make a square out of these components. The fun way to do it is in a series of rotations:
- Rotate the green piece 180° about Point G.
- Rotate the green and orange pieces together 180° about Point E.
- Rotate the green, orange, and blue pieces together 180° about Point D.
Here’s an illustration of the steps:

In this illustration, the red piece stays put through all the steps.
We can rotate the square to make its sides horizontal and vertical and label the various points to show where they ended up.

The vertices of the equilateral triangle have coalesced into a single point in the interior of the square. The right angles at H and J have split up to form the corners of the square.
Before we get into the algebra of the situation, let’s think about the various dimensions. The side lengths of the square, s, must be equal, so
A bit of algebra tells us that
Looking back at the drawing of the equilateral triangle, we see that the terms in the parentheses make up Line EF. Therefore, the length of EF is the side length of the square, s.
Now it’s time for some analytic geometry. We’ll put the origin of our xy coordinate system at Point A, and we’ll assume each side of the equilateral triangle has unit length. The coordinates are:
The area of the triangle is
This is the same as the area of the square, so
This will be the length of .
Points D and E are the midpoints of AC and BC:
Since we know the length of Line EF and the position of Point E, we can calculate the position of Point F along Line AB:
As I said, it’s kind of messy, but that’s what Mathematica is for. To six decimal places, the x coordinate is 0.254508. This is almost directly under Point D, but not quite.
Now we need the position of Point G. As it happens, it’s exactly unit to the right of Point F:
To six decimal places, this is 0.754508.
How do we know this? Consider this drawing:

The light green triangle, DEH, is congruent to the dark green triangle, GFJ. We know this because:
- The right angle at H matches the right angle at J.
- The length of DH is equal to the length of GJ (see the discussion above).
- The angle at D matches the angle at G because DE is parallel to FG and DH is parallel to GJ.
We therefore have an angle-side-angle congruence. The light green triangle is a duplicate of the dark green triangle, rotated 180° and moved up so its horizontal side is aligned with DE. The distance between D and E is , so that must also be the distance between F and G.
With the locations of F and G known, we can find H and J by running lines perpendicular to EF that intersect F and G. The full equations for the positions of H and J are even messier than those of F and G, so I’ll just give the six-place decimal approximations:
If you want to go through the Mathematica that generated these results and the graphics, here it is: