Friday, November 20, 2015
Brute-force Sudoku solver in JavaScript
Thursday, February 12, 2015
0hn0.com solver
Below is a solver for the puzzles at 0hn0.com. Using it I calculated that an empty 2x2 puzzle can be solved in 10 different ways, an empty 3x3 puzzle can be solved in 250 different ways, and an empty 4x4 puzzle can be solved in in 22946 different ways.
% uses clingo ASP solver from http://potassco.sourceforge.net/
% USAGE: clingo thisfile.txt
% The solution is in the squarecolor(Y,X,blue;red) terms in the output.
% problem specification; example 8x8 puzzle from 0hn0.com:
% - * 6 - 5 - - -
% - * - * - - 5 -
% - - - - - 4 8 -
% 4 - * - * - - -
% - - 5 - * 2 - -
% - - - - - 7 - 3
% 3 - - 7 6 - - -
% begin problem description in ASP
% num(Y,X,N) means the number in square (Y,X) is N
% (squares with numbers are all initially blue.)
% red(Y,X) means square (Y,X) is initially red.
#const ysize=8.
#const xsize=8.
num(1,1,1).
num(1,2,1).
num(1,5,2).
num(1,8,1).
red(2,2).
num(2,3,6).
num(2,5,5).
red(3,2).
red(3,4).
num(3,7,5).
num(4,6,4).
num(4,7,8).
num(5,1,4).
red(5,3).
red(5,5).
num(6,3,5).
red(6,5).
num(6,6,2).
num(7,6,7).
num(7,8,3).
num(8,1,3).
num(8,4,7).
num(8,5,6).
% end ASP problem description
% nothing below this line needs to be edited
% list the valid colors and directions
color(red;blue).
dir(up;down;left;right).
% squares with numbers are blue
squarecolor(Y,X,blue) :- num(Y,X).
% squares specified as red in the initial problem are red in the solution
squarecolor(Y,X,red) :- red(Y,X).
% num(Y,X) :- square(Y,X) was assigned a number in the initial problem
num(Y,X) :- num(Y,X,C).
% choose a color for each initially-empty square
1 { squarecolor(Y,X,C) : color(C) } 1 :- square(Y,X), not num(Y,X), not red(Y,X).
% dir(Y,X,D,YD,XD) = (YD,XD) is 1 square in direction D from (Y,X)
% square 0,0 is used to represent an invalid square, e.g. going up
% from a square in the top row.
row(1..ysize).
col(1..xsize).
square(Y,X) :- row(Y), col(X).
dirok(Y,X, up, Y-1,X) :- square(Y,X).
dirok(Y,X, down, Y+1,X) :- square(Y,X).
dirok(Y,X, left, Y,X-1) :- square(Y,X).
dirok(Y,X, right, Y,X+1) :- square(Y,X).
dir(Y,X,D, 0,0) :- square(Y,X), dir(D), dirok(Y,X,D,YD,XD), not square(YD,XD).
dir(Y,X,D, YD,XD) :- square(Y,X), dir(D), dirok(Y,X,D,YD,XD), square(YD,XD).
% consider any invalid square (0,0) to be colored red.
squarecolor(0,0,red).
% seendir(Y,X,D,N) = N blue squares can be seen in direction D from square (Y,X)
seendir(Y,X,D,1+SD) :- squarecolor(Y,X,blue), dir(D), dir(Y,X,D,YD,XD), seendir(YD,XD,D,SD).
% red squares block vision, so nothing can be seen in any direction from a red square.
seendir(Y,X,D,0) :- squarecolor(Y,X,red), dir(D).
% seen(Y,X,N) = Blue square (Y,X) can see N blue discs in all directions.
seen(Y,X,U+D+L+R-4) :- square(Y,X), squarecolor(Y,X,blue), seendir(Y,X,up,U),
seendir(Y,X,down,D), seendir(Y,X,left,L), seendir(Y,X,right,R).
% each blue square must see at least 1 other blue square (can't see only 0)
:- squarecolor(Y,X,blue), seen(Y,X,0).
% every blue square must see a number of neighbors equal to the number written on it.
:- num(Y,X,C), seen(Y,X,D), C != D.
Saturday, January 17, 2015
Counting steno strokes
How many different strokes are possible on a steno keyboard, using the conventional fingering?
Background
The world's fastest typists can type more than 200 words per minute, and they all use steno keyboards. On these keyboards, it's usual to press more than one key at the same time. The act of pressing one or more keys simultaneously is called a stroke. Each stroke is automatically converted to text by a computer, using a digital dictionary. A particular stroke could represent a letter, a symbol, a word, a phrase, or even an instruction such as "capitalize the next word." To learn more about how steno works, see How court reporting is done from the U.S. Bureau of Labor, or watch Plover: Thought to Text at 240 WPM by Mirabai Knight.
Counting strokes
How many different strokes are possible? To answer the question, I'll assume that each finger operates independently, count the number of different places each finger can be during a stroke, and multiply those numbers together to get the answer. This way of counting includes an "empty stroke" in which every finger is off the keyboard, but this doesn't count as stroke, so we'll subtract 1 at the end to account for that.
Steno keyboards and fingers
A steno keyboard is arranged as in the diagram below. In any particular stroke, each finger can press zero or more keys. Except for the number bar, which we'll deal with below, the conventional fingering assigns each key to one finger. (Some unconventional fingering techniques like the Philadelphia Shift allow the same key to be pressed by different fingers, but I won't consider them here.)
For some reason, steno keyboards have two keys (both labelled '*') in the 5th column, but in terms of the text that comes out on the screen, it never matters which one of those two is pressed. So in this note, I'll consider the 5th column to have only one key.
The diagram shows the 23 keys of a steno keyboard, with an oval for each finger. A solid red disc in the middle of a key represents the possibility that a finger is pressing only that key. A solid red disc between two keys indicates that a finger can press both keys at once. The right pinky can press 4 keys at once; this is indicated by a solid red disc in the middle of those 4 keys. There is also an empty circle for each finger to represent the possibility that the finger isn't pressing a key.
Counting the number bar and the left pinky
The long key at the top of the steno keyboard is called the number bar. For the purposes of counting steno strokes, it doesn't matter which finger presses it. To simplify the counting, and without sacrificing accuracy, we'll assume that if the number bar is pressed, it's pressed by the left pinky. How do we know that it's always possible for the left pinky to press the number bar? Not counting the number bar, the left pinky can be doing one of 2 things: either pressing the key in the first column, or doing nothing. No matter which of those it's doing, it could also be pressing the number key at the same time.
Counting the other fingers
Moving on, the left 3rd finger can be doing one of 4 things: (1) pressing the top key in the 2nd column, (2) pressing both keys in the 2nd column, (3) pressing the bottom key in the 2nd column, or (4) not pressing any key. Continuing on like this, we get to the right 1st finger, which can be doing any of 8 things, including pressing the key in the 5th column in various combinations with the top and bottom keys in the 6th column, or not pressing any key. Finally, we get to the right pinky, which can be in any of 10 possible places during a stroke, and the two thumbs, which can each be in any of 4 places.
And the answer is...
To count the number of possible strokes, multiply the number of possibilities for each individual finger together, and subtract 1 for the "empty stroke" when no keys are pressed: 4•4•4•4•8•4•4•10•4•4 - 1. That equals 5242879, or around 5.25 million.
Or is it?
A Google search for ["5242879" steno OR stenotype] turns up no results. Did I made a mistake somewhere? Let me know.
Wednesday, November 19, 2014
Ripple Effect
Here's a solver for Nikoli's Ripple Effect puzzle.
% Usage: clingo < rippleffect.lp
%
% Download clingo 4 from http://sourceforge.net/projects/potassco/files/clingo/
% The example puzzle used is from http://en.wikipedia.org/w/index.php?title=Ripple_Effect_(puzzle)&oldid=598393777
% +---+---+---+---+---+---+---+---+---+
% | | | 2 |
% + +---+---+ +---+---+---+---+ +
% | | | | | | | |
% + + +---+---+ + + + +---+
% | | | | | |
% +---+---+---+ +---+---+---+ +---+
% | | | | | |
% +---+---+---+---+ + +---+---+---+
% | | | | 5 | | |
% +---+ + + +---+---+ + + +
% | | | | | |
% + +---+---+---+---+---+---+ +---+
% | | | | | |
% +---+---+---+---+ + +---+---+ +
% | | | | |
% +---+---+---+---+---+---+ + + +
% | 4 | | |
% +---+---+---+---+---+---+---+---+---+
%%% inputs
#const size = 9.
% n(S,N) = square S has number N. (0 for blank)
n(11,0). n(12,0). n(13,0). n(14,0). n(15,0). n(16,0). n(17,0). n(18,0). n(19,2).
n(21,0). n(22,0). n(23,0). n(24,0). n(25,0). n(26,0). n(27,0). n(28,0). n(29,0).
n(31,0). n(32,0). n(33,0). n(34,0). n(35,0). n(36,0). n(37,0). n(38,0). n(39,0).
n(41,0). n(42,0). n(43,0). n(44,0). n(45,0). n(46,0). n(47,0). n(48,0). n(49,0).
n(51,0). n(52,0). n(53,0). n(54,0). n(55,5). n(56,0). n(57,0). n(58,0). n(59,0).
n(61,0). n(62,0). n(63,0). n(64,0). n(65,0). n(66,0). n(67,0). n(68,0). n(69,0).
n(71,0). n(72,0). n(73,0). n(74,0). n(75,0). n(76,0). n(77,0). n(78,0). n(79,0).
n(81,0). n(82,0). n(83,0). n(84,0). n(85,0). n(86,0). n(87,0). n(88,0). n(89,0).
n(91,4). n(92,0). n(93,0). n(94,0). n(95,0). n(96,0). n(97,0). n(98,0). n(99,0).
% r(S,R) = square S is in region R. Region symbols are arbitrary.
r(11,a). r(12,a). r(13,b). r(14,b). r(15,c). r(16,c). r(17,c). r(18,c). r(19,c).
r(21,a). r(22,d). r(23,e). r(24,b). r(25,b). r(26,f). r(27,g). r(28,g). r(29,c).
r(31,a). r(32,d). r(33,d). r(34,d). r(35,b). r(36,f). r(37,g). r(38,g). r(39,g).
r(41,h). r(42,h). r(43,h). r(44,d). r(45,i). r(46,i). r(47,i). r(48,g). r(49,j).
r(51,k). r(52,m). r(53,n). r(54,n). r(55,i). r(56,i). r(57,o). r(58,p). r(59,p).
r(61,q). r(62,m). r(63,n). r(64,n). r(65,o). r(66,o). r(67,o). r(68,p). r(69,p).
r(71,q). r(72,q). r(73,r). r(74,r). r(75,s). r(76,s). r(77,p). r(78,p). r(79,t).
r(81,v). r(82,v). r(83,v). r(84,v). r(85,s). r(86,s). r(87,u). r(88,t). r(89,t).
r(91,w). r(92,w). r(93,w). r(94,w). r(95,u). r(96,u). r(97,u). r(98,t). r(99,t).
%%% Solver
% sz(S,C) = the count of the number of squares in the region enclosing square S is C.
sz(S,C) :- C = #count { T : r(T, R) }, r(S,R).
% row(R) = R is a row index (1..size)
row(1..size).
% col(C) = C is a column index (1..size)
col(1..size).
% sq(Y,X,S) - the square at (Y,X) is S. Example: the square at (3,2) is 32.
sq(Y,X,10*Y+X) :- row(Y), col(X).
% nbetween(A,B,N) = A & B are in the same row or column, with N squares in between them.
nbetween(A,B,X2-X1-1) :- row(Y), col(X1), col(X2), X1 < X2, sq(Y,X1,A), sq(Y,X2,B).
nbetween(A,B,Y2-Y1-1) :- col(X), row(Y1), row(Y2), Y1 < Y2, sq(Y1,X,A), sq(Y2,X,B).
% m(S,N) :- in the solution, square S contains the number N.
m(S,N) :- n(S,N), N != 0.
% chose one value for each initially-empty square that does not exceed the size of its region.
1 { m(S,N) : N = 1..C } 1 :- n(S,0), sz(S,C).
% no duplicate values in the same region.
:- m(S1,N), m(S2,N), r(S1,R), r(S2,R), S1 < S2.
% Whenever the number N occurs twice in the same row or column, there must be at least N squares
% between those two occurrences
:- m(S1,N), m(S2,N), nbetween(S1,S2,M), M < N.
#show m/2.
And here's an encoding for the neat puzzle with no numeric clues at http://puzzleparasite.blogspot.com/2011/12/puzzle-71-ripple-effect.html:
% size of board #const size = 8. % n(S,N) = square S has number N. (0 for blank) n(11,0). n(12,0). n(13,0). n(14,0). n(15,0). n(16,0). n(17,0). n(18,0). n(21,0). n(22,0). n(23,0). n(24,0). n(25,0). n(26,0). n(27,0). n(28,0). n(31,0). n(32,0). n(33,0). n(34,0). n(35,0). n(36,0). n(37,0). n(38,0). n(41,0). n(42,0). n(43,0). n(44,0). n(45,0). n(46,0). n(47,0). n(48,0). n(51,0). n(52,0). n(53,0). n(54,0). n(55,0). n(56,0). n(57,0). n(58,0). n(61,0). n(62,0). n(63,0). n(64,0). n(65,0). n(66,0). n(67,0). n(68,0). n(71,0). n(72,0). n(73,0). n(74,0). n(75,0). n(76,0). n(77,0). n(78,0). n(81,0). n(82,0). n(83,0). n(84,0). n(85,0). n(86,0). n(87,0). n(88,0). % r(S,R) = square S is in region R. Region symbols are arbitrary. r(11,a). r(12,a). r(13,b). r(14,c). r(15,c). r(16,c). r(17,d). r(18,d). r(21,a). r(22,b). r(23,b). r(24,e). r(25,f). r(26,c). r(27,d). r(28,d). r(31,b). r(32,b). r(33,e). r(34,e). r(35,f). r(36,g). r(37,h). r(38,h). r(41,i). r(42,i). r(43,e). r(44,k). r(45,k). r(46,g). r(47,g). r(48,j). r(51,m). r(52,n). r(53,n). r(54,k). r(55,k). r(56,o). r(57,g). r(58,j). r(61,m). r(62,p). r(63,q). r(64,q). r(65,o). r(66,o). r(67,j). r(68,j). r(71,m). r(72,p). r(73,p). r(74,q). r(75,o). r(76,r). r(77,s). r(78,j). r(81,m). r(82,m). r(83,p). r(84,p). r(85,r). r(86,r). r(87,r). r(88,r).
0hh1
Here's a solver for the puzzles at 0hh1.com.
% USAGE: clingo -n 0 <thisfile.txt # show all solutions to puzzle
%
% Download from: http://sourceforge.net/projects/potassco/files/clingo/3.0.5/
% Two colors: 1 = red, 2 = blue. (0 = empty)
color(1). color(2).
#const size = 6.
% coord(Y,X) = Y,X is a valid coordinate of a cell in the puzzle.
coord(0..size-1,0..size-1).
% p(Y,X,C) = cell [Y,X] is color C in the original puzzle.
%- X X - - -
%O - - - - -
%- - - - - -
%- - - - O -
%- - - - - O
%- X - - - -
color(1). color(2).
#const size = 6.
% coord(Y,X) = Y,X is a valid coordinate of a cell in the puzzle.
coord(0..size-1,0..size-1).
% p(Y,X,C) = cell [Y,X] is color C in the original puzzle.
%- X X - - -
%O - - - - -
%- - - - - -
%- - - - O -
%- - - - - O
%- X - - - -
p(0,0,2). p(0,1,1). p(0,2,0). p(0,3,0). p(0,4,1). p(0,5,2).
p(1,0,1). p(1,1,0). p(1,2,0). p(1,3,2). p(1,4,0). p(1,5,0).
p(2,0,0). p(2,1,2). p(2,2,1). p(2,3,1). p(2,4,2). p(2,5,0).
p(3,0,0). p(3,1,0). p(3,2,2). p(3,3,0). p(3,4,0). p(3,5,0).
p(4,0,1). p(4,1,0). p(4,2,2). p(4,3,1). p(4,4,2). p(4,5,0).
p(5,0,1). p(5,1,2). p(5,2,0). p(5,3,0). p(5,4,2). p(5,5,0).
% q(Y,X,C) = cell[Y,X] is color C in the solved puzzle.
% The colors in the original puzzle must not change in the solution.
q(Y,X,C) :- p(Y,X,C); C != 0.
% Each empty square in the original puzzle must be colored red or blue in the solution.
1 { q(Y,X,C) : color(C) } 1 :- p(Y,X,0).
% f(Y,X) = cell(Y,X) is filled with some color.
f(Y,X) :- q(Y,X,C); C != 0.
% Solution cannot have empty squares.
:- coord(Y,X); not f(Y,X).
% Solution cannot have three adjacent cells of the same color horizontally.
:- q(Y,X,C); q(Y,X+1,C); q(Y,X+2,C).
% Solution cannot have three adjacent cells of the same color vertically.
:- q(Y,X,C); q(Y+1,X,C); q(Y+2,X,C).
% Exactly half of the cells in every row in the solution must be red.
:- coord(Y,_); {coord(Y,X) : q(Y,X,2) } != size/2.
% Exactly half of the cells in every column in the solution must be red.
:- coord(_,X); {coord(Y,X) : q(Y,X,1) } != size/2.
% diffrow(Y1, Y2) = rows Y1 and Y2 differ.
diffrow(Y1, Y2) :- q(Y1,X,C1); q(Y2,X,C2); C1 != C2; coord(Y1,_); coord(Y2,_); Y1 < Y2.
% diffcol(X1, X2) = columns X1 and X2 differ.
diffcol(X1, X2) :- q(Y,X1,C1); q(Y,X2,C2); C1 != C2; coord(_,X1); coord(_,X2); X1 < X2.
% Solution cannot have duplicate rows.
:- coord(Y1,_); coord(Y2,_); Y1 < Y2; not diffrow(Y1, Y2).
% Solution cannot have duplicate columns.
:- coord(_,X1); coord(_,X2); X1 < X2; not diffcol(X1, X2).
Monday, December 30, 2013
Mentally calculating the # of Tuesdays between 1700 and 2014
In this note I will talk about some mental tricks/techniques we can use for this, including:
- the First Sunday Doomsday Algorithm for calculating the day of the week for a particular date,
- the mnemonic major system (a.k.a. "pseudonumerology") for remembering intermediate results of calculations,
- the method of adding the complement of a number rather than subtracting,
- the method of casting out 11s for error checking, and
- the Pohlman-Mass fast test for divisibility by 7.
Let's begin. Overall, there are 2014 - 1700 = 314 years from 1700 to 2014. This works out to 365·314 + L days, where L is the number of leap years in that period.
The first step is to multiply 365·314. We can simplify this with some basic algebra, by noting that (a+b)(a+c) = a(a + b + c) + bc. Letting a = 300, b = 65, and c = 14, we have: 365·314 = (300 + 65)(300 + 14) = 300(300 + 65 + 14) + (65·14)
= 300·379 + 65·14.
300·379 isn't too bad to do in your head; it's 900 + 210 + 27 = 1137, and then adding two zeros gives 113700. We will need to remember this number for later. Using the mnemonic major system we can remember the first four digits 1137 as "toot mic" (picture someone blowing a horn into a microphone).
Now for 65·14. By writing this as 66·14 - 14, we can use the algebra from above and set a = (66+14)/2 = 40, b = 26, and c = -26, which gives us 66·14 = (40 + 26)(40 - 26) = 402 - 262, a difference of two squares. There are many tricks for doing squares. One is that ab^2 = 100a2 + 10·2ab + b2, so 262 = 100·4 + 10·24 + 36 = 400 + 240 + 36 = 676, so 402 - 262 works out to 1600-676. It is often simpler to calculate x - y by converting it to x + ~y, where ~y is the "second complement" of y. Here the second complement of 676 is 324 - 1000, so we have 1600 - 676 = 1600 + (324 - 1000) = 600 + 324 = 924. Finally we subtract 14 to get 65·14 = 910, which we can remember as "pots".
Now we need to add the last two steps, "toot mic" (113700) and "pots"(910); the sum is 114610, which we can remember as "toot reach toes" (a trumpeter playing a note with the trumpet reaching down to his toes).
We can check the arithmetic by casting out 11s. To do this we first calculate the value of each number mod 11:
365 = 5 + 3 - 6 = 2 (mod 11)
314 = 4 + 3 - 1 = 6 (mod 11)
114610 = 0 + 6 + 1 - (1 + 4 + 1) = 7 - 6 = 1 (mod 11)
Then we re-do our original problem of 365·314 using the mod 11 values and make sure the answer is correct:
365·314 = 2·6 = 12 = 1 = 114610 (mod 11)
They are equal, so that checks out.
Now, how many leap years were there in that period? My rule for leap years is this:
If the last two digits of the year are 00, throw them away and keep only the first two digits. Otherwise, keep just the last two digits. Then the year is a leap year just when what remains is divisible by 4.We can use this rule to figure out that there are 24 leap years in each of the 1700s, 1800s, and 1900s, for a total of 72 leap years in 1700, 1701, 1702, ... 1999. Furthermore, 2000, 2004, 2008, and 2012 were leap years, so in total there were 76 leap years in the 314-year period from Jan 1, 1700 through Jan 1, 2014.
Our total so far is 114610, "toot reach toes". Adding on 76 days (1 for each leap year) yields 114686, "toot reach fish" (a trumpeter playing for some fishes in the water).
According to the First Sunday Doomsday Algorithm, Dec 31, 2014 was a Tuesday, so we need to be sure to include that day in the calculation. In order to make the total number of days equal to an integer number of weeks, we need to end on a Thursday, so we add 2 more days to end on Thursday, Jan 2, 2014. This yields a total of 114688 days, which we can remember as "toot reach fife" (a trumpeter playing up to a fife on a shelf).
If the above arithmetic is correct, 114688 will equal an integer number of weeks; that is, it will be divisible by 7. It's easy to test for divisibility by 2 or 5, but 7 is a bit trickier. Wikipedia gives a fast method called Pohlman-Mass test for divisibility by 7, which I haven't seen described anywhere else. According to this method, 7 divides 114688 just when 7 divides (114688 - 114114) = 574. And 7 divides 574 just when 7 divides 57 - 4·2 = 49. And since 7 divides 49, 7 also divides 114688, which is consistent with the previous arithmetic being correct.
Actually carrying out the division mentally, which is kind of a pain, we find that 114688/7 = 16384. It just so happens that this is 214, and so there are 214 Tuesdays between Friday, Jan 1, 1700 and Jan 1, 2014.
For more info on mental calculation, two good books are:
- Dead Reckoning: Calculating without Instruments, by Ronald Doerfler
- Secrets of Mental Math, by Arthur T. Benjamin and Michael Shermer
Saturday, November 9, 2013
How to interpret probability problems
In The Irksome Tuesday Boy Problem, Rob Eastaway complains of ambiguity in Gary Foshee's infamous puzzle, which asks: "I have two children, (at least) one of whom is a boy born on a Tuesday - what is the probability that both children are boys?"
I don't get the controversy over this. There's no ambiguity. These problems are meant to be thought of as repeated experiments, like surveys. We want to determine P(2 boys | two children at least one of which is a boy born on Tuesday). So this one becomes:
- Pick a family at random
- "Hello, sir/madam, do you by chance have exactly two children, at least one of which was a boy born on Tuesday?"
- If they answer "no", then the interview ends immediately.
- Otherwise, ask: "Do you have two boys?"
- If "yes", record this call as a "hit"
- if "no", record this call as a "miss"
After doing lots of these surveys, let h be the number of hits and let m be the number of misses. The desired probability is then h/(m+h).
If the answer depends on some reasonable assumption that is not explicitly stated in the problem, just calculate the answer based on that assumption and make it explicit it to your answer. Example: "The answer is 13/27, provided that the probability of a newborn being a boy is 1/2 and the probability of being born on Tuesday is 1/7."