1

So my goal for the program is for it to receive an Int matrix for input, and program converts all numbers > 0 to a unique sequential char, while 0's convert into a '_' (doesn't matter, just any character not in the sequence).

eg.

main> matrixGroupings [[0,2,1],[2,2,0],[[0,0,2]]
[["_ab"],["cd_"],["__e"]]

The best I've been able to achieve is

[["_aa"],["aa_"],["__a"]]

using:

matrixGroupings xss = map (map (\x -> if x > 0 then 'a' else '_')) xss

As far as I can tell, the issue I'm having is getting the program to remember what its last value was, so that when the value check is > 0, it picks the next char in line. I can't for the life of me figure out how to do this though.

Any help would be appreciated.

netugi
  • 13
  • 3

1 Answers1

1

Your problem is an instance of an ancient art: labelling of various structures with a stream of labels. It dates back at least to Chris Okasaki, and my favourite treatment is by Jeremy Gibbons.

As you can see from these two examples, there is some variety to the way a structure may be labelled. But in this present case, I suppose the most straightforward way will do. And in Haskell it would be really short. Let us dive in.

The recipe is this:

  • Define a polymorphic type for your matrices. It must be such that a matrix of numbers and a matrix of characters are both rightful members.
  • Provide an instance of Traversable class. It may in many cases be derived automagically.
  • Pick a monad to your liking. One simple choice is State. (Actually, that is the only choice I can think of.)
  • Create an action in this monad that takes a number to a character.
  • Traverse a matrix with this action.

Let's cook!

  • A type may be as simple as this:

    newtype Matrix a = Matrix [[a]] deriving Show
    

    It is entirely possible that the inner lists will be of unequal length — this type does not protect us from making a "ragged" matrix. This is poor design. But I am going to skim over it for now. Haskell provides an endless depth for perfection. This type is good enough for our needs here.

    We can immediately define an example of a matrix:

    example :: Matrix Int
    example = Matrix [[0,2,1],[2,2,0],[0,0,2]]
    
  • How hard is it to define a Traversable? 0 hard.

    {-# language DeriveTraversable #-}
    
    ...
    
    newtype Matrix a = Matrix [[a]] deriving (Show, Functor, Foldable, Traversable)
    

    Presto.

  • Where do we get labels from? It is a side effect. The function reaches somewhere, takes a stream of labels, takes the head, and puts the tail back in the extra-dimensional pocket. A monad that can do this is State.

  • It works like this:

    label :: Int -> State String Char
    label 0 = return '_'
    label x = do
        ls <- get
        case ls of
            [ ] -> error "No more labels!"
            (l: ls') -> do
                put ls'
                return l
    

    I hope the code explains itself. When a function "creates" a monadic value, we call it "effectful", or an "action" in a given monad. For instance, print is an action that, well, prints stuff. Which is an effect. label is also an action, though in a different monad. Compare and see for youself.

  • Now we are ready to cook a solution:

    matrixGroupings m = evalState (traverse label m) ['a'..'z']
    

This is it.

λ matrixGroupings example
Matrix ["_ab","cd_","__e"]

Bon appetit!

 

P.S.   I took all glory from you, it is unfair. To make things fun again, I challenge you for an exercise: can you define a Traversable instance that labels a matrix in another order — by columns first, then rows?

Ignat Insarov
  • 4,660
  • 18
  • 37
  • Thanks so much! Though I wasn't able to use the {-# extension #-} format in my code for some reason, so I had to use :set -XDeriveTraversable. – netugi Oct 26 '19 at 22:11
  • @netugi You need to put it before the `module ... where` line. – Ignat Insarov Oct 26 '19 at 22:12
  • Thank you. I just want you to know I did give your exercise a go for a little bit, but I didn't wanna spend too much time on it at the moment since this is for a big university assignment due in a few days. I'll give it another go if I have the free time after that. I do appreciate the challenge, it helped with me understand how the code works better. – netugi Oct 26 '19 at 22:35