20170513
*
In the very first chapter of the book Concrete Mathematics 2ed there is a discussion about the Tower of Hanoi. This post is a distillation of that discussion.
There are 3 rods, with 8 discs (with holes) resting on one rod; the discs are sorted in size like a pyramid, with the smallest disc on top. We want to move all discs to another rod, but with the following rules: (1) a move consists of moving a single disc onto a rod; (2) you may never place a bigger disc on top of a smaller one. A question arises — how many steps are required to move the entire tower of disks onto another rod?
First consider the simplest case, without any discs. Because there are no discs to move, we cannot make any moves, and so the number of steps required is 0. We can write this as
\[ S_0 = 0 \]
with \(S\) meaning the number of steps and the subscript representing the number of discs in the tower.
Now let’s consider how the problem scales. With 1 disc, the answer is a single step since the one disc is itself the entire tower. With 2 discs, the answer is three steps — one step to move the top (small) disc to another rod, one step to move the big disc to the destination rod, and lastly one step to move the small disc on top of the big disc. With 3 discs, the answer is seven steps — the insight here is that we treat the top two discs exactly the same as the previous problem; so we need 3 moves to move the top two to another rod, then one move to move the biggest disc to the destination rod, then again 3 moves to move the 2disc subtower to the destination rod.
The example with 3 discs is quite telling. We can use the insights gained there to set an upper bound to the number of steps required for the general case of \(n\) discs; if we take more steps than this upper bound, we would know that we made mistakes. For a tower of size \(n\), we require \(S_{n  1}\) steps to move all discs except the biggest one, then move the biggest disc, then move the subtower on top of that disc with (again) \(S_{n  1}\) steps. So the upper bound is
\[ \begin{equation} \label{eq:recurrence} S_n = \begin{cases} 0 & \text{if } n = 0 \\ 2 * (S_{n  1}) + 1 & \text{if } n > 0. \end{cases} \end{equation} \]
If that’s the upper bound, then is there a separate formula for the lower bound (optimal solution)? Nope! It’s because there must come a time in solving the puzzle where we move the biggest disc to the destination rod. To get to the biggest disc, we must have moved all discs on top of it to another rod (the subtower); and, after having moved the biggest disc, we must move this subtower back on top of that rod (back onto the biggest disc). Because of these constraints stemming the definition of the puzzle itself, we know that for \(n\) > 0 we must take at least \(2 * (S_{n  1}) + 1\) steps.
The upper and lower bounds agree in their formulation, and this formulation (Equation \(\ref{eq:recurrence}\)) is our recurrence. In mathematics, a recurrence relation is basically a recursivelydefined equation, where a base case in the recurrence defines the starting point. In Equation \(\ref{eq:recurrence}\), the base case is \(n = 0\); for \(n > 0\), we define the number of steps required in a recursive manner.
In our discussion of finding the upper and lower bounds, there were two key concepts — the need to move the biggest disc, and the need to move the subtower twice (before and after moving the biggest disc). Our recurrence clearly agrees with these two concepts. The “\(+ 1\)” in the nonbase case is the step of moving the biggest disc, whereas the \(2 * (S_{n  1})\) is the number of steps required to move the subtower twice.
Recurrences are great, but they are painful to compute. For example, it’s not immediately clear what \(S_{11}\) or \(S_{54}\) evaluates to. It would be really nice if we could avoid defining \(S_n\) recursively.
And this is where math meets science. In the scientific method, we have to come up with a hypothesis and then test that hypothesis with one or more experiments. We can do the same thing here by trying to guess the solution to the recurrence.
For one thing, we know that \(S_n\) grows as \(n\) grows (it will never be the case that \(S_n\) somehow plateaus or decreases down the road). The more discs there are, the more work we have to do, right? So let’s look at small cases to see how the numbers grow, and see if there is a pattern to the growth rate of \(S_n\).
\(n\)  \(S_n\) 

0  0 
1  1 
2  3 
3  7 
4  15 
5  31 
6  63 
7  127 
8  255 
We don’t have to actually simulate the puzzle to derive these values; using the recurrence Equation \(\ref{eq:recurrence}\) we start off from the first row (the base case) and then calculate our way down, reusing \(S_n\) from the previous row as \(S_{n  1}\). ^{1}
Anyway, the values of \(S_n\) sure look familiar — especially if we use base 2.
\(n\)  binary(\(S_n\)) 

0  \(0_2\) 
1  \(1_2\) 
2  \(11_2\) 
3  \(111_2\) 
4  \(1111_2\) 
5  \(11111_2\) 
6  \(111111_2\) 
7  \(1111111_2\) 
8  \(11111111_2\) 
It looks like our recurrence simplifies to just
\[ \begin{equation} \label{eq:solution} S_n = 2^n  1 \quad \text{for } n \geq 0, \end{equation} \]
except it is no longer a recurrence as there is no need to define a base case. We’ll call it a solution to the recurrence.
Although the empirical evidence looks very good, we have not formally proved that the solution (Equation \(\ref{eq:solution}\)) holds for all \(n\). It’s one thing to say that something is true for all observed cases (scientific experiment), and quite another to say that something is true for all cases (mathematical proof).
Can we prove it? Yes! Fortunately for us, Equation \(\ref{eq:recurrence}\) lends itself to proof by induction. Induction requires you to first prove some number \(k_0\) as a starting point (the base case) using some proposition \(P\). Then you prove that \(P\) holds for \(k + 1\) (the next number); i.e., show that going from \(k\) to \(k + 1\) does not change \(P\). This is the inductive step. In this way, we prove the “totality” of \(P\) as it applies to all numbers in the range \([k_0, k_{m}]\) and we are done. ^{2}
Here we want to prove that Equation \(\ref{eq:solution}\) holds for all \(n\) (all natural numbers). ^{3} For this proof let’s rewrite Equation \(\ref{eq:solution}\) to use \(k\) instead of \(n\):
\[ \begin{equation} \label{eq:proposition} S_k = 2^k  1 \quad \text{for } k \geq 0. \end{equation} \]
Equation \(\ref{eq:proposition}\) is our proposition \(P\). The base case is easy enough to prove: \(S_0 = 0\) because there are no disks to move. For the inductive step, we use the nonbase part of our recurrence from Equation \(\ref{eq:recurrence}\) to get
\[ \begin{align} S_k &= 2 * (S_{k  1}) + 1 \label{eq:induct1} \end{align} \]
and rewrite it in terms of \(k + 1\):
\[ \begin{align} S_{k + 1} &= 2 * (S_{k}) + 1. \label{eq:induct2} \end{align} \]
Now the critical part: we replace \(S_k\) with Equation \(\ref{eq:proposition}\) (our proposition), because we assume that our proposition is true for all steps up to \(k\) (but not \(k + 1\), which is what we’re trying to prove):
\[ \begin{align} S_{k + 1} &= 2 * (2^k  1) + 1. \end{align} \]
In case you forgot algebra, \(2 * 2^k = 2^1 * 2^k = 2^{k + 1}\) and we can use this to simplify our equation.
\[ \begin{align} S_{k + 1} &= 2 * (2^k  1) + 1\\ &= [2 * (2^k  1)] + 1\\ &= [(2 * 2^k  2)] + 1\\ &= (2^{k + 1}  2) + 1\\ &= 2^{k + 1}  1 \label{eq:induct3}. \end{align} \]
And now we can see that Equation \(\ref{eq:induct3}\) (our “evolved” proposition \(P\), if you will) is the same as our solution (Equation \(\ref{eq:solution}\)), even though we increased \(k\) to \(k + 1\)! This is because simple substitution allows us to replace “\(k + 1\)” with “\(n\)”. We have completed our proof by induction. ^{4}
The book goes on to offer an alternate recurrence to Equation \(\ref{eq:recurrence}\), by adding 1 to both sides:
\[ \begin{align} (S_n) + 1 &= \begin{cases} 0 + 1 & \text{if } n = 0 \\ 2 * (S_{n  1}) + 1 + 1 & \text{if } n > 0 \\ \end{cases}\\ &= \begin{cases} 1 & \text{if } n = 0 \\ 2 * (S_{n  1}) + 2 & \text{if } n > 0. \label{eq:recurrence2} \end{cases} \end{align} \]
This recurrence is the same as the original, except that it adds 1 to the answer. Now we let \(W_n = (S_n) + 1\) and \(W_{n  1} = (S_{n  1}) + 1\) and rewrite everything in terms of \(W\):
\[ \begin{align} W_n &= \begin{cases} 1 & \text{if } n = 0 \\ 2 * (W_{n  1}) & \text{if } n > 0. \label{eq:recurrence3} \end{cases} \end{align} \]
Notice how the “\( + 2\)” in Equation \(\ref{eq:recurrence2}\) goes away, because the coefficient \(2\) in Equation \(\ref{eq:recurrence3}\) will multiply with the “\( + 1\)” from \(W_{n  1}\) to get it back. Using this alternate recurrence, it’s easy to see that the solution is just \(W_n = 2^n\), because \(W\) can only grow by multiplying \(2\) to itself! Hence
\[ \begin{align} W_n = (S_n) + 1 = 2^n \end{align} \]
and subtracting 1 from all sides gives us
\[ \begin{align} (W_n)  1 =S_n = 2^n  1. \end{align} \]
The lesson here is that if it is difficult to find the solution to a recurrence, we can use basic algebra rules to transform the recurrence to something more amenable. In this case, all it took was adding 1 to the original recurrence.
I thoroughly enjoyed figuring this stuff out because possibly for the first time in my life I used my programming experience (recurrence/recursion, memoization) to help myself understand mathematics — not the other way around. The other way around was not even enjoyable — calculating what i
was in some \(n\)th iteration of a for
loop never really excited me.
I hope this explanation helps you better understand the first few pages of Concrete Mathematics; I had to read that part three times over to really “get it” (never having learned what induction is). And henceforth, I will never look at a string of consecutive 1’s in binary the same way again. 😃
In computer science, this process of avoiding the recalculation of previously known values is called memoization and is useful in generating the first N values of a recursive algorithm in \(O(N)\) (linear) time.↩
Note that if \(k_0 = 0\), then \([k_0, k_{m}]\) is the set of all natural numbers (zero plus the positive integers).↩
There is no need to prove the recurrence (Equation \(\ref{eq:recurrence}\)) as we have already proved it in the process of deriving it.↩
In Concrete Mathematics 2 ed. p. 3 (where the book uses \(T_n\) instead of \(S_n\)), the proof is simply a oneliner: \[ T_n = 2(T_{n  1}) + 1 = 2(2^{n  1}  1) + 1 = 2^n  1. \] But I find it a bit too terse for my tastes.↩
20170414
*
The Fibonacci Sequence is defined as follows:
\[ \begin{align} \mathrm{F}_{0} = 0\\ \mathrm{F}_{1} = 1\\ \mathrm{F}_{n} = \mathrm{F}_{n  2} + \mathrm{F}_{n  1}. \end{align} \]
That is, each Fibonacci number \(\mathrm{F}_{n}\) is the sum of the previous two Fibonacci numbers, except the very first two numbers which are defined to be 0 and 1. ^{1}
From the definition above, it appears that computing \(\mathrm{F}_{n}\) requires one to always compute \(\mathrm{F}_{n  2}\) and \(\mathrm{F}_{n  1}\). This is false: enter the “doubling method”. ^{2} ^{3}
The doubling method uses a couple of mathematical formulas derived from matrix multiplication as it applies to calculating Fibonacci numbers; it can be seen as an improvement over the matrix multiplication method, although it does not use matrix multplication itself. The matrix multiplication method uses the following formula:
\[ \begin{equation} \begin{bmatrix} 1 & 1\\ 1 & 0 \end{bmatrix}^n = \begin{bmatrix} \mathrm{F}_{n + 1} & \mathrm{F}_{n}\\ \mathrm{F}_{n} & \mathrm{F}_{n  1} \end{bmatrix}. \end{equation} \]
This result is quite interesting in its own right; to find \(\mathrm{F}_{n}\) you only need to raise the matrix
\[ \begin{bmatrix} 1 & 1\\ 1 & 0 \end{bmatrix} \]
to the \(n\)th power. To be more precise, this method is matrix exponentiation. The only downside is that much of the answer is wasted — we don’t care about \(\mathrm{F}_{n  1}\), not to mention how \(\mathrm{F}_{n}\) is redundantly computed twice.
What if we could find \(\mathrm{F}_{n}\) not by multiplying or adding some numbers, but by multiplying and adding other Fibonacci terms? Of course, we’re not talking about adding \(\mathrm{F}_{n  2}\) and \(\mathrm{F}_{n  1}\) because that would be too slow. Let’s have a look at the matrix identity again (reversed for easier reading):
\[ \begin{equation} \begin{bmatrix} \mathrm{F}_{n + 1} & \mathrm{F}_{n}\\ \mathrm{F}_{n} & \mathrm{F}_{n  1} \end{bmatrix} = \begin{bmatrix} 1 & 1\\ 1 & 0 \end{bmatrix}^n. \end{equation} \]
If we substitute in \(2n\) for \(n\), we get
\[ \begin{align} \begin{bmatrix} \mathrm{F}_{2n + 1} & \mathrm{F}_{2n}\\ \mathrm{F}_{2n} & \mathrm{F}_{2n  1} \end{bmatrix} & = \begin{bmatrix} 1 & 1\\ 1 & 0 \end{bmatrix}^{2n} \\ & = \bigg(\begin{bmatrix} 1 & 1\\ 1 & 0 \end{bmatrix}^{n}\bigg)^2 \end{align} \]
and we can substitute in our matrix identity from above to rewrite this as
\[ \begin{align} & = \bigg(\begin{bmatrix} \mathrm{F}_{n + 1} & \mathrm{F}_{n}\\ \mathrm{F}_{n} & \mathrm{F}_{n  1} \end{bmatrix}\bigg)^2 \end{align} \]
and carry out the squaring to get
\[ \begin{align} & = \begin{bmatrix} {{\mathrm{F}_{n + 1}}^2 + {\mathrm{F}_{n}}^2} & {{\mathrm{F}_{n + 1}\mathrm{F}_{n}} + {\mathrm{F}_{n}\mathrm{F}_{n  1}}}\\ {{\mathrm{F}_{n}\mathrm{F}_{n + 1}} + {\mathrm{F}_{n  1}\mathrm{F}_{n}}} & {{\mathrm{F}_{n}}^2 + {\mathrm{F}_{n  1}}^2} \end{bmatrix}. \end{align} \]
The top right and bottom left terms are identical; we can also rewrite them to be a bit simpler.
\[ \begin{align} {{\mathrm{F}_{n + 1}\mathrm{F}_{n}} + {\mathrm{F}_{n}\mathrm{F}_{n  1}}} & = \mathrm{F}_{n}(\mathrm{F}_{n + 1} + \mathrm{F}_{n  1}) \\ & = \mathrm{F}_{n}[\mathrm{F}_{n + 1} + (\mathrm{F}_{n + 1}  \mathrm{F}_{n})] \\ & = \mathrm{F}_{n}(2\mathrm{F}_{n + 1}  \mathrm{F}_{n}). \end{align} \]
This simplication achieves an important task — it obviates \(\mathrm{F}_{n  1}\) by cleverly defining it as \(\mathrm{F}_{n + 1}  \mathrm{F}_{n}\). Putting everything together, whe have
\[ \begin{align} \begin{bmatrix} \mathrm{F}_{2n + 1} & \mathrm{F}_{2n}\\ \mathrm{F}_{2n} & \mathrm{F}_{2n  1} \end{bmatrix} & = \begin{bmatrix} {{\mathrm{F}_{n + 1}}^2 + {\mathrm{F}_{n}}^2} & {\mathrm{F}_{n}(2\mathrm{F}_{n + 1}  \mathrm{F}_{n})}\\ {\mathrm{F}_{n}(2\mathrm{F}_{n + 1}  \mathrm{F}_{n})} & {{\mathrm{F}_{n}}^2 + {\mathrm{F}_{n  1}}^2} \end{bmatrix} \end{align} \]
where the first row (or column) gives us two very useful identities
\[ \begin{align} \mathrm{F}_{2n} & = {\mathrm{F}_{n}(2\mathrm{F}_{n + 1}  \mathrm{F}_{n})} \\ \mathrm{F}_{2n + 1} & = {{\mathrm{F}_{n}}^2 + {\mathrm{F}_{n + 1}}^2}. \end{align} \]
As these identities form the heart of the doubling method, let’s call them the doubling identities.
And now we just need one more piece to formulate our doubling method; we need to borrow an idea from number theory. Given any positive integer \(n\), it is the same as either \(2m\) (even) or \(2m + 1\) (odd), where \(m = \lfloor\frac{n}{2}\rfloor\); for our purposes, let us call this property the “halving property”.
Whereas the doubling identities allow us to “double” our way into bigger numbers, the halving property allows us to halve our way down to smaller and smaller numbers. The marriage of these two concepts gives rise to the doubling method.
To compute the \(n\)th Fibonacci term we break \(n\) itself down into its halves (\(2m\)) recursively, until we go down to \(n = 0\). At this point we multiply our way back up using the doubling identities. Because halving and doubling by themselves always calculate \(\mathrm{F}_{2m}\), we have to manually return \(\mathrm{F}_{2m + 1}\) if our current sequence index number \(n\) is odd.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 

Line 12 is where we do the halving. We use the rightshift operator to do this. Lines 13 and 14 are our doubling identities (I use the leftshift operator here because it feels more natural to me). The ifcondition on line 15 returns \(\mathrm{F}_{2m + 1}\) if \(n\) was odd, and \(\mathrm{F}_{2m}\) otherwise.
For comparison, here is an iterative version. On the one hand it avoids Python’s recursion limit, but the downside is a small loss of elegance (we have to loop twice — first to build up the halving/doubling points, and again for the main loop).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 

I hope you enjoyed reading about this method of calculationg Fibonacci numbers as much as I enjoyed learning the math behind it. This algorithm can be sped up if it uses a faster multiplication algorithm as a
and b
get very large (e.g., Karatsuba multiplication). ^{4} Time complexity is \(\Theta(\log{n})\); it reminds me of the binary search algorithm, in how the problem space is halved repeatedly. Neat!
We can choose to define the first two terms as 1 and 1 instead, but this distinction is needlessly arbitrary.↩
There is actually a known formula for our purposes, where \[ \mathrm{F}_{n} = \frac{\varphi^n  (\varphi)^{n}}{2\varphi  1}\] where \(\varphi = \frac{1 + \sqrt{5}}{2} \approx 1.6180339887\cdots\) (the golden ratio). Unfortunately this requires arbitraryprecision floating point calculations.↩
For more discussion, see https://www.nayuki.io/page/fastfibonaccialgorithms.↩
Python already uses Karatsuba multiplication natively for large integers.↩
20170402
*
Yesterday I made a minimal working example of calling C from Haskell, where I call a simple C function to compute the greatest common denominator, or “GCD”. The Haskell portion only serves as a wrapper around the C function. This post is a brief look at the whole setup.
I used ghc
8.0.1, and gcc
5.4.0. ^{1}
20170402callingcfromhaskell
├── build.sh
├── c
│ ├── gcd.c
│ └── gcd.h
└── hs
├── ffi.hs
└── GCD.hs
2 directories, 5 files
1 

1 2 3 4 5 6 7 8 9 10 

1 2 3 4 5 6 7 8 9 10 

1 2 3 4 

1 2 3 4 5 6 7 8 9 

To compile the example, run the build.sh
script. Here is the expected output of the built executable:
$ ./hs/ffi
4
15
12
. The gcd()
C function is easy to work with because it is a pure function without side effects. You can run the ffi
binary against valgrind
to make sure that we are not leaking any memory (sample output below).
$ valgrind errorexitcode=1 leakcheck=yes ./hs/ffi
==14582== Memcheck, a memory error detector
==14582== Copyright (C) 20022015, and GNU GPL'd, by Julian Seward et al.
==14582== Using Valgrind3.12.0 and LibVEX; rerun with h for copyright info
==14582== Command: ./hs/ffi
==14582==
==14582== Warning: set address range perms: large range [0x4200000000, 0x14200100000) (noaccess)
4
15
12
==14582==
==14582== HEAP SUMMARY:
==14582== in use at exit: 0 bytes in 0 blocks
==14582== total heap usage: 48 allocs, 48 frees, 60,006 bytes allocated
==14582==
==14582== All heap blocks were freed  no leaks are possible
==14582==
==14582== For counts of detected and suppressed errors, rerun with: v
==14582== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Below are some things I tried, but could not get to work.
gcd.c
file by moving the function definition in gcd.c
to gcd.h
(and delete gcd.c
entirely). I compiled the object file with gcc c Wall Wextra Werror o gcd.o gcd.h
but then I got this error: $ ghc make ffi.hs ../c/gcd.o
[1 of 2] Compiling GCD ( GCD.hs, GCD.o )
[2 of 2] Compiling Main ( ffi.hs, ffi.o )
Linking ffi ...
../c/gcd.o: file not recognized: File format not recognized
collect2: error: ld returned 1 exit status
`cc' failed in phase `Linker'. (Exit code: 1)
GCD.hs
you can see the line foreign import ccall "gcd.h gcd"
. Instinctively I thought that the gcd.h
in "gcd.h gcd"
served as a kind of disambiguator, for where the gcd()
function came from. So then I defined another function named gcd()
in a different C header file (gcd_other.h
), compiled it separately, but got a “multple definition” error: $ ghc make ffi.hs ../c/gcd.o ../c/gcd_other.o
[1 of 2] Compiling GCD ( GCD.hs, GCD.o )
[2 of 2] Compiling Main ( ffi.hs, ffi.o )
Linking ffi ...
../c/gcd_other.o: In function `gcd':
gcd_other.c:(.text+0x0): multiple definition of `gcd'
../c/gcd.o:gcd.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
`cc' failed in phase `Linker'. (Exit code: 1)
The version of gcc should not matter at all – actually, any decent C compiler should work.↩
20161204
*
I just opensourced my dotfiles! The repo is probably one of the older Git repos out there and has over 2000 commits! The first commit was made in March 10, 2009, over 7 years ago. Mind you, even by that time I had accumulated a whole suite of configs, and I even had an exotic setup with XMonad 0.8.1!
Here are some takeaways:
It’s nice to have clean commit history — every line of code can answer why it exists. You are doing yourself a disservice (in the long run) if you have overly terse commit messages. Generally speaking, be as verbose as you can be, but within reason. Keeping the first line of the commit message down to less than 80 characters goes a long way in setting the tone for any auxiliary paragraphs that follow.
I am quite proud of the richness of my commit messages. Pretty much everything makes sense and I don’t have to use Google to reason about my choices.
I’ve used a stupidly simple build system of creating symlinks to “install” my configuration — all with the help of a single Makefile
. It’s not very flexible, and to be honest my Shell sklls are much better than what they used to be such that I could replace this setup with a script. But alas, the need for such a change has not been serious enough to warrant it.
Moreover, having a simple “build” system ensures robustness; the more I get older, the more I value systems that have a long “bitrot halflife”. I admire Knuth’s TEX system for this very same reason. And this is the same reason why I will probably not use anything higherlevel than a shell script for the job.
Every longliving code repository ends up collecting clutter over the years. It’s important to delete such code (and of course any corresponding comments), to keep the codebase nimble. Ultimately, the less code you have to maintain, the better.
Software evolves. Always be on the lookout for better software, and new ways to configure them! Leverage the power of open source (free upgrades!) and make it work for you.
If not, you should definitely consider it — what have you got to lose? Keeping a configuration repo (distributed across home computers) is probably the best way for you to learn how to use a distributed source control system; indeed it was how I learned to use Git properly.
Happy hacking!
]]>20161203
*
I’ve been using Vim (and now, Emacs with Evil mode) for years — and still, every once in a while I get a pleasant surprise. Today I learned that you can replay macros from Visual Line mode! So you don’t always have to record something like j0
at the end of your macro to get down to the next line. I.e., after recording your macro for just 1 line, select other lines that you want to replay the macro against with Visual Line mode (V
). To replay, do
:'<,'>g/^/norm @q
(assuming that you recorded your macro into the q
register with qq...q
).
Thanks to Chris McCord for the tip (seek to about 3:20 in the video).
]]>20161030
*
For years, whenever I wanted to interact with the outer shell environment from Latex, I would use one of two methods:
\input{foo}
.But I learned recently that it can be done much more simply, if you are willing to use Luatex!
Let’s assume that you keep your Latex document in source control and want to inject the Git hash into the document. First, define a new command called \shell
.
% Call shell! See http://tex.stackexchange.com/a/114939/30920.
\newcommand\shell[1]{\directlua{
local handle, err = io.popen([[#1]])
if not handle then
tex.print(err)
os.exit(1)
end
local result = handle:read("*a")
handle:close()
tex.print(result)
}}
Then use it like this:
\shell{TZ='America/Los_Angeles' date}
\shell{git describe always}%
\shell{(( $(git nopager diff 2>/dev/null  wc l) + $(git nopager diff cached 2>/dev/null  wc l) > 0 )) && echo '*'}
. I then use lualatex shellescape foo.tex
to compile it. This is actual code from a Luatex document of mine.
I am not sure which shell program gets invoked, but for most things it should not matter much.
Now you know how to shell out from Latex!
Happy hacking!
]]>20161001
*
I’ve spent quite some time on keyboards in my previous posts, and this post is no different. After several months of tweaking my original layout, I have more or less settled on the final design. You can get the sources at my repo here.
Is for the name “ZQ”, it was originally chosen from the way these keys were arranged; the location of these keys have since changed, but the name has stuck.
□ □ □ □ □ □ □ □ □ □ □ □
! # * □ □ □ □ / ~ $
□ □ ; y o p v 1 1 m f t r _ □ □
□ 2 a i e u w 3 2 h j k l n 3 □ < Home row
4 z x q ' " b d g c s 4
5 6 7 8 9 5 , 6 7 8 < Thumb row
Leftside legend
1) PageUp
2) Escape
3) Tab
4) Shift
5) Insert
6) Super (Windows key)
7) Space
8) Caps Lock (remapped with xmodmap to Hyper key)
9) Control
Rightside legend
1) PageDown
2) Return
3) Delete
4) Shift
5) FN2
6) FN
7) Alt
8) Right Alt (aka "AltGr" for US International Layout)
□ □ □ □ □ □ □ □ □ □ □ □
□ □ □ □ □ □ □ □ □ □
□ □ ? 7 8 9 = a a ` \ . : & □ □
□ □  4 5 6 + ^ @ b { ( ) } □ □ < Home row
□ 0 1 2 3 %  [ < > ] □
□ □ b □ □ □ □ □ □ □ < Thumb row
Leftside legend
a) Home
b) Space
Rightside legend
a) End
b) Backspace
□ □ □ □ □ □ □ □ □ □ □ □
□ □ □ □ □ □ □ □ □ □
□ □ l g h i □ □ □ □ □ □ □ □ □ □
□ □ k d e f □ □ □ a b c d □ □ □ < Home row
□ j a b c □ □ □ □ □ □ □
□ □ □ □ □ □ □ □ □ □ < Thumb row
Leftside legend
a) F1
b) F2
c) F3
d) F4
e) F5
f) F6
g) F7
h) F8
i) F9
j) F10
k) F11
l) F12
Rightside legend
a) Left Arrow
b) Down Arrow
c) Up Arrow
d) Right Arrow
I have not covered this topic before, but I think it is worth mentioning. On the underside of each keycap, there is a number; presumably for the row that the keycap is designed for. I’ve rearranged the default keycap placement to better reflect how I use my keys. The biggest change from the default Esrille setup is that I vertically flip the thumb row keys, so that they are “upsidedown”. I find this arrangement more comfortable for my thumbs.
n n n n n n n n n n n n
n n n n n n n n n n
n 3 3 3 3 3 3 n n 3 3 3 3 3 3 n
3 3 2 2 2 2 2 n n 2 2 2 2 2 3 3 < Home row
1 2 1 1 1 2 2 1 1 1 2 1
1 1 1 1 1 1 1 1 1 1 < Thumb row*
Legend
n) No number
1) Row 1
2) Row 2
3) Row 3
*The thumb row keycaps are flipped upsidedown for better comfort.
After some time, I realized that the end goal was to design a layout that was not more “optimal” in the sense of mechanical efficiency, but rather to design something more comfortable to use. I can readily say that even at this early stage, I tend to like this layout more over Qwerty because my hands stay put as I type. The only time I move my wrists from their default position is when I need to reach the six keys up top in the base layer (!#*/~$
).
It may turn out that this new layout does not really improve raw typing speed; but really I don’t care because homerow access to parentheses and the backspace key are too good to let go.
The design has changed quite a bit in these past few months. After some initial trials, I realized that the arrangement recommended by my program was not really optimized the way I wanted it to be. For one, the corpus I fed into the program was not very good because it didn’t realy reflect my realworld usecase; I use Vimstyle HJKL keys almost everywhere, and really to get a truly representative histogram of keypresses, I should have used a keylogger for some months to record my actual usage. As time was of the essence, I decided to just evolve the layout gradually, tweaking bits I found annoying.
One hurdle was simply trying to avoid using the same finger in succession. In the current ZQ layout, the right index finger is charged with six keys: MHBFJD. It took a lot of trial and error to arrive at this combination.
I also just kept the original Qwerty placement of the HJKL keys. The main reason is that I use these keys all the time, so much that they deserve their original homerow placement. And, actually they helped in reducing samefinger consecutiveness (J and K are rare letters in English).
Another point of concern was the interaction of some key combinations like YN and <Return>. It is common to type either Y or N and press <Return> immediately after, when dealing with interactive torminal programs. The same goes for some UNIXy combinations like ~/
for the home directory in the filesystem, or *
and /
for interactive search in vim(1) and less(1), respectvely. The current design of ZQ strives to make these combinations easy to type.
Lastly, I paid a great deal of attention for certain common letter combinations — in particular, “gh”, “ch”, “sh”, “th”, and “wh”. Because I decided to keep HJKL keys on the home row, and because H was assigned to the right index finger, I had to make sure that I place the GCSTW keys either on the left side of the keyboard (as I did with “W”), or place them for the other fingers. This effort alone resulted in dictating where most of the keys ended up.
After all that’s been said, time will tell if I truly do end up using this layout. I have a friend who uses Dvorak for work and Qwerty for gaming; perhaps I’ll end up in a similar boat.
]]>20160924
*
Almost exactly two years ago, I discussed what I called the Parking Lot Problem. Recently I discovered that it is a widelyknown problem, enough to be featured in the very first chapter of Pearls of Functional Algorithm Design (2010) by Richard Bird — where it is simply called “smallest free number”. In this post, I want to go over Bird’s explanations in more detail; my aim is to spare you the effort in deciphering his opaque writing style.
Bird presents two solutions — an imperative, arraybased solution and a functional solution based on divideandconquer.
Bird describes the problem as “computing the smallest natural number not in a given finite set X of natural numbers”. Here, natural numbers means the set of all positive integers and zero, or just [0..]
in Haskell.
I would like to add some further terminology. Let us think of the set X as xs
(a list of elements in X), and call the set of all free numbers as the free set. Using our original parking lot analogy, the infinite parking lot is the set of all natural numbers, X is the list of parked spots (occupied), and finally the free set is the list of all unoccupied (open) parking spots.
1 2 3 4 

The worst case of minfreeNaive is \(\Theta(n^2)\), because it translates into imperative pseudocode as follows:
# Algorithm P1
minfreeNaive(xs)
{
let freeSpots = array from 0 to infinity
let i = all natural numbers 0 to infinity
let j = i
let xs_max_idx = xs.length  1
for (i = 0;; i++) {
for (j = 0; j < xs_max_idx; j++) {
if (i == xs[j]) {
remove i from freeSpots
}
}
if (i > xs_max_idx) {
break
}
}
return freeSpots.first_one
}
. Now imagine if xs looks like [9,8,7,6,5,4,3,2,1,0]. Then the first iteration of the outer i forloop would check all 10 values in xs, until finally hitting the last value in xs, 0 to remove that 0 from candidates. Then the second iteration would check all values 9 through 2, until removing 1 from candidates. And so on, until it removed 9 as well. So, the total number of times that single if statement gets executed is
\[ 10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 = 55 \]
. The formula for the sum of all positive, consecutive integers 1 through N is
\[ \frac{n(n + 1)}{2} = \frac{n^2 + n}{2}. \]
In BigO notation, the above reduces to just \(n^2\) because of the first term \(n^2\) in \(n^2 + n\). ^{1} As a side note, the above equation has a colorful history in mathematics, anecdotally attributed to Gauss.
Bird says the following:
The key fact for both the arraybased and divide and conquer solutions is that not every number in the range [ 0 .. length xs ] can be in xs. Thus the smallest number not in xs is the smallest number not in filter (<= n) xs, where n = length xs.
. Let’s examine the first sentence. Consider length xs = 1
. That is, what if xs
is only 1 element big (only 1 car is parked in the lot)? Intuitively, it appears that we don’t need to perform millions and millions of checks. Since we know that there is only 1 car parked, we just need to consider if that car is in Parking Spot 0 (the first free spot, or PS0). If it is, then we can assign the next slot, PS1. Otherwise, we can assign PS0 itself. If there are 2 cars parked (length xs = 2
), in total we need only consider the first 2 spots, PS0, PS1 — if those are both taken, then the answer is PS2.
This leads us to the main theorem of this problem (let’s call it the Fullness Theorem):
For any n cars parked, we can consider the spots numbered
[0..(n1)]
; if all of those spots are full, we can assign spot n itself.
(This statement may seem elementary, but it plays a crucial role in the divideandconquer approach discussed later.) Now, since length [0..(n1)]
coincidentally happens to be just n, the total number of spots taken into consideration for this problem is n + 1 — parking spots [0..(n1)]
and spot n
itself. And so we can reduce the free set to just [0..(n1)] ++ [n]
, or the equivalent [0 .. length xs]
and ignore all other possible free spots. ^{2} To restate, our answer to the original problem statement lies somewhere in this range [0 .. length xs]
, which we will call reducedFrees.
Now let’s look at the second sentence. It describes the set filter (<n) xs
, which we will call reducedXs. ^{3} The set reducedXs is found by removing all elements in xs that are too big for our problem size of n + 1 spots — i.e., beyond the range in reducedFrees.
Using the insight gained above, we can restate the problem as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 

.
Bird says “[t]he function search takes an array of Booleans, converts the array into a list of Booleans and returns the length of the longest initial segment consisting of True entries. This number will be the position of the first False entry.” This is true, and we’ll soon see why this is the case.
In order to understand how minfreeArray1 works, let’s first examine a further simplification of the problem. Conceptually we are only interested in the very first group of consecutively parked cars (if it exists at all), because as soon as this first group of cars ends, we are at the lowestnumbered free parking spot. In binary, we can represent an empty spot as 0 and a parked car as 1. The set of parked cars in reducedXs might look something like this (using a .
for 0
):
111111.11.1.111.1.111.111.11.1......1.1.111.1
^^^^^^
. Although there are many groups of parked cars, we are only interested in the first group, denoted by the hat signs. Consider another example:
.111.1.111.11...
^^^
. In this there is the triplet of cars, but it starts after an empty spot at PS0. Lastly let’s consider
..........1..111111.111.1.1.111.1
^
; again, the first group of cars (in this case just 1 car) is preceded by an empty spot (actually, many such empty spots). In the last two examples, the answer is simply 0, for the very first spot PS0. For all other cases, the first group of cars starts from PS0, and extends some arbitrary number of spots, until “breaking” by an available spot. So there are two cases really as far as reducedXs is concerned:
The algorithm then is simply length $ takeWhile (==True) checklist
, where checklist
is a list of Boolean values with a 1:1 mapping of the parking spots, in order (with True
representing a parked car and False
representing an empty spot). If we’re in case 2) as above, then we get 0 because takeWhile
never grows. If we’re in case 1), takeWhile
keeps growing until the first empty spot; coincidentally, the length of takeWhile
’s return list happens to be the index of the next free spot, we can just use the size of the return list of takeWhile
asis.
And this is exactly what the search
function does in the algorithm Bird describes. elems
returns all the elements of an Array. takeWhile
grows a list so long as the given predicate evaluates to True; since we already have Booleans, we can just use id. All we need to give as an argument to search
is a Boolean list that is ordered from PS0 to PSn (the range of reducedXs). This conversion of a list of unordered natural numbers into a sorted list of Boolean values in the range covered by reducedXs is handled by checklist
.
Bird uses the library function Data.Array.accumArray
to populate checklist
. accumArray
takes a list of indexvalue pairs, and if there are multiple pairs with the same index, combines the values of those pairs using the accumulating function. A common use case of accumArray
is to use it to create a histogram of values, by using (+)
as the accumulating function (so that all values at a particular index are summed together). In the checklist
implementation by Bird, the accumulating function is ()
(logical OR function) to account for the possibility of duplicate numbers in xs
. E.g., if xs = [1, 2, 1]
, then the ordered pairs are [(0, False), (1, True), (2, True), (1, True)]
, and checklist
evaluates to [False, True, True]
, because the True
value in the two instances of (1, True)
are simply ORed together by ()
.
accumArray
to sort numbersBird mentions that you can use accumArray
to sort positive integers. The code is as follows:
import Data.Array (Array, accumArray)
countlist :: [Int] > Array Int Int
countlist xs = accumArray (+) 0 (0, n) (zip xs (repeat 1))
sort xs = concat [ replicate k x  (x, k) < assocs $ countlist xs ]
. (Bird defines sort
without the use of assocs
which gives a list of tuples of the form (index, elementatindex)
, but that is in error.) The way it works is, countlist
essentially builds a histogram of numbers we want to sort. So, given [0, 6, 2, 0, 0]
, we get [(0,3),(2,1),(6,1)]
. We then use replicate
in sort
to “unpack” each element of the histogram. Continuing with the example, (0,3)
becomes [0, 0, 0]
, (2,1)
becomes [2]
, and so on. Since the result looks like [[0,0,0],[2],[6]]
we have to concat
it to get [0,0,0,2,6]
, our sorted list.
It should be reiterated here that ultimately we want to have an ordered list of Booleans that preserves the occupied parking spot information in the original list of “taken” spots. The way in which checklist
performs the conversion of unordered numbers into a nice list of Booleans in the range [0..n]
is virtually identical in design to the algorithm described by Jon Bentley in the very first chapter of his book Programming Pearls (2nd Ed., 2000). There Bentley used a bitmap to represent a Boolean array because of strict memory requirements — but otherwise the spirit of the data structure remains the same.
Bird’s final arraybased algorithm uses the ST Monad to squeeze out some more performance of the checklist
function. Here is the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 

. The use of the ST monad here reduces memory overhead, and according to Bird it is the most efficient approach using an imperative style on top of arrays.
Ah, recursion. Bird describes the following divideandconquer algorithm as a faster alternative to accumArray
. ^{4}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 

The overall idea is that we can define the problem minimum of ([0..] \\ xs)
by dividing up xs
into 2 halves, and then look into the correct subpart for the solution. Notice that we are partitioning the xs
(soley the list of parked spots), and not the parking lot itself.
For example, we can divide up xs
into as
and bs
, where (as, bs) = partition (<b) xs
. (The partition
library function simply splits up a given list into 2 subsets, those that satisfy the given condition, and those that do not.) Deciding which partition to look at is simple: look in the upper partition if the lower partition (containing the smallernumbered parking spots) is full.
The line (n == 0) = a
merely means that, if the list of cars is empty, simply choose the lowest number (which is, by definition, a
). The line (m == b  a) = minfrom b (n m, bs)
chooses the bigger partition of the two partitions, on the condition (m == b  a)
. This condition asks whether the length of as
(the first partition) equal to the distance of b  a
— in other words, whether as
fills up the entire range [a..(b1)]
. If it does fill up the entire range, then this parking lot subsection is completely packed with cars, so there is no point in looking; we must look into the other partition ([b..]
) for the first empty spot. Otherwise, we look into the first partition.
The hard part here is choosing the value of b
(the pivot at which we decide to partition xs
). By definition, our partitions are as
and bs
, where (as, bs) = partition (<b) xs
.) There are two things we want:
as
and bs
, andas
.We want minimal size difference between as
and bs
because otherwise we might end up calling minfrom
many times; we want it so that whether we use as
or bs
(in whichever sequence), we deal with smaller and smaller lists of parked cars. The only way to do this is to divide the list of cars by half each time. This is where we get div n 2
. This is, more or less, the spirit of binary search.
The requirement of the second condition is more subtle — we want to avoid taking a zerolength partition for as
, because our main conditional m == b  a
relies on the fact that this distance, b  a
, is nonzero. This is because it must ask the question, “do the parking spots in the first partition fill up all spots in the range that it can cover?”, and this question loses its meaning if we give it an empty partition. Seen another way, the statement partition (<b) xs
, and the act of choosing those xs
that are b
or bigger if the first partition is completely full, is the recursive analogue of the Fullness Theorem. Whereas the Fullness Theorem did not really help much in the iterative arraybased solution, it plays a key role in this recursive solution, because it correctly describes how to partition xs
with minimum fuss. The phrase “otherwise assign spot n itself” in that Theorem translates to choosing the nonfull, bigger partition, because it starts with spot n — the only twist here is that instead of assigning spot n directly, we reassign ourselves a new problem of looking for parking spots starting with spot n. To be clear, this partitioning scheme merely discards consecutive runs of parked cars, about div n 2
spots at a time.
For demonstrative purposes, let’s consider what would happen if we ignored what we just said and really did define b
as
b = a + (div n 2)
for the case of xs = [0]
and n = 1
; we would start off with
minfrom 0 (1, [0])
and
\[ b = 0 + (\mathrm{div}\;1\,2) = 0 + 0 = 0, \]
such that
partition (<0) [0]  ([], [0])
 as = []
 m = 0
 bs = [0]
 n = 1
and since
(m == b  a)  (0 == 0  0) true!
we would in turn execute
minfrom b (n  m, bs)  minfrom 0 (1, [0])
, resulting in an infinite loop! Thus the correct way to choose b
is with
b = a + (div n 2) + 1
Bird gives the running time as \(\Theta(n)\). He offers this cryptic phrase:
… the number of steps \(T(n)\) for evaluating minfrom 0 xs when n = length xs satisfies \(T(n) = T(n\,div\,2) + \Theta(n)\), with the solution \(T(n) = \Theta(n)\).
Alas, I am not sure what this means. Here’s my own justification of why we have running time \(\Theta(n)\). The two most expensive operations in the recursive algorithm are m = length as
and partition (<b) xs
. The thing is that both of these calculations take \(\Theta(n)\) time, and both occur only once each, for every call to minfrom
. Now, minfrom
calculates length as
, but it does not calculate length bs
. This is again, because of the Fullness Theorem — we only care about the first partition being completely packed with cars. Thus, we never really calculate m = length as
over the same range. The worst case is an input like xs = [0..1000]
where the entire range of concern is packed with cars; in this case we would calculate the length of [0..500]
, then see that it’s full and choose the second partition. We’d then choose [501..750]
, and so on, such that the sum of these calculations effectively cost as much as length xs
, or \(n\) itself.
In my sister post, I also described a similar problem, dubbed the Parking Load problem. At the time, I was quite surprised at how the answer was much simpler and easier to calculate. From the insight I gained from the Fullness Theorem, I think it is clear why that is the case. Indeed, the Parking Load problem is just a slight wrinkle of the Fullness Theorem, where n
(number of parked cars) is known, but b
(the endpoint of the “partition”), if you will, is unknown. The problem is to simply compute \(b + 1  n\). (We have to add 1 to b
because we use 0based indexing.) I love it when you can explain something in a new way — don’t you?
I think this lays to rest (for now) the intricacies of the Parking Lot problem, or as Bird puts it, finding the smallest free number. Still, I like my parking lot analogy better because I believe it’s important to talk about problems in a way that can be related to the real world.
BigO only cares about growth of the algorithm; the \(n^2\) will come to dominate the growth rate as \(n\) gets bigger.↩
It is for this reason, apart from looping indefinitely, that justifies the break condition for the outer loop in Algorithm P1.↩
Bird wrote (<=n) as the filter condition, but this is in error. The simpler (<n)
does the job just as well.↩
According to Bird, it is 20% faster than the arraybased algorithm.↩
20160828
*
When I first started using TeX, my main OS was Arch Linux. This was fine until I switched to NixOS a couple years ago — I found that NixOS’s packaging of TexLive was not as current as Arch Linux.
This is still the case today.
As much as I love using NixOS, creating a Nix package of a very large project like TexLive is no joke.
Containerization makes sense for small, doonethingwell services like webservers and such, but it makes even more sense for big, complicated collections of packages like TexLive. There are hundreds (thousands?) of components in TexLive: individual TeX packages, fonts, typesetting engines, etc. Thankfully, Arch Linux maintainers do a great of keeping up with upstream packages, and TexLive is no exception!
I’ve created a new Github project (simply called texlivedocker
) to house the Dockerfiles I use to create TexLive images — all built on top of Arch Linux as the base image. The project uses TravisCI to upload images to Docker Hub.
Be sure to have a look at the README for suggestions on usage.
If you have any suggestions for improvement (please bear in mind that we want to keep the images as simple as possible), please let me know in the Github issue tracker.
Happy TeXing!
]]>20160717
*
Like most people, I use git daily. For many years, I used to have these two aliases:
alias gdf="git diff"
alias gdfc="git diff cached"
Last year I started working professionally as a developer and I began to work on many different repos at the same time. Oftentimes I would do either a git diff
or git diff cached
, then come back to it 10 minutes later but then forget whether the diff had a cached
flag or not. I needed to script some more git helpers functions!
I created a new shell function called gdf
to replace the two aliases above. It works by first showing you the git diff
output, then the git diff cached
output. For both outputs, a vertical colored “ribbon” is printed on the left margin to denote whether it’s the working tree (git diff
) or index (git diff cached
aka “staging area”). The name of the repo is prepended/appended to the output as well to further disambiguate it. Here are the functions:
■ 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 

1 2 3 4 5 6 7 8 9 10 11 12 13 

I use Zsh as my shell, so I wrote the above in Zsh. I simply drop these files inside my autoloaded directory, which is defined like this:
fpath=(~/.zsh/func $fpath) # add ~/.zsh/func to $fpath
autoload U ~/.zsh/func/*(:t) # load all functions in ~/.zsh/func
Here is some sample output (used in the course of writing this blog post):
 blog 
T diff git a/post/20160717gitdiffimproved.org b/post/20160717gitdiffimproved.org
R index 631bcf4..9f6fbac 100644
E  a/post/20160717gitdiffimproved.org
E +++ b/post/20160717gitdiffimproved.org
@@ 7,7 +7,7 @@ tags: programming
┃
┃ * Motivation
┃
┃ Like most people, I use git every day.
┃ +Like most people, I use git daily.
┃ For many years, I used to have these two aliases:
┃
┃ #+begin_src shell
┃ @@ 15,9 +15,9 @@ alias gdf="git diff"
┃ alias gdfc="git diff cached"
┃ #+end_src
┃
┃ Last year, I started working professionally as a developer, and I began to work on many different repos at the same time.
┃ +Last year I started working professionally as a developer and I began to work on many different repos at the same time.
┃ Oftentimes I would do either a ~git diff~ or ~git diff cached~, then come back to it 10 minutes later but then forget whether the diff had a ~cached~ flag or not.
┃ So, I needed to script some more git helpers functions!
┃ +I needed to script some more git helpers functions!
┃
┃ * Implementation
┃
┃ @@ 41,9 +41,11 @@ autoload U ~/.zsh/func/*(:t) # load all functions in ~/.zsh/func
┃ Here is some sample output:
┃
┃ #+begin_src diff
+ foo
T #+end_src
R
E #+begin_src diff
E +
+ bar
┃ #+end_src
┃
┃ * Conclusion
 blog 
 blog 
I diff git a/post/20160717gitdiffimproved.org b/post/20160717gitdiffimproved.org
N index dc2fca4..631bcf4 100644
D  a/post/20160717gitdiffimproved.org
E +++ b/post/20160717gitdiffimproved.org
X @@ 38,6 +38,14 @@ fpath=(~/.zsh/func $fpath) # add ~/.zsh/func to $fpath
autoload U ~/.zsh/func/*(:t) # load all functions in ~/.zsh/func
┃ #+end_src
┃
┃ +Here is some sample output:
┃ +
┃ +#+begin_src diff
┃ +#+end_src
┃ +
┃ +#+begin_src diff
┃ +#+end_src
┃ +
┃ * Conclusion
┃
┃ I've been a happy ~gdf~ user for some months now.
 blog 
I’ve been a happy gdf
user for some months now. The only “downside” is that because of the vertical ribbon (and the repo name at the top/bottom), the output is no longer readable by patch
(or copypastable into a diffreading utility/service). But, this is a minor grievance at best as one can easily invoke the lowlevel git diff
or git diff cached
directly to get the raw (patch
able) output.
Happy hacking!
]]>