Can you decipher the following sentences?
All hmuan biegns are bron fere and euqal in dgiinty and rgiths. Tehy are ednwoed wtih raeosn and cnocseicne and sohlud act tworads one aonhter in a sipirt of borhtreohod.
It’s the first article of the Universal Declaration of Human Rights, with letters in each word rearranged in a certain way.
In the above sentences the first and the last letter of each word stay in their places, while the remaining letters are scrambled. Such text is surprisingly easy to read, especially when reading at a high speed. It seems that our brains need the first and the last letter as anchors, but order of the letters in between is not so important. This feature is dubbed typoglycemia.
Code in Haskell
To play with the above observation, I wrote a short Haskell program which obfuscates an input text as explained above.
The first function,
- a function
a -> Boolwhich classifies each element in a list as either “interesting” or not,
- another function
[a] -> [a]which does something with a contiguous block of “interesting” elements,
- and a list of elements
The result is a list of elements
in which all the blocks of “interesting” elements
are transformed using the above-mentioned function.
The second function,
obfuscateSwap, takes a list of elements
and shuffles all the elements except the first and the last
by swapping adjacent elements.
obfuscateSwap "towards" == "tworads".
Here’s a different implementation,
which reverses the internal elements
obfuscateReverse "towards" == "tdrawos"):
A few things to notice:
obfuscateSwap . obfuscateSwapand
obfuscateReverse . obfuscateReverseare equivalent to
id, so we can use the same function to “encrypt” and “decrypt” a message.
- I like the type of
mapBlocks: there is a nice separation of work,
mapBlocksprocesses an input list but outsources obfuscation to another function. Also, it’s not limited to strings. Only after partial application of
isLetterwe get a function operating on strings.
- I don’t like the implementation of
mapBlocks: it’s not tail recursive and we will run out of heap for larger inputs. It should definitely be rewritten. A straightforward tail recursive version would feature an additional accumulator.
- Results of
obfuscateReverseare harder to decipher than results of
- I’m thinking about (ab)using Parsec for an alternative implementation of
To see the difference between the two versions of obfuscate,
read a bit longer text, the second article of UDHR.
Here’s scrambled using
Eevyrnoe is etntield to all the rgiths and ferdemos set froth in tihs Dcealariton, wtiohut dsiitcniton of any knid, scuh as rcae, clouor, sex, lnauggae, rlegioin, ploticial or ohter oipinon, ntaoianl or scoail oirign, poreptry, brith or ohter satuts. Fruhtreomre, no dsiitcniton sahll be mdae on the bsais of the ploticial, jrusiidtcoianl or itnreanitnoal satuts of the cuotnry or treirotry to wihch a preosn blenogs, wehhter it be idnpeneednt, turst, non-slef-gvoreinng or udner any ohter lmititaoin of svoreiengty.
Here’s scrambled using
Enoyreve is eeltitnd to all the rthgis and fmodeers set ftroh in tihs Doitaralcen, wuohtit doitcnitsin of any knid, scuh as rcae, cuolor, sex, lgaugnae, roigilen, pacitilol or oehtr ooinipn, nanoital or saicol oigirn, ptrepory, btrih or oehtr sutats. Fromrehtrue, no doitcnitsin slahl be mdae on the bisas of the pacitilol, janoitcidsirul or ianoitanretnl sutats of the crtnuoy or trotirrey to wcihh a posren bgnoles, wehtehr it be inednepednt, tsurt, non-slef-gninrevog or uednr any oehtr loitatimin of stngierevoy.