Intellects
Intellects

Reputation: 57

Palindrome checker that ignores non-alphanumeric characters and case Haskell

Need to make a program that checks if a given string is a palindrome, it should work whether the case is different, and should ignore non-alphanumeric characters, only using the ord and chr functions in Data.char and regular functions, nothing else. I was able to create the regular palindrome checker:

reverseStr::String->String
reverStr s | s == [] = []
reverseStr (h:t) = reverseStr t ++ [h]

isPalindrome :: String -> Bool
isPalindrome s = s == reverseStr s

I've started work on a function to normalize case:

normalizeCase::String->String
normalizeCase h | h == [] = []
normalizeCase (h) = if ord h > 64 && ord h < 123
    then map (chr $ (ord h + 32)) [h]
    else h

But I get these errors:

• Couldn't match expected type ‘Char -> Char’
              with actual type ‘Char’
• In the first argument of ‘map’, namely ‘(chr $ (ord h + 32))’
  In the expression: map (chr $ (ord h + 32)) [h]
  In the expression:
    if ord h > 64 && ord h < 123 then
        map (chr $ (ord h + 32)) [h]
    else
        h

  |
6 |   then map (chr $ (ord h + 32)) [h]   |             ^^^^^^^^^^^^^^^^^^


    • Couldn't match type ‘Char’ with ‘[Char]’
  Expected type: String
    Actual type: Char
• In the expression: h
  In the expression:
    if ord h > 64 && ord h < 123 then
        map (chr $ (ord h + 32)) [h]
    else
        h
  In an equation for ‘normalizeCase’:
      normalizeCase [h]
        = if ord h > 64 && ord h < 123 then
              map (chr $ (ord h + 32)) [h]
          else
              h
  |
7 |   else h   |        ^

I'm still very new to Haskell and have no idea how to implement ord or chr properly so that it works with this checker, so any help would be appreciated!

Upvotes: 3

Views: 552

Answers (1)

Jon Purdy
Jon Purdy

Reputation: 54999

In this code:

normalizeCase::String->String
normalizeCase h | h == [] = []
normalizeCase (h) = if ord h > 64 && ord h < 123
    then map (chr $ (ord h + 32)) [h]
    else h

Your second pattern (h) (equivalent to just h) matches any list (as long as it wasn’t already matched by the first pattern, h | h == []); so h is a list here and ord h doesn’t make sense, since ord expects a Char, not a [Char].

Assuming h was a character, then chr $ ord h + 32 would also be a character, but map expects a function as its first argument; this is the source of the error that Char -> Char was expected but you gave Char. For the second argument to map, you pass [h], which is a list of a single element h (which in your code is also a list, so you are providing [[Char]] when you want [Char]).

Also assuming h was a character, your condition ord h > 64 && ord h < 123 matches any character between uppercase A and lowercase z, including a few characters you don’t want ([]^_`). The fact that h is a list of characters is the source of the error that Char was expected but you gave [Char].

You also seem to be mixing recursive style with map—in this case you should either use map or define the function by cases.

Here’s how your code could look with these errors fixed. First, using recursion:

normalizeCase :: String -> String

-- Given an empty list, return an empty list.
-- This happens if the original input was empty,
-- or when we reach the end of the recursion.
normalizeCase [] = []

-- Given a non-empty list,
-- test the first character ‘c’.
normalizeCase (c : cs) = if c >= 'A' && c <= 'Z'

  -- If it’s uppercase, lowercase it and
  -- prepend it to the result of normalizing
  -- the remainder of the string ‘cs’.
  then chr (ord c + 32) : normalizeCase cs

  -- Otherwise, don’t change it, but still
  -- prepend it to the result of normalizing
  -- the remainder of the string ‘cs’.
  else c : normalizeCase cs

Or, using map:

normalizeCase :: String -> String

-- Given any string:
normalizeCase s

  -- For each character:
  = map

    -- If it’s uppercase, lowercase it.
    (\ c -> if c >= 'A' && c <= 'Z'
      then chr (ord c + 32)
      else c)

    -- Over the whole string.
    s

Char can be compared directly (c >= 'A'), which is more readable, but if you’re expected to use ord for the comparison as well, that would be ord c >= 65.

I know you’re not supposed to use any other standard functions for this task, but for future reference, this can also be implemented very straightforwardly using toLower from Data.Char:

import Data.Char (toLower)

normalizeCase :: String -> String
normalizeCase s = map toLower s

-- Alternatively, a point-free/eta-reduced version:
normalizeCase = map toLower

For the additional task of removing non-alphanumeric characters, you can use filter, a list comprehension with a guard condition, or write a direct recursive version yourself.

Upvotes: 3

Related Questions