0

I have to define a function called zeros which takes input of two lists and returns a boolean which returns True if the number 0 appears the same amount of times in each list and false otherwise.

This is the last question in my homework and and I have managed to solve the question get it to work but I wondered if anybody can spot ways in which to reduce the amount of code. Any ideas are appreciated.
My code so far is as follows:

x :: Int
x = 0 
instances::[Int]->Int
instances [] = 0
instances (y:ys)
    | x==y = 1+(instances ys)
    | otherwise = instances ys



zeros :: [Int] -> [Int] -> Bool
zeros [] [] = False
zeros x y
       | ((instances x) == (instances y)) = True
       | otherwise = False
4
  • 3
    Seems to me that zeros [] [] should equal True? At least it doesn't seem any more false than zeros [1] [], which your code would consider True. I'm not sure you even need to handle the case zeros [] []... Commented Apr 23, 2012 at 23:49
  • no i don't think it should be because if the lists were empty then they would contain no zeros at all hence it would be false Commented Apr 23, 2012 at 23:51
  • 1
    I'm having trouble convincing myself that "no zeros" and "zero zeros" aren't the same thing. For example, zeros [1] [] would return True the way you have written things (both have zero zeros). Whereas zeros [] [] would return False (despite that both have zero zeros). Commented Apr 23, 2012 at 23:54
  • A really short solution : zeroes = (==) `on` filter (== 0) Commented Apr 24, 2012 at 9:40

6 Answers 6

3

Without giving too much away, since this is homework, here are a few hints.

Do you know about list comprehensions yet? They would be useful in this case. For example, you could combine them with an if expression to do something like this:

*Main> let starS s = [if c == 's' then '*' else ' ' | c <- s]
*Main> starS "schooners"
"*       *"

You can even use them to do filtering. For example:

*Main> let findFives xs = [x | x <- xs, x == 5]
*Main> findFives [3,7,5,6,3,4,5,7,5,5]
[5,5,5,5]

Neither of these is a complete answer, but it shouldn't be hard to see how to adapt these structures to your situation.

You should also think about whether you actually need a guard here! For example, here's a function written with a guard in the same style as yours:

lensMatch [] [] = True
lensMatch xs ys
             | ((length xs) == (length ys)) = True
             | otherwise = False

Here's a function that does the same thing!

lensMatch' xs ys = length xs == length ys

You can see that they are the same; testing the first:

*Main> lensMatch [1..4] [1..4]
True
*Main> lensMatch [1..4] [1..5]
False
*Main> lensMatch [] [1..5]
False
*Main> lensMatch [] []
True

And testing the second:

*Main> lensMatch' [1..4] [1..4]
True
*Main> lensMatch' [1..4] [1..5]
False
*Main> lensMatch' [] [1..5]
False
*Main> lensMatch' [] []
True

Finally, I agree very strongly with sblom's comment above; zeros [] [] should be True! Think about the following statement: "For each item x in set s, x > 0". If set s is empty, then the statement is true! It's true because there are no items in s at all. This seems to me like a similar situation.

Sign up to request clarification or add additional context in comments.

Comments

1

Have you thought of doing this in one pass by filtering each list to get just the zeroes and then comparing the length of the lists to see if they are equal?

zeroCompare xs ys = cZeroes xs == cZeroes ys
  where
    cZeroes as = length $ filter (== 0) as

4 Comments

I did consider that and i actually think thats what my tutor will be looking for but i was unsure to implement the filter and guard in one sweep
what is the significance of 'as' ?
What's the significance of xs and ys? None. It's just a way of stating an input parameter. I could have written in it points free style as cZeroes = length . filter (== 0), but I thought it would be easier for you to understand it with a parameter.
ah i actually understood it in a points free style. thanks a lot though dude!
1

I can't believe nobody has suggested to use foldr yet. Not the shortest or best definition, but IMO the most educational:

instances :: Eq a => a -> [a] -> Int
instances n = foldr incrementIfEqual 0
    where incrementIfEqual x subtotal
              | x == n = subtotal + 1
              | otherwise = subtotal

zeros :: Num a => [a] -> [a] -> Bool
zeros xs ys = instances 0 xs == instances 0 ys

Though for a really brief definition of instances, what I came up with is basically the same as Abizern:

instances :: Eq a => a -> [a] -> Int
instances x = length . filter (==x)

Comments

0

Instead of length and filter, you can take the result of a predicate p, convert it to 0 or 1, and sum the result:

count p = sum . map (fromEnum.p)

--or

import Data.List
count p = foldl' (\x -> (x+).fromEnum.p) 0

In your case, p is of course (==0). Converting Bool to Int using fromEnum is a very useful trick.

Comments

0

Another idea would be to deal with both list simultaneously, which is a little bit lengthy, but easy to understand:

zeros xs ys = cmp xs ys == 0 where
  cmp (0:xs) ys = cmp xs ys + 1
  cmp xs (0:ys) = cmp xs ys - 1
  cmp (_:xs) ys = cmp xs ys
  cmp xs (_:ys) = cmp xs ys
  cmp [] []     = 0

Comments

0

I would break the problem down into smaller problems involving helper functions.

This is how I would break it down:

  1. Main function to compare two counts
  2. Count helper function

First: You need a way to count the amount of zeroes in a list. For example, I would approach this by doing the following if searching for the number of 0 in an integer list:

count :: [Int] -> Int
count xs = foldl (\count num -> if num == 0 then (count + 1) else count) 0 xs

Second: You need a way to compare the count of two lists. Essentially, you need a function that takes two lists in as parameters, calculates the count of each list, and then returns a boolean depending on the result. For example, if each list is an int list, corresponding with my count example above:

    equalZeroes :: [Int] -> [Int] -> Bool
    equalZeroes x y = (count x) == (count y)

You could also define count under the where keyword inside the equalZeroes function like so:

equalZeroes :: [Int] -> [Int] -> Bool
equalZeroes x y = (count x) == (count y)
    where
        count :: [Int] -> Int
        count xs = foldl (\count num -> if num == 0 then (count + 1) else count) 0 xs

When running this code, calling the function as so would get the desired boolean values returned:

equalZeroes [0,1,4,5,6] [1,4,5,0,0]
-> False

 equalZeroes [0,1,4,5,6] [1,4,5,0]
 -> True

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.