Haskell: Assigning unique char to matrix values if x > 0

℡╲_俬逩灬. 提交于 2020-01-25 00:20:14

问题


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.


回答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?



来源:https://stackoverflow.com/questions/58574605/haskell-assigning-unique-char-to-matrix-values-if-x-0

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!