8 Replies - 866 Views - Last Post: 23 September 2016 - 05:09 AM

#1 O'Niel  Icon User is offline

  • D.I.C Regular

Reputation: 14
  • View blog
  • Posts: 389
  • Joined: 13-September 15

Looping through list of tuples; two if statements

Posted 17 September 2016 - 05:10 AM

Hi

I want to make a basic Haskell program in which an user can give in the name of a locker, and see if that locker is already used or not. (not for real life use, just an exercise I thought of).

This is my code:
locker :: [(String, Bool)]
locker =
    [
    ("A1", True), 
    ("A2", True),
    ("A3", False),
    ("B1", True),
    ("B2", False),
    ("B3", False),
    ("C1", True),
    ("C2", False),
    ("C3", False)
    ]

isTaken :: (String, Bool) -> Bool
isTaken xs =
    if (snd xs) == True
        then True
        else False

isTaken' :: String -> Bool
isTaken' x y =
    if fst (locker !! y) == x
        then do
            snd $ locker !! y
            isTaken' x 0

    if y > 0
        then isTaken' x (y - 1)
        

main =
    putStrLn $ show $ isTaken' "A1" (length locker)
    putStrLn $ show $ isTaken $ locker !! 0 




The first isTaken function is working. The user just gives in the index of the locker, and it returns if the locker is taken or not.
However, the user has to give in the index; not the name.

At the isTaken' function, I get this error:
haskell_test.hs:29:5: parse error on input `if'



However, I don't see the mistake. Can't I have two if-statements?
I know in Haskell the if function isn't: "If true, do this", but "If true, this is the new value"; yet I don't now how to do this than.

My algorithm in isTaken' is this:
  • Keep doing recursive 'loop' until the tuple with given name is found
  • If found: Return the second value (True/False => Free/Taken)
  • Call the function with y parameter 0, which will break the recursive loop (see second if-statement)


Anyone knows what's wrong? Thanks!

Is This A Good Question/Topic? 0
  • +

Replies To: Looping through list of tuples; two if statements

#2 sepp2k  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 2517
  • View blog
  • Posts: 4,001
  • Joined: 21-June 11

Re: Looping through list of tuples; two if statements

Posted 17 September 2016 - 05:53 AM

isTaken :: (String, Bool) -> Bool
isTaken xs =
    if (snd xs) == True
        then True
        else False


if something == True is just a more verbose way of writing if something (note that == itself just returns True or False, so you might as well write if (something == True) == True) and if something then True else False is just a more verbose way of writing something. So the above can just be written as:

isTaken xs = snd xs



Or more readably using pattern matching:

isTaken (_, taken) = taken



isTaken' :: String -> Bool
isTaken' x y =



Your type signature does not match your number of arguments. I also feel like your function should take the locker list as an argument.

do
    snd $ locker !! y
    isTaken' x 0



You should really review some basic material on how monads and do-statements work (or just stop trying to use them) before you continue trying to write programs. Bool is not a monad, so you can't use do-notation with it. And if it were, it most likely wouldn't have the semantics you're looking for. In fact it is not entirely clear, what you're looking for here. What do you expect the value of isTaken' x 0 to be? And in what way do you want that value to be combined with the value of snd $ locker !! y.

Quote

Can't I have two if-statements?


Not unless they're nested in each other. You also can't have an if without an else. Remember that ifs in Haskell don't have effects, they have values. If you have two ifs after each other (or just in general two expressions), what do you expect to happen with first if's value? To be discarded? Then why have it at all? And if you have an if without an else, what should its value be in case the condition is false?

When trying to understand Haskell code, you should ask yourself "what's the value of this" rather than "what does this do". If that question doesn't make sense for a particular piece of code, the code is most likely wrong.

Quote

Call the function with y parameter 0, which will break the recursive loop


The way to stop a recursion is to not call the function recursively in that case.

PS: The usual way to iterate through a loop is through pattern matching, not by index. It's simpler and less error-prone and pattern matching is O(1), unlike indexing into a list, which is O(n).

This post has been edited by sepp2k: 17 September 2016 - 05:54 AM

Was This Post Helpful? 1
  • +
  • -

#3 baavgai  Icon User is offline

  • Dreaming Coder
  • member icon


Reputation: 6996
  • View blog
  • Posts: 14,635
  • Joined: 16-October 07

Re: Looping through list of tuples; two if statements

Posted 17 September 2016 - 03:23 PM

If you find yourself checking the length of the list in haskell, take a step back: chances are you're doing it wrong. Also, if you're going to keep the list you're iterating in scope, it doesn't make a whole lot of sense to also have to pass the length. Of course, if you're allowed to use length, then I would consider looking at filter.
Was This Post Helpful? 1
  • +
  • -

#4 O'Niel  Icon User is offline

  • D.I.C Regular

Reputation: 14
  • View blog
  • Posts: 389
  • Joined: 13-September 15

Re: Looping through list of tuples; two if statements

Posted 18 September 2016 - 11:09 AM

I have no clue about how to do this in Haskell:
locker :: [(String, Bool)]
locker =
    [
    ("A1", True), 
    ("A2", True),
    ("A3", False),
    ("B1", True),
    ("B2", False),
    ("B3", False),
    ("C1", True),
    ("C2", False),
    ("C3", False)
    ]

isTaken :: (String, Bool) -> Bool
isTaken xs =
    if (snd xs) == True
        then True
        else False

isTaken' :: String -> [a] -> Bool
isTaken' name xs =
    

main =
    putStrLn $ show $ isTaken' "A1" locker
    --putStrLn $ show $ isTaken $ locker !! 0





How'd I make the isTaken' function, so that the user gives in a name of a locker, and then returns the second element of the tuple from which the first element matches the given name?

I know about pattern matching, but have no clue about how to do it with a list of tuples.

I could use this pattern match:
isTaken' (a, B)/>/> = b



But now I have to find a way, to make 'a' be the name of the locker. How do I do this pattern matching for a list of tuples?

I tried this:
isTaken' a [(a, B)/>] = b



(Return b of the tuple (a,B) and a is the given parameter-name).
But it ain't working.

This post has been edited by O'Niel: 18 September 2016 - 11:22 AM

Was This Post Helpful? 0
  • +
  • -

#5 sepp2k  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 2517
  • View blog
  • Posts: 4,001
  • Joined: 21-June 11

Re: Looping through list of tuples; two if statements

Posted 18 September 2016 - 11:52 AM

The pattern [(a, b )] matches only if the given list has exactly one element. When taking apart a list, you generally want to match on the empty list [] (which in your case is going to be hit when the given name isn't found in the list) and the non-empty list x : xs. You also can't re-use the same name in a pattern match, so you'll have to give the first argument and the second element of the tuple different names and then us a guard or an if to check whether they're equal. So the general structure would be:

isTaken' name ((lockername, taken) : lockers) =
    if name == lockername
    then taken
    else -- Code for when the names are not the same
isTaken _ [] =
    -- Code for when the name's not in the list


Was This Post Helpful? 1
  • +
  • -

#6 O'Niel  Icon User is offline

  • D.I.C Regular

Reputation: 14
  • View blog
  • Posts: 389
  • Joined: 13-September 15

Re: Looping through list of tuples; two if statements

Posted 22 September 2016 - 01:29 AM

Thanks a lot!

Working code:
import System.IO

lockers :: [(String, Bool)]
lockers = 
        [
        ("A1", True),
        ("A2", True),
        ("A3", False),
        ("B1", True),
        ("B2", False),
        ("B3", False),
        ("C1", True)
        ]

--checkLocker :: String -> [(a, B)/>/>] -> Bool
isTaken name ((lockerName, taken) : lockerList) =
        if name == lockerName
                then taken
        else False
isTaken _ [] = False

getInput txt = do 
        putStr txt
        hFlush stdout
        getLine

outputLockerInfo :: Bool -> String
outputLockerInfo x =
        if x
                then "Locker is already taken."
                else "Locker is free."

main = do
        putStrLn "Check if a locker is already taken."
        
        lockerToCheck <- getInput "Locker: "       
        putStrLn $ outputLockerInfo $ isTaken lockerToCheck lockers        
        




Explanation:
isTaken name ((lockerName, taken) : lockerList) =
        if name == lockerName
                then taken
        else False
isTaken _ [] = False



((lockername, taken) : lockerlist)
,
is actually the same as (xs:x), but because our list (xs) exists out of tuples, we need to define that
((lockername, taken) : lockerlist)

(xs:x) is a parameter-function-thing is a 'thing' to 'loop' through each element of the list by splitting the back up-and-of each time. Right?

Then, if the given parameter 'name' equals one element of that list, we return taken which is a part of an element in our list.
Is this all right?
isTaken _ [] = False

I don't understand this part.
I know _ means a 'variable without a name', we use this when we need a variable but it's name doesn't really matter because we won't really use it.
And [] means list.

And we use this to check if a given name is not in the list; but I don't understand the algorithm behind it.
Is it, if this name-parameter is given, (_) and we get an empty list as return?

Thanks!

This post has been edited by O'Niel: 22 September 2016 - 01:34 AM

Was This Post Helpful? 0
  • +
  • -

#7 sepp2k  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 2517
  • View blog
  • Posts: 4,001
  • Joined: 21-June 11

Re: Looping through list of tuples; two if statements

Posted 22 September 2016 - 04:07 AM

First of all there's a mistake in your algorithm. If you try to use it on A2 instead of A1, you'll get False as the result. The below clarifications should explain why.

View PostO, on 22 September 2016 - 10:29 AM, said:


((lockername, taken) : lockerlist)
,
is actually the same as (xs:x), but because our list (xs) exists out of tuples, we need to define that
((lockername, taken) : lockerlist)


You don't need to write (lockername, taken) - you could just write (locker : lockers), but then locker is a tuple and you need to use fst and snd to access its name or taken status. By writing (lockername, taken) instead, you don't have to use the fst and snd functions. And lockername is of course much more of a speaking name than "fst locker" would be.

Quote

(xs:x) is a parameter-function-thing is a 'thing' to 'loop' through each element of the list by splitting the back up-and-of each time. Right?


No, it's a pattern and it doesn't loop through anything.

The constructor : creates a non-empty list where the left operand is the first element of the list (the head) and the right operand is a list containing the remaining elements (the tail). So if you write myList = 1 : [2, 3], you get the list [1,2,3].

Now the pattern : is the exact counter-part to that. If you have a pattern p1 : p2, this pattern will match exactly when applied to a non-empty list whose first element matches the pattern p1 and whose list of remaining elements matches the pattern p2. So if you write f (x : xs) = ..., this case of the function will be used when the argument is a non-empty list (and x and xs will refer to the head and tail of the list respectively) and if you write f (42 : xs) = ... that case will apply only if the list is non-empty and its first element is 42.

Quote

Then, if the given parameter 'name' equals one element of that list, we return taken which is a part of an element in our list.
Is this all right?


No, you only return taken when the parameter equals the first locker's name. Otherwise you return False. You'll need to use recursion to get the behavior you want.

Quote

isTaken _ [] = False

I don't understand this part.
I know _ means a 'variable without a name', we use this when we need a variable but it's name doesn't really matter because we won't really use it.
And [] means list.


Specifically it means the empty list. Like if you write myList = [], myList is an empty list. And if you use [] as pattern, it will match only the empty list.

Quote

And we use this to check if a given name is not in the list; but I don't understand the algorithm behind it.
Is it, if this name-parameter is given, (_) and we get an empty list as return?


"if the name-parameter is given" isn't really a condition. You can't apply a function without giving a value for its parameters. The pattern says "if the second parameter is the empty list (no condition on the first parameter)". The condition would be the same if we had written isTaken name [] = False, but, as you said, we use _ since we don't actually need to use the variable name in the code (after all the code is just False).

The logic here is that the first case of the function (the non-empty case) is supposed to be recursive. So if the item is not in the list, we'll eventually have gone through all the items and apply isTaken to the empty list. And when that happens, we reach this case.

This post has been edited by sepp2k: 22 September 2016 - 06:56 AM

Was This Post Helpful? 2
  • +
  • -

#8 O'Niel  Icon User is offline

  • D.I.C Regular

Reputation: 14
  • View blog
  • Posts: 389
  • Joined: 13-September 15

Re: Looping through list of tuples; two if statements

Posted 22 September 2016 - 11:56 PM

Thanks a lot!
This is the working code than:
import System.IO

lockers :: [(String, Bool)]
lockers = 
        [
        ("A1", True),
        ("A2", True),
        ("A3", False),
        ("B1", True),
        ("B2", False),
        ("B3", False),
        ("C1", True)
        ]

--isTaken :: a -> [(a, B)/>] -> b
isTaken name ((lockerName, taken) : lockerList) =
        if name == lockerName
                then taken
        else False
isTaken _ [] = False

getInput txt = do 
        putStr txt
        hFlush stdout
        getLine

outputLockerInfo :: Bool -> String
outputLockerInfo x =
        if x == True
                then "Locker is already taken."
                else "Locker is free."

main = do
        putStrLn "Check if a locker is already taken."
        
        lockerToCheck <- getInput "Locker: "       
        putStrLn $ outputLockerInfo $ isTaken lockerToCheck lockers
        




However, how'd I do the commented type signature on line 16?
When I change b to Bool I get: "Expected Bool but b given", but when I change Bool to b; I get the same error but reversed.
Was This Post Helpful? 0
  • +
  • -

#9 sepp2k  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 2517
  • View blog
  • Posts: 4,001
  • Joined: 21-June 11

Re: Looping through list of tuples; two if statements

Posted 23 September 2016 - 05:09 AM

View PostO, on 23 September 2016 - 08:56 AM, said:

This is the working code than:


This looks exactly like your non-working code. Are you sure you posted the right code?

Quote

However, how'd I do the commented type signature on line 16?
When I change b to Bool I get: "Expected Bool but b given", but when I change Bool to b; I get the same error but reversed.


Please post the exact pieces of code that you've tried with the exact error messages. Since you use taken as an if condition, it does need to have type Bool. If you get the reverse error message after changing it, you probably didn't change it in all places.

Also note that you're comparing lockerName for equality, so it must have at least a type that implements Eq (if you don't want to hardcode the type as String).
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1