The problem#
If you take the word
, reverse the order of the letters
and reverse the alphabet:wizard
From: abcdefghijklmnopqrstuvwxyz
To: ZYXWVUTSRQPONMLKJIHGFEDCBA
then you get the word
back, an observation made at least
as early as 1972.wizard
Now let's write a shell script to verify this so we can find other words with similar interesting properties. The obvious shell script to verify this
echo wizard | tr a-z z-a | rev
unfortunately fails with the error
tr: range-endpoints of 'z-a' are in reverse collating sequence order
The error is by design: it's not clear what a sequence in reverse order should mean, so POSIX actually requires that it not work.
Reverse-ordered sequence the hard way#
The arguments to tr
can be strings interpreted as lists of
characters instead of character ranges. We could just precompute
(or type by hand) the alphabet in order and in reverse order and write
echo wizard | tr abcdefghijklmnopqrstuvwxyz zyxwvutsrqponmlkjihgfedcba | rev
But what if we want to automate generating the alphabet? (Perhaps there's some other character sequence we might care about in another situation.)
seq
will generate sequences of numbers in reverse order if
given a negative increment:
$ seq 3 -1 1
3
2
1
But seq
doesn't generate characters. Luckily, someone has already
figured out how to convert from numbers to characters in shell, so we
can use the chr()
function in our script:
chr() {
printf \\"$(printf '%03o' "$1")"
}
ShellCheck is not happy with this function because it's
abusing printf
by generating a string for printf
to take as
its first argument. The way it works is writing the number it is given
as input in octal, prefixing it with a backslash and then using the
fact that \NNN
where NNN
is an octal number is one of the special
sequences printf
accepts.
As a convenience, we will also take ord()
which uses an obscure
shell feature to convert a character to a number:
ord() {
printf '%d' "'$1"
}
Now we can generate the sequence with seq
:
$ za="$(for charNum in $(seq "$(ord z)" -1 "$(ord a)")
do
chr "$charNum"
done)"
$ echo $za
zyxwvutsrqponmlkjihgfedcba
Now we can use $za
as an argument to tr
:
$ echo wizard | tr a-z "$za" | rev
wizard
Reverse-ordered sequence the easy way#
If we are willing to accept a dependency on bash
, there is a very useful
bashism that simplifies the above:
za="$(echo {z..a} | tr -d ' ')"
This post has some examples of bash
's {..}
syntax.
$ echo {z..a}
z y x w v u t s r q p o n m l k j i h g f e d c b a
It's intended to be iterated over, which is why it has spaces, but the
tr -d ' '
removes the spaces.
Iterate over the dictionary#
The following script uses look
to find all words which
get back to the same word when reversed and the alphabet is reversed:
#!/bin/bash
az="$(echo {a..z} | tr -d ' ')"
za="$(echo {z..a} | tr -d ' ')"
for word in $(look "$1" | tr -d "'" | tr "[:upper:]" "[:lower:]")
do
if [[ "$(echo "$word" | tr "$az" "$za" | rev)" = "$word" ]]
then
echo "$word"
fi
done
It takes a few minutes to run on my computer. I won't spoil the results,
but one of the words with the same property as
is
wizard
.sh
Comments
Have something to add? Post a comment by sending an email to comments@aweirdimagination.net. You may use Markdown for formatting.
There are no comments yet.