1

I have to write a shift function that searches an element's pair from a "table" (list). And if the element is not in the given list, it has to give a '#' az a result.

Examples:

shift [('b', 'g'), ('c', 'h'), ('a', 'f')] 'a' == 'f'
shift [('b', 'g'), ('c', 'h'), ('a', 'f')] 'b' == 'g'
shift [('b', 'g'), ('c', 'h'), ('a', 'f')] 'b' == 'g'
shift [('b', 'g'), ('c', 'h'), ('a', 'f')] 'x' == '#'

My code:

shift :: [(Char,Char)] -> Char -> Char
shift z c = [b |(a,b)<-z,a==c]!!0

It works, but not for the exceptions. I can't seem to make it work for elemnts that are not in the list. I have tried:

shift z c 
    | c `elem` z =[b |(a,b)<-z,a==c]!!0
    | otherwise ='#'

and a helper function:

isgood z c
    | c `elem` z = (shift z c)
    | otherwise = '#'

But they don't work. How to solve this?

Vivi
  • 75
  • 7
  • 1
    Hint: take a look at `ord` and `chr`. – Willem Van Onsem May 16 '20 at 17:09
  • 1
    It could be a job for Map objects. If so, you might find function [findWithDefault](https://hackage.haskell.org/package/containers-0.6.2.1/docs/Data-Map-Internal.html#v:findWithDefault) useful. – jpmarinier May 16 '20 at 17:37
  • 1
    The most direct library function for this is `lookup`. You can use `fromMaybe` with the result of that, or pattern match on it yourself if you prefer. – Robin Zigmond May 16 '20 at 17:46

1 Answers1

3

There are lots of ways you can go about writing this, which all essentially do the same thing.

One way is just a slight modification of one of your previous attempts, the one which "works, but not for the exceptions":

shift :: [(Char,Char)] -> Char -> Char
shift z c = [b |(a,b)<-z,a==c]!!0

The only problem with this, as I'm sure you've observed, is that the program crashes when you failed to find the character, rather than the function giving you '#' as desired. This is because (!! 0), like head (which it is equivalent to), fails with an ugly runtime error when applied to an empty list. So all you need to do is to check if the list is empty or not, and either take the first element or give the default answer. The neatest way to do this in my opinion is to pattern match in a case expression:

shift :: [(Char,Char)] -> Char -> Char
shift z c = case [b |(a,b)<-z,a==c] of
    [] -> '#'
    (c:_) -> c

But this concept of looking something up in a "lookup table" in the form of a list of pairs is so standard that the Haskell Prelude already has a function for it, called, naturally, lookup. It returns a Maybe type as a safe way of handling failure, and you can pattern match on the result in a similar way as in the previous version if you want:

shift z c = case (lookup c z) of
    Nothing -> '#'
    Just c -> c

and this can be further simplified using fromMaybe, another standard library function:

shift z c = fromMaybe '#' (lookup c z)

Two further points to finish:

  • using a list of pairs as a dictionary like data structure, while OK for simple cases, is not very efficient. It's better to use a specialised structure such as Map for this.
  • shift is a strange name for this function, since it needs a lookup table of arbitrary pairs. For a genuine Caesar shift cipher the table is implicit in the shift number, and you would want a function of type Int -> Char -> Char, which would be implemented rather differently.
Robin Zigmond
  • 17,805
  • 2
  • 23
  • 34
  • if you're a point free fetishist you could also do `shift = fromMaybe '#' ... lookup` and define `(...) = (.) . (.)` not that anyone cares. – Ace shinigami May 16 '20 at 19:29
  • You'll also need to use `flip lookup` rather than just `lookup` for that to work :). But yes, I deliberately steered clear of suggesting anything like that, or even using the `$` operator instead of the parentheses, to avoid confusing the issue or introducing things that would need explanation for someone new to Haskell. – Robin Zigmond May 16 '20 at 21:54