DEV Community

RedCalmContemplator
RedCalmContemplator

Posted on

Random Haskell Code Challenge

I was at work when a friend and colleague of mine showed me a funny meme, and it is about a student that tricked the teacher in a simple programming challenge, or probably a test. The requested output from the student is to create a program and produce the following result on the screen

ABCDEFGFEDCBA
ABCDEF_FEDCBA
ABCDE___EDCBA
ABCD_____DCBA
ABC_______CBA
AB_________BA
A___________A
Enter fullscreen mode Exit fullscreen mode

At first, it seems that the challenge involves an algorithm "for loops" and manipulation/re-arranging of data inside the loop to achieve the desired result. The student presented a dummy non-working code that has two nested for loops and some index manipulation. These loops appear to be dead code, and the student then adds a procedure that prints the encoded output. After that, the teacher didn't bother to check the underlying logic and asked the student to run the code directly, yet the student managed to hide the encoded code in plain sight.

for(int i=0; i<arr.length; i++) {
  for(int j=0; i... ) {
    // some none working code..
  }
}
printsomething()

printSomething() {

  print("ABCDEFGFEDCBA")
  print("ABCD....")
  ...
}
Enter fullscreen mode Exit fullscreen mode

That's very funny to think of, and it's quite challenging, and I thought perhaps "Why not solve this kind of challenge in Haskell code" I'm not an expert in Haskell, nor do I use Haskell in my day-to-day work, but in any case, it's a good exercise so let's jump in.

Without a problem definition, we assume that we need to print "A" to "G" and after that, it will go back from "F" to "A", and at the very least that's how it appears in the first line. We could then apply this rule for all the succeeding lines, and just with an added twist we also eliminate the characters in the middle until there's only one character left to print out.

First Step: Generate a Data

Generate a list of lists, and each list will have different lengths

list 1 of length 7
list 2 of length 6
list 3 of length 5
...

we then put this on a list, that would be

[ list1, list2, list3, ... ]
Enter fullscreen mode Exit fullscreen mode

as of now this list contains ordered numbers incremented by 1, we achieve that by Haskell infinite list and take only the desired length.

take 5 [1..] -- take 5 elements in an infinite list.
-- [1, 2, 3, 4, 5]
Enter fullscreen mode Exit fullscreen mode

Okay, so far we have a function to build a list of numbers incrementing by 1. Next, we generate a list of length 7 used as a row, this then will be mapped to generate a list as a length, let's call this rangeMatrix.

import Data.List

rangeMatrix = fmap (\n -> take n [1..]) [1..7]
-- [ [1]
-- , [1, 2]
-- , [1, 2, 3]
-- , [1, ... ]
-- ...
-- ]
Enter fullscreen mode Exit fullscreen mode

The fmap here is like in javascript Array.map, which maps each element in the list using a mapper function, in our case the mapping function accepts an integer (n) and returns a list taking only (n) elements.

Now that we have a generated matrix of numbers, we convert these numbers to our desired characters "A" to "G", we then create a simple mapping function to achieve this

conv :: Int -> Char
conv 1 = 'A'
conv 2 = 'B'
conv 3 = 'C'
conv 4 = 'D'
conv 5 = 'E'
conv 6 = 'F'
conv 7 = 'G'
conv _ = ' '
Enter fullscreen mode Exit fullscreen mode

then we map the rangeMatrix numeric to converted character.

rangeMatrixChars :: [[Int]] -> [[Char]]
rangeMatrixChars xss = fmap (fmap conv) xss
-- [ ['A']
-- , ['A', 'B']
-- , ['A', 'B', 'C']
-- , ['A', ... ]
-- ...
-- ]
Enter fullscreen mode Exit fullscreen mode

Second step: Format Data

The next step and the most interesting part is to format this matrix to a readable string. It is a common practice in functional programming to design modular functions, each of which has responsibilities. In our case, we just currently designed a function that generates data, and now we will create a function that accepts our matrix characters and process it to a single list of characters (also equivalent to String in Haskell).

prettyFormat :: [[Char]] -> [Char]
prettyFormat xss =
 foldl prettyFormatStep [] xss
Enter fullscreen mode Exit fullscreen mode

and each step of the format will do

prettyFormatStep :: Int -> [Char] -> [Char] -> [Char]
prettyFormatStep maxChar [] xs =
  xs
prettyFormatStep maxChar acc xs =
  xs
  ++ ['\n']
  ++ acc
Enter fullscreen mode Exit fullscreen mode
ABCDEFG
ABCDEF
ABCDE
ABCD
ABC
AB
A
Enter fullscreen mode Exit fullscreen mode

We're getting closer to the desired output. In addition, we need to tweak it out a little bit. Next, we add negative space, to show disappearing characters. As of now, we use the * character

negativeSpaceFormat maxChar whiteSpaceChar xs = 
  replicate (abs (length xs - maxChar)) whiteSpaceChar
Enter fullscreen mode Exit fullscreen mode

and in prettyFormatStep will be

prettyFormatStep maxChar [] xs =
  xs
+ ++ (negativeSpaceFormat maxChar '*' xs ++ reverse xs)

prettyFormatStep maxChar acc xs =
  xs
+ ++ (negativeSpaceFormat maxChar '*' xs ++ reverse xs)
  ++ ['\n']
  ++ acc
Enter fullscreen mode Exit fullscreen mode
ABCDEFG
ABCDEF*
ABCDE**
ABCD***
ABC****
AB*****
A******
Enter fullscreen mode Exit fullscreen mode

and finally, we add the mirror of the output per row.

prettyFormatStep maxChar [] xs =
  xs
  ++ negativeSpaceFormat maxChar '*' xs
+ ++ (negativeSpaceFormat maxChar '*' xs ++ reverse xs)

prettyFormatStep maxChar acc xs =
  xs
  ++ negativeSpaceFormat maxChar '*' xs
+ ++ (negativeSpaceFormat maxChar '*' xs ++ reverse xs)
  ++ ['\n']
  ++ acc
Enter fullscreen mode Exit fullscreen mode

Since the negative whitespace characters are in symmetry we don't need to reverse it, but in general, the reversed will also be applied. The final output would be

ABCDEFGGFEDCBA
ABCDEF**FEDCBA
ABCDE****EDCBA
ABCD******DCBA
ABC********CBA
AB**********BA
A************A
Enter fullscreen mode Exit fullscreen mode

But wait! We have an error, the output so far is not the same as the expected output at the beginning of this article. If we notice the first row has a duplicate G and the second row has an extra * for each row. The problem is that we mirror exactly our data which is the exact inverse of the list. We could fix this by removing the first element of the inversed list. We can use the function tail to fix this.

prettyFormatStep maxChar [] xs =
  xs
  ++ negativeSpaceFormat maxChar '*' xs
- ++ (negativeSpaceFormat maxChar '*' xs ++ reverse xs)
+ ++ tail (negativeSpaceFormat maxChar '*' xs ++ reverse xs)

prettyFormatStep maxChar acc xs =
  xs
  ++ negativeSpaceFormat maxChar '*' xs
- ++ (negativeSpaceFormat maxChar '*' xs ++ reverse xs)
+ ++ tail (negativeSpaceFormat maxChar '*' xs ++ reverse xs)
  ++ ['\n']
  ++ acc
Enter fullscreen mode Exit fullscreen mode
ABCDEFGFEDCBA
ABCDEF*FEDCBA
ABCDE***EDCBA
ABCD*****DCBA
ABC*******CBA
AB*********BA
A***********A
Enter fullscreen mode Exit fullscreen mode

If you read the article up to this point you deserve applause 👏 we did arrive at the expected output.

There are a few optimization and syntax that needs to be adjusted, some of you may have a better understanding of Haskell may point out eta reduction, list comprehension, and many more. I leave it to you to tinker with this solution.

Thank you, and have fun coding

Full runnable source code: https://replit.com/@RonnelReposo/SimplisticAffectionatePackagedsoftware#Main.hs

Top comments (0)