Skip to content

Instantly share code, notes, and snippets.

@regiskuckaertz
Created June 22, 2018 14:14
Show Gist options
  • Save regiskuckaertz/4fe0370a4c0a05f29a97aff3c389b468 to your computer and use it in GitHub Desktop.
Save regiskuckaertz/4fe0370a4c0a05f29a97aff3c389b468 to your computer and use it in GitHub Desktop.
Reverse-Then-Add sequence

Let's see what Wolfram Alpha has for us:

RTA is the sequence obtained from reversing the digits of a number n and adding the result to the original number. The 196-algorithm operates RTA iteratively until a palyndromic number is obtained.

Let's define alg196 then. We can take the definition above literally and get the following script:

alg196 :: Integer -> [Integer]
alg196 n = takeWhile palyndromic . rtaseq n

rtaseq will produce an infinite stream of numbers, but we only care about those up to the first that is palyndromic. Essentially we want to produce the sequence

rtaseq n = n : n2 : n3 : n4 : ...
  where n2 = n  + reverse n
        n3 = n2 + reverse n2
        n4 = n3 + reverse n3
        ...

We notice that each element of the list depends on the previous one. There are many ways to express that in Haskell, but one that really shines is via cyclic lists. If I were to define the list [1, 2, 3, 4, ...], I could say that it is the list that starts with 1 followed by the list itself where every element is added to 1.

plus1 = 1 : fmap (+1) plus1

In our case, we need to take each number, reverse it, and add it to itself. This can be expressed just as easily by summing pair-wise the elements of the list itself with the elements of the list "mirrored", i.e. where the digits have been reversed:

rtaseq n = ns where ns = n : zipWith (+) ns (fmap mirror ns)

The mirror function can be described in a two-step process: first get the digits, then reassemble them in reverse order.

mirror :: Integer -> Integer
mirror = undigits . digits

We can either choose to extract the digits from left-to-right and assemble from right-to-left, or the opposite. We choose the former. The digits are extracted by iteratively dividing by 10, populating the result with the remainders. Haskell provides a nice divMod function that does both in one fell swoop and returns a tuple (n, r) where r is the remainder.

It is a typical example of anamorphism, i.e. an unfold. So we'll use unfoldr for that.

digits :: Integer -> [Integer]
digits = unfoldr step
  where step 0 = Nothing
        step n = let (m, r) = n `divMod` 10 in Just (r, m)

Running digits on 1234 will indeed give you the list [4,3,2,1] in return. Next we turn to undigits, which goes in the opposition direction. It is an example of a catamorphism aka a fold.

undigits :: [Integer] -> Integer
undigits = foldl1' f
  where f m n = 10 * m + n

foldl1 assumes a non-empty list and foldl1' is useful when f is strict in its argument to avoid blowing up the stack. We'll cover this in a future session.

Running undigits on [4,3,2,1] will give you back 4321, which is exactly what we want.

We move on to palyndromic. We can use our digits function and prove that a number is palyndromic if its digits are the same in both orders:

palyndromic n = let ns = digits n in ns == reverse ns

Finally we turn to takeWhile. We could use the version available in the Prelude, but the sequence would exclude the first number that is palyndromic. So we must come up with our own implementation, which is trivial

takeWhile :: (a -> Bool) -> [a] -> [a]
takeWhile p [] = []
takeWhile p (x:xs) | p x       = x : takeWhile p xs
                   | otherwise = [x]

And we're done ✌️

An alternative definition

We could take the definition even more literally and iterate the RTA algorithm:

alg196 = takeWhile palyndromic . iterate rta

The expression iterate f x produces the list [x, f x, f (f x), f (f (f x)), ...], which is quite convenient. We just need a proper definition of rta n which is simply the number itself added to is own reverse:

rta n = n + mirror n
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment