简体   繁体   中英

Haskell polymorphic function that checks if a list is palindrome

I'm trying to solve this excercise but I can't think of a solution. I need to check if a list is palindrome, taking these considerations:
If the list is simple I just need to check if it's palindrome horizontally, but if it's a nested list I need to check both, vertical and horizontal.
I also need to keep in mind that each element inside the list has to be palindrome by itself, for example:

A = [1,2,3,3,2,1] is palindrome

for this case I just created a single function that uses reverse :

unidimensional:: (Eq a) => [a] -> Bool
unidimensional [] = error"List is empty."
unidimensional xs = xs == reverse xs

also this case, for example:

B = [[1,2,1],[1,2,1]] horizontally palindrome and to check if it's palindrome vertically I just transpose it, [[1,1],[2,2],[1,1]] and the result is, yes, it is palindrome both ways.

I solved this by using the function transpose to evaluate vertically if it is palindrome and then, using the function unidimensional that I used before I check if it's palindrome horizontally, everything fine:

--Checks if it's palindrome horizontally and vertically 
bidimensional:: (Eq a) => [[a]] -> Bool
bidimensional [[]] = error"Empty List."
bidimensional (xs) = if left_right xs && up_down xs then True else False
--Checks if it's palindrome horizontally:
left_right (x:xs) = if x == reverse x then
if xs /= [] then bidimensional xs else True 
else False
--Checks if it's palindrome vertically:
up_down:: (Eq a) => [[a]] -> Bool
up_down (xs) = left_right ys where ys = transpose xs
transpose:: [[a]]->[[a]]
transpose ([]:_) = []
transpose x = (map head x) : transpose (map tail x)



the problem is here:

The input my program needs to receive has to be something like this:

> palindrome [[1,2,2,1], [3,7,9,9],[3,7,9,9], [1,2,2,1]]

My problem is: my function palindrome should receive as a parameter a list [a] , but palindrome should work for nested lists, like [[[a]]] or [[a]]

palindrome is the function that takes the input.

The thing is that when I get a simple list, my head, which is x is a number, and xs which is the tail, would be the rest of the numbers, and that's ok, but when palindrome receives a nested list, for example [[[2,2],[2,2]],[[1,1],[1,1]]] the head, x is now [[2,2],[2,2]] , so I can't use my function like, bidimensional because [[2,2],[2,2]] is not a list anymore, when I try to call bidimensional with xs which is [[2,2],[2,2]] I get an error: xs is now type a and not [[a]]

My question is : How can I make my function palindrome, work with any type of list (simple and nested) taking into account the error I mentioned before. Thanks in advance!

My question is: How can I make my function palindrome, work with any type of list (simple and nested) taking into account the error I mentioned before. Thanks in advance!

Let's think about this hypothetical palindrome function and try to figure out what type signature it would need. (Thinking about a function in terms of its type signature always helps me.)

Suppose palindrome has a signature palindrome :: [x] -> Bool

We what the following statements to be true:

palindrome [1, 2, 1] === True
palindrome [1, 2, 2] === False
palindrome [[1, 2, 1], [3, 4, 3], [1, 2, 1]] === True
palindrome [[1, 2, 2], [3, 4, 3], [1, 2, 2]] === False

In the first two assertions, palindrome specializes to [Integer] -> Bool , so x is Integer in those cases. The only sensible implementation with x === Integer is simply to check if the supplied list of integers is a palindrome, ie, check that the first element equals the last, pop those off and repeat (or, equivalently, check that xs equals reverse xs as you have). We can use this algorithm whenever x is an instance of Eq .

In the last two properties, palindrome specializes to [[Integer]] -> Bool , so x is [Integer] there. It seems like we should be able to detect that x is itself a list of integers, and then know that we need to recursively apply palindrome to each inner list. However, Haskell polymorphism doesn't work that way. In order for a function to be polymorphic over a type parameter x , we need to define a single implementation of that function that works the same way no matter what type x happens to be .

In other words, to define a polymorphic function palindrome :: [x] -> Bool , our implementation cannot know anything about the type parameter x . This forces us to use the same implementation when x === [Integer] as we used when x === Integer , which would evaluate to True instead of False for the final test case.

You won't be able to make your palindrome function work the way you want it to for nested lists in Haskell if you insist on the input being standard [] types.

One thing you might do is have your palindrome function take an extra parameter of type Int that tells the function how deeply to check. In that case, you'd need to know ahead of time how deeply nested your input is. Another thing you might do is write your palindrome function to take input in some other data structure besides [] , like a Tree , or something with arbitrary nesting. Maybe you can write your function to accept a Value , a type that represents arbitrary JSON values from the popular library Aeson .

Note: It's probably a good idea for palindrome [] to be True , no?

You could use a typeclass.

class Palindrome a where
  palindrome :: a -> Bool

instance Palindrome Integer where
  palindrome _ = True

instance (Eq a, Palindrome a) => Palindrome [a] where
  palindrome xs = xs == reverse xs && all palindrome xs

For a two-dimensional list, you are checking if the list is a palindrome, "both horizontally and vertically". This can be done without the transpose function: the list is a palindrome "vertically" if the list is a palindrome, and the list is a palindrome "horizontally" if every sublist is a palindrome. The second instance checks both.

For a one-dimensional list, the second instance simply checks if the list is a palindrome. In this case, the && all palindrome xs might as well not be there: since the first instance specifies that Integer s are always palindromes, all palindrome xs always evaluates to True . This can be seen as the "base case" for this recursive algorithm.

This will work for any depth of nested lists. You can also instantiate the class for other base data types, or (I'm pretty sure) even for ALL types of class Eq (although this leads to overlapping instances which is its own can of worms).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM