Ladders and friction
January 11, 2024 at 7:54 AM by Dr. Drang
Rhett Allain, who’s the physics columnist for Wired and a professor of physics at Southeast Louisiana State University, solved a funny problem a couple of weeks ago involving a ladder leaning against a wall. You can see his solution on YouTube or Medium. I think of it as an oddball problem because it’s very different from the “ladder leaning against a wall” problems I’ve been seeing for over 40 years.
It started as a calculus problem in which you are to assume that Point A moves down the wall with a constant speed and then determine the speed of Point B. Prof. Allain thought (rightly) that Point A of a real ladder wouldn’t fall at a constant speed, so he decided to turn it into a physics problem with gravity acting to accelerate the ladder’s downward movement. His goal in this altered problem was to determine the point at which Point A loses contact with the wall. A key feature in Allain’s problem was zero friction between the wall and the ladder and the floor and the ladder. This certainly makes the solution easier.
Ladder problems in engineering
Both of these problems are different from the ladder/wall problems that are commonly given to engineers in their introductory mechanics courses. Ladders and walls are typically found in the friction section of the study of statics. In these problems, the idea is to determine either
 the lowest angle for which the ladder doesn’t slide down the wall for a given coefficient of friction; or
 the lowest coefficient of friction for which the ladder doesn’t slide down the wall for a given angle.
The key word in these problems is doesn’t. These are statics problems, in which things don’t move, and we look for the conditions under which the ladder stays in place. Here are some images from example problems in textbooks I have:
From Seely & Ensign’s Analytical Mechanics for Engineers (Wiley, 1941):
From Den Hartog’s Mechanics (McGrawHill, 1948, but I have the Dover reprint):
From Synge & Griffith’s Principles of Mechanics (McGrawHill, 1949):
From McGill & King’s Engineering Mechanics: Statics (PWSKent, 1989):
They use different loading and different friction conditions, but basic idea is the same in all of them.
Static friction
In addition to the laws of statics—Newton’s Second Law with no acceleration—these problems make use of Coulomb’s^{1} static friction relationship:
$$f\le \mu \phantom{\rule{thinmathspace}{0ex}}N$$where $f$ is the friction force, which runs parallel to the contact surface in the direction opposite that of impending motion; $N$ is the normal force; and $\mu $ is the friction coefficient. Let’s talk about each of these.
In common speech, “impending” means “about to happen,” but that isn’t quite what the authors of engineering textbooks mean when they use it in this context. These are, after all, statics problems, so there is no movement about to happen. The meaning here has been stretched to “what would happen if there were no friction.”
The “normal” in “normal force” means “perpendicular.” The normal force is perpendicular to the contact surface.
The friction coefficient, $\mu $, is a marvel of engineering simplification. The magnitude of the friction force depends on many things: which two materials are in contact, their surface roughness, any lubrication that might be present, even the temperature. But to get practical solutions, we simplify these many conditions down to a single number. Well, maybe not a single number, because when you look up the friction coefficient for a particular set of materials, you’ll often find a range of values. Still, boiling friction down to a number, even if we don’t know the number exactly, is a great way to think about friction and get reasonable answers.
The simplest ladder problem
In the simplest version of the ladder problem, we assume there is friction between the ladder and the floor (Point B), but no friction between the ladder and the wall (Point A). If you look carefully, you’ll see that this is the problem Synge & Griffith were exploring. Here’s a freebody diagram of the ladder and all the forces acting on it:
We’ve taken the mass center of the ladder to be at its geometric center.
The three equations of statics are
$$\∑{F}_{x}={N}_{A}{f}_{B}=0$$ $$\∑{F}_{y}={N}_{B}\mathrm{mg}=0$$ $$\∑{M}_{B}=\mathrm{mg}\left(\frac{L}{2}\mathrm{cos}\theta \right){N}_{A}(L\mathrm{sin}\theta )=0$$Here, the x and y directions are horizontal and vertical as usual, and the positive direction for moments about Point B is counterclockwise.
Because we were smart in choosing Point B to take moments about, the second and third equations have only one unknown variable each and can be solved directly:
$${N}_{B}=\mathrm{mg}$$ $${N}_{A}=\frac{\mathrm{mg}}{2}\mathrm{cot}\theta $$We substitute the second of these solutions into the first statics equation to get
$${f}_{B}=\frac{\mathrm{mg}}{2}\mathrm{cot}\theta $$Here’s where the friction coefficient comes in. We know that ${f}_{B}\le \mu {N}_{B}$, so substituting in the solutions from statics, this inequality turns into
$${f}_{B}=\frac{\mathrm{mg}}{2}\mathrm{cot}\theta \le \mu {N}_{B}=\mu \mathrm{mg}$$or, after rearranging,
$$\mu \ge \frac{1}{2}\mathrm{cot}\theta $$So this gives us the minimum friction coefficient for the ladder to stay up at a given angle. A little algebra tells us the lowest at which the ladder will stay up for a given coefficient of friction:
$$\theta \ge {\mathrm{tan}}^{1}\frac{1}{2\mu}$$If you’re worried about trig functions changing signs and messing up these inequalities, recall that the ladder angle is always between 0° and 90°, so there are no sign changes for tangent or cotangent.
A slightly more complicated ladder problem
Now let’s add friction between the ladder and the wall. To keep the number of variables to a minimum, we’ll assume the ladder/wall friction coefficient is the same as the ladder/floor friction coefficient.
Here’s the freebody diagram:
The addition of ${f}_{A}$ to the system means we no longer have a statically determinant system. Now we have four unknowns to go with the three equations of statics:
$$\∑{F}_{y}={f}_{A}+{N}_{B}\mathrm{mg}=0$$ $$\∑{F}_{x}={N}_{A}{f}_{B}=0$$ $$\∑{M}_{B}=\mathrm{mg}\left(\frac{L}{2}\mathrm{cos}\theta \right){N}_{A}(L\mathrm{sin}\theta ){f}_{A}(L\mathrm{cos}\theta )=0$$And we have two inequalities of Coulomb friction:
$${f}_{A}\le \mu {N}_{A}\phantom{\rule{mediummathspace}{0ex}},\phantom{\rule{1em}{0ex}}{f}_{B}\le \mu {N}_{B}$$We can combine these five relationships, and if we’re very careful in keeping the inequalities pointed in the right directions, we’ll come up with an inequality that relates the friction coefficient to the angle of the ladder. But most engineers wouldn’t solve the problem that way. Instead, they’d use the following physical reasoning to simplify the algebra:
 When two surfaces are on the verge of slipping, the inequality in Coulomb’s friction relationship turns into an equality. This makes the algebraic manipulations easier, because we don’t have to worry about the inequality signs flipping on us.
 The ladder can’t slip at just one of its ends. If it slips at Point A, it must also slip at Point B, and vice versa. And the same is true when the two points are on the verge of slipping.
 The larger the friction coefficient, the lower the angle at which the ladder will be on the verge of slipping. So when we solve for the ladder angle or friction coefficient on the verge of slipping, it will be the minimum angle or minimum friction coefficient.
Using these three bits of reasoning, we can say
$${f}_{A}=\mu {N}_{A}\phantom{\rule{mediummathspace}{0ex}},\phantom{\rule{1em}{0ex}}{f}_{B}=\mu {N}_{B}$$and combine these equations with the statics equations as follows:
The second equation of statics tells us that
$${f}_{B}={N}_{A}$$so
$${N}_{A}=\mu {N}_{B}$$The first equation of statics tells us
$${f}_{A}=\mathrm{mg}{N}_{B}$$so
$$\mathrm{mg}{N}_{B}=\mu {N}_{A}={\mu}^{2}{N}_{B}$$Therefore,
$${N}_{B}=\frac{\mathrm{mg}}{1+{\mu}^{2}}$$ $${N}_{A}=\frac{\mu \mathrm{mg}}{1+{\mu}^{2}}$$and
$${f}_{A}=\frac{{\mu}^{2}\mathrm{mg}}{1+{\mu}^{2}}$$Plugging the expressions for ${N}_{A}$ and ${f}_{A}$ into the third equation of statics gives
$$\mathrm{mg}\left(\frac{L}{2}\mathrm{cos}\theta \right)\frac{\mu \mathrm{mg}}{1+{\mu}^{2}}(L\mathrm{sin}\theta )\frac{{\mu}^{2}\mathrm{mg}}{1+{\mu}^{2}}(L\mathrm{cos}\theta )=0$$which can be rearranged to
$$(\frac{1}{2}\frac{{\mu}^{2}}{1+{\mu}^{2}})\mathrm{cos}\theta =\frac{\mu}{1+{\mu}^{2}}\phantom{\rule{thinmathspace}{0ex}}\mathrm{sin}\theta $$This can be simplified in the following steps,
$$\frac{1+{\mu}^{2}2{\mu}^{2}}{2(1+{\mu}^{2})}\mathrm{cos}\theta =\frac{\mu}{1+{\mu}^{2}}\phantom{\rule{thinmathspace}{0ex}}\mathrm{sin}\theta $$ $$\frac{1{\mu}^{2}}{2}\mathrm{cos}\theta =\mu \mathrm{sin}\theta $$to
$$\mathrm{tan}\theta =\frac{1{\mu}^{2}}{2\mu}$$So if we’re given $\mu $, the minimum angle for which the ladder won’t slip is
$$\theta ={\mathrm{tan}}^{1}\left(\frac{1{\mu}^{2}}{2\mu}\right)$$If $\mu >1$, this gives us a negative $\theta $, which is outside the range of ladder angles (0° to 90°) for which the statics equations were written. So a friction coefficient greater than one means the ladder will stay up at any angle.
If we’re given the ladder angle, the minumum friction coefficient to prevent slipping is
$$\mu =\sqrt{1+{\mathrm{tan}}^{2}\theta}\mathrm{tan}\theta $$This one of the two solutions to a quadratic equation. The other gives a negative friction coefficient, which we don’t care about. As the ladder angle approaches 0°, the minimum friction coefficient approaches one. This is consistent with the finding above that a friction coefficient above one means the ladder will stay up at any angle.
Note that our claims that these are the minimum angle and friction coefficient don’t come out of the algebra, they come out of Item 3 of our physical reasoning.
By the way, just because we went through this solution using physical reasoning to simplify the math, that doesn’t mean we couldn’t get the same result using the inequalities and carrying out the algebra. I have a few pages in my notebook in which I did that (including a couple of mistakes along the way) just to prove to myself that I could.
Comparison
One last thing we can do is plot the results of the two problems and see how they compare. Here are the miniumum friction coefficients necessary to keep the ladder from slipping over the full range of angles.
There’s not much difference between the two problems when the ladder angle is high. That’s because the ladder isn’t pressing hard against the wall at those angles, so most of the work of keeping the ladder up in the second problem is being done by the floor/ladder friction. On the other hand, at low ladder angles, the difference between the two problems is huge; the wall/ladder friction in the second problem is doing a lot of work.
Simple problems like this aren’t especially important in the everyday working life of an engineer (unless you work for Werner), but the principles they teach are applicable across many disciplines. That’s why they appear in so many textbooks.

This is CharlesAugustin Coloumb, the guy who’s most famous for his law of electrical charges. In accordance with Stiger’s Law of Eponymy, the friction relationship that bears his name was written about by others before him. ↩
Again with man pages and BBEdit
December 21, 2023 at 3:50 PM by Dr. Drang
Julia Evans has been posting on Mastodon recently about the GNU Project’s insistence on documenting its commands through info pages instead of man pages and (the following may be biased by my own thoughts) how absolutely awful that is. The posts reminded me that although I wrote about how I open and read man pages in BBEdit last year, I never showed how I use references to related man pages as links. Time to change that.
So you don’t have to go through the terrible burden of reading an earlier blog post, here’s the source code of the command I use from the Terminal (or iTerm) to open man pages in a new BBEdit window:
bash:
1: #!/bin/bash
2:
3: # Interpret the arguments as command name and section. As with `man`,
4: # the section is optional and comes first if present.
5: if [[ $# lt 2 ]]; then
6: cmd=${1}
7: sec=''
8: else
9: cmd=${2}
10: sec=${1}
11: fi
12:
13: # Get the formatted man page, filter out backspaces and convert tabs
14: # to spaces, and open the text in a new BBEdit document. Set the title
15: # of the window and scroll to the top.
16: man $sec $cmd  col bx  bbedit viewtop clean t $cmd
This is slightly different from the code I originally posted. It incorporates options suggested by readers to the bbedit
command in the pipeline of Line 16:
viewtop
puts the cursor and the scroll position at the top of the document.clean
sets the state of the document to unmodified so you can close it without getting the “do you want to save this?” warning.t $cmd
sets the title of the document to the command name.
I’ve also changed the name of the command from bman
to bbman
to better fit the naming pattern set by bbedit
, bbfind
, and bbdiff
. So if I type
bbman ls
at the command line, a new BBEdit window will open^{1} with the text of the ls
man page. The bold characters that I’d see if I ran
man ls
don’t appear in the BBEdit window, but I’ve never gotten any value out of that limited sort of text formatting, so I don’t miss it.
Although most of what I do in a man page is search and read, sometimes I like to use the hints within the text to open a new, related man page. So I wrote an AppleScript (BBEdit has great AppleScript support) that uses the cursor postion or the selected text to open a new page. Here’s how it works:
Say I’m in SEE ALSO section of the ls
man page, and I want to open the chmod
man page that’s referred to there. I can either doubleclick to select chmod
or just singleclick to put the cursor within the word.
I then select chmod
man page.
Here’s the Man Page AppleScript:
1: use AppleScript version "2.4"  Yosemite (10.10) or later
2: use scripting additions
3:
4:  This script is expected to be run either with the command name selected (as if
5:  by doubleclicking) or with the cursor within the command name. The section
6:  (in parentheses) may be immediately after the command name.
7:
8:  Function for getting the man page section from between parenthesis.
9:  Input is the character position of the opening parenthesis (if present).
10:  Returns the section or an empty string.
11: on getSection(parenPos)
12: tell application "BBEdit"
13: if (character parenPos of front document as text) is "(" then
14: set secStart to parenPos + 1
15: set secEnd to find ")" searching in front document
16: set secEnd to (characterOffset of secEnd's found object)  1
17: return characters secStart through secEnd of front document as text
18: else
19: return ""
20: end if
21: end tell
22: end getSection
23:
24:  Start by selecting the word the cursor is in (via ⌥←, ⌥⇧→) if there isn't already a selection.
25: tell application "BBEdit"
26: if length of selection is 0 then
27: tell application "System Events"
28: key code 123 using option down
29: delay 0.125
30: key code 124 using {option down, shift down}
31: delay 0.125
32: end tell
33: end if
34: end tell
35:
36:  Set the command name according to the selection and the section according to
37:  whatever may be in parentheses immediately after the command name. This is
38:  in a new tell block to ensure that the selection has been updated by the
39:  previous tell block.
40: tell application "BBEdit"
41: set cmdName to selection as text
42: set parenPos to (characterOffset of selection) + (length of selection)
43: set manSection to my getSection(parenPos)
44: end tell
45:
46:  Get the man page and pipe it through col to delete backspaces and expand tabs. Then
47:  pipe that to bbedit with appropriate options. The clean option means the new
48:  document is treated as unmodified so it can be closed without a confirmation dialog.
49: set manCmd to "man " & manSection & " " & cmdName & "  col bx "
50: set manCmd to manCmd & " /usr/local/bin/bbedit viewtop clean t " & cmdName
51: do shell script manCmd
I think it’s pretty well commented. You can see that Lines 48–51 invoke the same shell pipeline used in the bbman
script. The main features that make this different from the shell script are:
 Handling the case in which no text is selected but the cursor is within the name of the command. Lines 25–34 check for this condition and select the enclosing word by simulating ⌥← followed by ⌥⇧→.
 Figuring out the man page section by looking inside the parentheses that may follow the name of the command. That’s handled by the
getSection
function in Lines 11–22, which uses the character position of the end of the command name to start its search.
Over the years, I’ve seen lots of ways to turn man pages into HTML, which would make the linking more natural. But they’ve all seemed more trouble than they’re worth. The trick of passing the man
output through col bx
to turn it into plain text is something I’ve always come back to, whether my preferred text editor has been BBEdit, TextMate, or (on Linux) NEdit.

On my computer, BBEdit is always running. ↩
Holes in the Wolfram Knowledgebase
December 3, 2023 at 3:02 PM by Dr. Drang
Wolfram touts its Knowledgebase as “the world’s largest and broadest repository of computable knowledge” and “carefully curated expert knowledge directly derived from primary sources.” There’s certainly a lot in there, but there are some inexplicable holes that could be filled with little effort.
I used the Knowledgebase last year in my post about orbital curvature. Things like
Entity["Planet", "Earth"]["AverageOrbitDistance"]
and
Entity["Planet", "Earth"]["Mass"]
pulled information out of the Knowledgebase so I didn’t have to look it up outside of Mathematica and paste it into my code. Very convenient.
But I learned a few months ago that another part of the Knowledgebase was missing data, which could get in the way of other types of calculation. I was testing out the kind of state and countylevel information I could access, and my initial explorations focused on where I live: DuPage County, Illinois.
To be sure, the Knowledgebase has lots of info on DuPage County. It knows, for example, the area, the population, the per capita income, and the number of annual births and deaths. But it doesn’t know the county seat, which I would think is easier to determine and enter into the Knowledgebase than most of the other stuff—not to mention more stable than transient figures like population and income.
Broadening my exploration to all the counties in Illinois, I learned that of our 102 counties, Wolfram knew the capitals of all of them except DuPage and DeKalb counties. So this command
AdministrativeDivisionData[
Entity["AdministrativeDivision", {"CookCounty", "Illinois",
"UnitedStates"}], "CapitalName"]
returns
Chicago
as expected, while both of these commands,
AdministrativeDivisionData[
Entity["AdministrativeDivision", {"DuPageCounty", "Illinois",
"UnitedStates"}], "CapitalName"]
and
AdministrativeDivisionData[
Entity["AdministrativeDivision", {"DeKalbCounty", "Illinois",
"UnitedStates"}], "CapitalName"]
return
Missing["NotAvailable"]
This is not exactly obscure information, and there are reliable sources from which to get it. Here, for example is a map from the Illinois Blue Book, an official publication of the state.
As you (and the folks at Wolfram) can see, the DuPage and DeKalb county seats are Wheaton and Sycamore, respectively.
I sent an email to Wolfram about the missing county seat data and got a boilerplate reply saying their development team would review it. That was in August; the Knowledgebase still returns Missing["NotAvailable"]
.
Recently, I decided to look for missing county seats in every state. Here are all the counties—or administrative divisions that Wolfram treats like counties— that are missing their capitals in the Knowledgebase:
County w/o seat  State 

DeKalb County  Alabama 
Aleutians West  Alaska 
Bethel  Alaska 
Chugach  Alaska 
Copper River  Alaska 
Dillingham  Alaska 
HoonahAngoon  Alaska 
Nome  Alaska 
Prince of WalesHyder  Alaska 
Petersburg  Alaska 
Skagway  Alaska 
Southeast Fairbanks  Alaska 
Kusilvak Census Area  Alaska 
Wrangell  Alaska 
YukonKoyukuk  Alaska 
Mono County  California 
Sierra County  California 
Conejos County  Colorado 
Wakulla County  Florida 
Columbia County  Georgia 
Crawford County  Georgia 
DeKalb County  Georgia 
Echols County  Georgia 
Kalawao County  Hawaii 
Owyhee County  Idaho 
DeKalb County  Illinois 
DuPage County  Illinois 
DeKalb County  Indiana 
LaPorte County  Indiana 
Plaquemines Parish  Louisiana 
St. James Parish  Louisiana 
Keweenaw County  Michigan 
Lake of the Woods County  Minnesota 
DeSoto County  Mississippi 
Franklin County  Mississippi 
DeKalb County  Missouri 
McPherson County  Nebraska 
Esmeralda County  Nevada 
Eureka County  Nevada 
Lincoln County  Nevada 
Storey County  Nevada 
Burlington County  New Jersey 
Mora County  New Mexico 
Rio Arriba County  New Mexico 
Bronx County (The Bronx)  New York 
Broome County  New York 
Kings County (Brooklyn)  New York 
New York County (Manhattan)  New York 
Queens County (Queens)  New York 
Richmond County (Staten Island)  New York 
Camden County  North Carolina 
Currituck County  North Carolina 
Hyde County  North Carolina 
Dunn County  North Dakota 
Bristol County  Rhode Island 
Kent County  Rhode Island 
Buffalo County  South Dakota 
DeKalb County  Tennessee 
Borden County  Texas 
Glasscock County  Texas 
Kenedy County  Texas 
King County  Texas 
Loving County  Texas 
McMullen County  Texas 
Montague County  Texas 
Palo Pinto County  Texas 
Young County  Texas 
Rich County  Utah 
Alexandria (independent city)  Virginia 
Amelia County  Virginia 
Bath County  Virginia 
Bland County  Virginia 
Bristol (independent city)  Virginia 
Buckingham County  Virginia 
Buena Vista (independent city)  Virginia 
Charles City County  Virginia 
Charlottesville (independent city)  Virginia 
Chesapeake (independent city)  Virginia 
Colonial Heights (independent city)  Virginia 
Covington (independent city)  Virginia 
Cumberland County  Virginia 
Danville (independent city)  Virginia 
Dinwiddie County  Virginia 
Emporia (independent city)  Virginia 
Fairfax (independent city)  Virginia 
Falls Church (independent city)  Virginia 
Fluvanna County  Virginia 
Franklin (independent city)  Virginia 
Fredericksburg (independent city)  Virginia 
Galax (independent city)  Virginia 
Goochland County  Virginia 
Hampton (independent city)  Virginia 
Hanover County  Virginia 
Harrisonburg (independent city)  Virginia 
Hopewell (independent city)  Virginia 
Isle of Wight County  Virginia 
King and Queen County  Virginia 
King George County  Virginia 
King William County  Virginia 
Lancaster County  Virginia 
Lexington (independent city)  Virginia 
Lunenburg County  Virginia 
Lynchburg (independent city)  Virginia 
Manassas (independent city)  Virginia 
Manassas Park (independent city)  Virginia 
Martinsville (independent city)  Virginia 
Mathews County  Virginia 
Middlesex County  Virginia 
Nelson County  Virginia 
New Kent County  Virginia 
Newport News (independent city)  Virginia 
Norfolk (independent city)  Virginia 
Northumberland County  Virginia 
Norton (independent city)  Virginia 
Nottoway County  Virginia 
Petersburg (independent city)  Virginia 
Poquoson (independent city)  Virginia 
Portsmouth (independent city)  Virginia 
Powhatan County  Virginia 
Prince George County  Virginia 
Radford (independent city)  Virginia 
Richmond County  Virginia 
Roanoke County  Virginia 
Salem (independent city)  Virginia 
Stafford County  Virginia 
Staunton (independent city)  Virginia 
Suffolk (independent city)  Virginia 
Sussex County  Virginia 
Virginia Beach (independent city)  Virginia 
Waynesboro (independent city)  Virginia 
Williamsburg (independent city)  Virginia 
Winchester (independent city)  Virginia 
Quite a list. Now there are legitimate (or at least arguable) reasons some of these counties are missing their county seat:
 Some counties really don’t have a county seat. Kalawao County in Hawaii, for example.
 Some administrative districts in Alaska don’t have a capital. Alaska has boroughs rather than counties, and one of them, called the Unorganized Borough,^{1} is further subdivided into “census areas,” none of which have a capital.
 Virginia has, in addition to counties, “independent cities,” which appear to be at the same level as counties.^{2} While I think Wolfram would be justified in saying these independent cities are their own capitals, it’s decided to say the capitals are missing, which is a legitimate choice, too.
 Five counties in New York match up with the boroughs of New York City. Pretty hard to choose the capital of a portion of a city, so these counties also have missing capitals.
But most of the counties with missing county seats are like DuPage and DeKalb counties—regular counties with regular county seats that are just not included in the Knowledgebase, despite them being easy to look up and verify. There are fewer than 100 of them. I don’t know why they’re missing, but filling in missing values like this is a pretty standard data cleaning operation. And as I said earlier, this is pretty much a onetime operation; counties just don’t change seats very often.
I haven’t sent this list off to Wolfram. If the people on its development team can’t be bothered to clean the data in their own home state, how likely is it that they’ll fill in all the other states’ data? But they should.
Update 4 Dec 2023 11:45 AM
Chon Torres on Mastodon informed me that the two California counties, Mono and Sierra, do have county seats, but they’re unincorporated, and that might explain why they’re missing from the Knowledgebase. That’s a good explanation, but I would argue with Wolfram that it’s a poor reason for excluding a capital. A county seat should have county government offices—Chon mentioned that he’s been at the Sierra County Courthouse in Downieville—but I don’t see why it needs a municipal government.
Adding to the madness of Virginia government, Sam Davies told me that an independent city can also be the county seat of a county that it’s been carved out of. The example he gave was Charlottesville, which is both an independent city and the capital of Albermarle County. To me, a more disturbing example is Fairfax, which is an independent city but also the county seat of—yes, that’s right—Fairfax County.
Thanks to Chon and Sam for the local government expertise.

Which is apparently not actually a borough itself, despite its name. This is more than I wanted to know about Alaska’s government. ↩

As with the Unorganized Borough in Alaska, this is more than I wanted to know about Virginia’s government. ↩
Dates, triangles, and Python
December 1, 2023 at 12:31 PM by Dr. Drang
Tuesday morning, which was November 28, John D. Cook started a post with
The numbers in today’s date—11, 28, and 23—make up the sides of a triangle. This doesn’t always happen; the two smaller numbers have to add up to more than the larger number.
He went on to figure out the angles of the plane triangle with side lengths of 11, 28, and 23 and then extended his analysis to triangles on a sphere and a pseudosphere. But I got hung up on the quoted paragraph. Which days can and can’t be the sides of a triangle? And how does the number of such “triangle days” change from year to year?
So I wrote a little Python to answer these questions.
python:
1: from datetime import date, timedelta
2:
3: def isTriangleDay(dt):
4: "Can the the year, month, and day of the given date be the sides of a triangle?"
5: y = dt.year % 100
6: m = dt.month
7: d = dt.day
8: sides = sorted(int(x) for x in (y, m, d))
9: return sides[0] + sides[1] > sides[2]
10:
11: def allDays(y):
12: "Return a list of all days in the given year."
13: start = date(y, 1, 1)
14: end = date(y, 12, 31)
15: numDays = (end  start).days + 1
16: return [ start + timedelta(days=n) for n in range(numDays) ]
17:
18: def triangleDays(y):
19: "Return a list of all the triangle days in the given year."
20: return [x for x in allDays(y) if isTriangleDay(x) ]
isTriangleDay
is a Boolean function that implements the test Cook described for a datetime.date
object. Note that Line 5 extracts just the last two digits of the year, which is what Cook intends. You could, I suppose, change Line 9 to
python:
9: return sides[0] + sides[1] >= sides[2]
if you want to accept degenerate triangles, where the three sides collapse onto a single line. I don’t.
The allDays
function uses a list comprehension to return a list of all the days in a given year, and triangleDays
calls isTriangleDay
to filter the results of allDays
down to just triangle days. I think both of these functions are selfexplanatory.
With these functions defined, I got all the triangle days for 2023 via
python:
print('\n'.join(x.strftime('%Y%m%d') for x in triangleDays(2023)))
which returned this list of dates (after reshaping into four columns):
20230123 20230627 20230919 20231117
20230222 20230628 20230920 20231118
20230223 20230717 20230921 20231119
20230224 20230718 20230922 20231120
20230321 20230719 20230923 20231121
20230322 20230720 20230924 20231122
20230323 20230721 20230925 20231123
20230324 20230722 20230926 20231124
20230325 20230723 20230927 20231125
20230420 20230724 20230928 20231126
20230421 20230725 20230929 20231127
20230422 20230726 20230930 20231128
20230423 20230727 20231014 20231129
20230424 20230728 20231015 20231130
20230425 20230729 20231016 20231212
20230426 20230816 20231017 20231213
20230519 20230817 20231018 20231214
20230520 20230818 20231019 20231215
20230521 20230819 20231020 20231216
20230522 20230820 20231021 20231217
20230523 20230821 20231022 20231218
20230524 20230822 20231023 20231219
20230525 20230823 20231024 20231220
20230526 20230824 20231025 20231221
20230527 20230825 20231026 20231222
20230618 20230826 20231027 20231223
20230619 20230827 20231028 20231224
20230620 20230828 20231029 20231225
20230621 20230829 20231030 20231226
20230622 20230830 20231031 20231227
20230623 20230915 20231113 20231228
20230624 20230916 20231114 20231229
20230625 20230917 20231115 20231230
20230626 20230918 20231116 20231231
That’s 136 triangle days for this year. To see how this count changes from year to year, I ran
python:
for y in range(2000, 2051):
print(f'{y} {len(triangleDays(y)):3d}')
which returned
2000 0
2001 12
2002 34
2003 54
2004 72
2005 88
2006 102
2007 114
2008 124
2009 132
2010 138
2011 142
2012 144
2013 144
2014 144
2015 144
2016 144
2017 144
2018 144
2019 144
2020 144
2021 142
2022 140
2023 136
2024 132
2025 127
2026 120
2027 113
2028 104
2029 93
2030 82
2031 72
2032 61
2033 51
2034 41
2035 33
2036 25
2037 19
2038 13
2039 8
2040 5
2041 2
2042 1
2043 0
2044 0
2045 0
2046 0
2047 0
2048 0
2049 0
2050 0
I knew there was no point in checking on years later in the century—it was obvious that every year after 2042 would have no triangle days. As you can see, the 2010s were the peak decade for triangle days. We’re now in the early stages of a 20year decline.
After doing this, I looked back at my code and decided that most serious Python programmers wouldn’t have done it the way I did. Instead of functions that returned lists, they would build allDays
and triangleDays
as iterators.^{1} Not because there’s any need to save space—the space used by 366 datetime.date
objects is hardly even noticeable—but because that’s more the current style.
So to make myself feel more like a real Pythonista, I rewrote the code like this:
python:
1: from datetime import date, timedelta
2:
3: def isTriangleDay(dt):
4: "Can the the year, month, and day of the given date be the sides of a triangle?"
5: y = dt.year % 100
6: m = dt.month
7: d = dt.day
8: sides = sorted(int(x) for x in (y, m, d))
9: return sides[0] + sides[1] > sides[2]
10:
11: def allDays(y):
12: "Iterator for all days in the given year."
13: d = date(y, 1, 1)
14: end = date(y, 12, 31)
15: while d <= end:
16: yield d
17: d = d + timedelta(days=1)
18:
19: def triangleDays(y):
20: "Iterator for all the triangle days in the given year."
21: return filter(isTriangleDay, allDays(y))
isTriangleDay
is unchanged, but allDays
now works its way through the days of the year with a while
loop and the yield
statement, and triangleDays
uses the filter
function to iterate through just the triangle days.
Using these functions is basically the same as using the listbased versions, except that you can’t pass an iterator to len
. So determining the number of triangle days over a range of years can be done by either by converting the iterator to a list before passing it to len
,
python:
for y in range(2000, 2051):
print(f'{y} {len(list(triangleDays(y))):3d}')
or by using the sum
command with an argument that produces a one for each element of triangleDays
,
python:
for y in range(2000, 2051):
print(f'{y} {sum(1 for x in triangleDays(y))}')
The former sort of defeats the purpose of using an iterator, so I guess it’s better practice to use the latter, even though I find it weird looking.
It may well be that my perception of “real” Python programmers is wrong and they wouldn’t bother with yield
and filter
in such a piddly little problem as this. But at least I got some practice with them.

A confession: I find it hard to distinguish between between the proper use of the terms generator and iterator. My sense is that generators provide a way of creating iterators. So once the function is written, do you have a generator, an iterator, or both? ↩