Difference between revisions of "FP Laboratory 11"

From Marek Běhálek Wiki
Jump to navigation Jump to search
 
(30 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
<translate>
 
<translate>
== Binary Trees == <!--T:1-->
+
== Complex data structure == <!--T:6-->
* Create a data type <code>Tree</code> that defines binary tree where values are stored in leaves and also in branches.  
+
Consider following data structure representing some kind of GUI.
 +
</translate>
 +
 
 +
<syntaxhighlight lang="Haskell">
 +
data Point = Point {column::Int,row::Int} deriving (Show)
 +
 
 +
data Position = Position {leftTopCorner :: Point, width :: Int, height :: Int}
 +
 
 +
data Component
 +
  = TextBox {name :: String, position :: Position, text :: String}
 +
  | Button {name :: String, position :: Position, text :: String}
 +
  | Container {name :: String, children :: [Component]}
 +
</syntaxhighlight>
 +
 
 +
<translate>
 +
<!--T:7-->
 +
As an example, we can use following data structure.
 
</translate>
 
</translate>
  
<div class="mw-collapsible mw-collapsed" data-collapsetext="Hide solution" data-expandtext="Show solution">
 
 
<syntaxhighlight lang="Haskell">
 
<syntaxhighlight lang="Haskell">
data Tree a = Leaf a
+
gui :: Component
            | Branch a (Tree a) (Tree a) deriving (Show)
+
gui =
 +
  Container "My App"
 +
    [ Container "Menu"
 +
        [ Button "btn_new" (Position (Point 0 0) 100 20) "New",
 +
          Button "btn_open" (Position (Point 100 0) 100 20) "Open",
 +
          Button "btn_close" (Position (Point 200 0) 100 20) "Close"
 +
        ],
 +
      Container "Body" [TextBox "textbox_1" (Position (Point 0 20) 300 500) "Some text goes here"],
 +
      Container "Footer" []
 +
    ]
 
</syntaxhighlight>
 
</syntaxhighlight>
</div>
 
<div style="clear:both"></div>
 
  
 
<translate>
 
<translate>
<!--T:2-->
+
<!--T:8-->
* Prepare an example of a binary tree.
+
* Add the data type <code>Component</code> into the type class <code>Show</code>.
 +
 
 +
<!--T:9-->
 +
The result for our data from previous example should be something like this.
 
</translate>
 
</translate>
 +
 +
<syntaxhighlight lang="Haskell" class="myDark">
 +
ghci> gui
 +
Container - My App
 +
        Container - Menu
 +
                (0,0)[100,20] Button[btn_new]: New
 +
                (100,0)[100,20] Button[btn_open]: Open
 +
                (200,0)[100,20] Button[btn_close]: Close
 +
        Container - Body
 +
                (0,20)[300,500] TextBox[textbox_1]: Some text goes here
 +
        Container - Footer
 +
</syntaxhighlight>
  
 
<div class="mw-collapsible mw-collapsed" data-collapsetext="Hide solution" data-expandtext="Show solution">
 
<div class="mw-collapsible mw-collapsed" data-collapsetext="Hide solution" data-expandtext="Show solution">
 
<syntaxhighlight lang="Haskell">
 
<syntaxhighlight lang="Haskell">
testTree1 :: Tree Int           
+
instance Show Position where
testTree1 = Branch 12 (Branch 23 (Leaf 34) (Leaf 45)) (Leaf 55)
+
    show (Position (Point col row) width height) = "(" ++ show col ++ "," ++ show row ++ ")["++ show width++","++ show height++"]"
  
testTree2 :: Tree Char           
+
instance Show Component where
testTree2 = Branch 'a' (Branch 'b' (Leaf 'c') (Leaf 'd')) (Leaf 'e')
+
    show :: Component -> String
 +
    show gui = showIndent "" gui where
 +
        showIndent ind (TextBox name position text) = ind ++ show position ++ " TextBox[" ++ name ++ "]: " ++ text ++"\n"
 +
        showIndent ind (Button name position text) = ind ++ show position ++ " Button[" ++ name ++ "]: " ++ text ++"\n"
 +
        showIndent ind (Container name children) = let
 +
            inner = concat[showIndent (ind++"\t") c |c<-children]
 +
            in ind ++ "Container - " ++ name ++ "\n" ++ inner
 
</syntaxhighlight>
 
</syntaxhighlight>
 
</div>
 
</div>
Line 29: Line 72:
  
 
<translate>
 
<translate>
<!--T:3-->
+
<!--T:10-->
* Create a function that sums all values stored in the tree.
+
* Cerate a function <code>insertInto</code>, it will insert an element into the existing container from a GUI. The functions parameters will be:
 +
** first parameter will be the GUI, where we are inserting the new element;
 +
** second parameter is the name of the container, where we insert the new element, you can safely assume, that it will always exist. The element will be placed as last in the container;
 +
** last parameter is the inserted element.
 
</translate>
 
</translate>
  
 
<syntaxhighlight lang="Haskell">
 
<syntaxhighlight lang="Haskell">
sum' :: Tree Int -> Int
+
insertInto :: Component -> String -> Component -> Component
 +
</syntaxhighlight>
 +
 
 +
<syntaxhighlight lang="Haskell" class="myDark">
 +
ghci> insertInto gui "Footer" (TextBox "Done" (Position (Point 0 500) 300 10) "We are done!")
 +
Container - My App
 +
        Container - Menu
 +
                (0,0)[100,20] Button[btn_new]: New
 +
                (100,0)[100,20] Button[btn_open]: Open
 +
                (200,0)[100,20] Button[btn_close]: Close
 +
        Container - Body
 +
                (0,20)[300,500] TextBox[textbox_1]: Some text goes here
 +
        Container - Footer
 +
                (0,500)[300,10] TextBox[Done]: We are done!
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
<div class="mw-collapsible mw-collapsed" data-collapsetext="Hide solution" data-expandtext="Show solution">
 
<div class="mw-collapsible mw-collapsed" data-collapsetext="Hide solution" data-expandtext="Show solution">
 
<syntaxhighlight lang="Haskell">
 
<syntaxhighlight lang="Haskell">
sum' :: Tree Int -> Int
+
insertInto :: Component -> String -> Component -> Component
sum' (Leaf x) = x
+
insertInto (Container cName children ) toName element
sum' (Branch x l r) = sum' l + x + sum' r
+
    | cName == toName = Container cName (children++[element])  
 +
    | otherwise = Container cName [insertInto c toName element  |c<-children]
 +
insertInto x toName element = x    
 
</syntaxhighlight>
 
</syntaxhighlight>
 
</div>
 
</div>
Line 47: Line 108:
  
 
<translate>
 
<translate>
<!--T:4-->
+
<!--T:22-->
* Create a function that extracts all values from the tree into an list.
+
* Create a function <code>deleteFrom</code>, it will delete an element from the existing container from a GUI. The functions parameters will be:
 +
** first parameter will be the GUI, where we are deleting the element;
 +
** second parameter is the name of the component, that will be removed, you can safely assume, that it will always exist;
 
</translate>
 
</translate>
  
 
<syntaxhighlight lang="Haskell">
 
<syntaxhighlight lang="Haskell">
toList :: Tree a -> [a]
+
deleteFrom :: Component -> String -> Component
 +
</syntaxhighlight>
 +
 
 +
<syntaxhighlight lang="Haskell" class="myDark">
 +
ghci> deleteFrom gui "btn_close"           
 +
Container - My App
 +
  Container - Menu
 +
    (0,0)[100,20] Button[btn_new]: New
 +
    (100,0)[100,20] Button[btn_open]: Open
 +
  Container - Body
 +
    (0,20)[300,500] TextBox[textbox_1]: Some text goes here
 +
  Container - Footer
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
<div class="mw-collapsible mw-collapsed" data-collapsetext="Hide solution" data-expandtext="Show solution">
 
<div class="mw-collapsible mw-collapsed" data-collapsetext="Hide solution" data-expandtext="Show solution">
 
<syntaxhighlight lang="Haskell">
 
<syntaxhighlight lang="Haskell">
toList :: Tree a -> [a]
+
deleteFrom :: Component -> String -> Component
toList (Leaf x) = [x]
+
deleteFrom (Container x child) target = Container x [deleteFrom c target |c<-child, name c /=target ]
toList (Branch x l r) = toList l ++ [x] ++ toList r
+
deleteFrom c _ = c
 
</syntaxhighlight>
 
</syntaxhighlight>
 
</div>
 
</div>
Line 65: Line 139:
  
 
<translate>
 
<translate>
<!--T:5-->
+
<!--T:11-->
* One possibility how to represent a tree in a textual form is <code>a(b(d,e),c(e,f(g,h)))</code>. Create functions that are able to read and store a tree in such a notation.
+
* Extend the definition of a button in our GUI as follows.
</translate>  
+
</translate>
  
<div style="float: right"> [[File:Video logo.png|80px|link=https://youtu.be/b0EF9WQw-uw]]</div>
 
 
<syntaxhighlight lang="Haskell">
 
<syntaxhighlight lang="Haskell">
toString :: Show a => Tree a -> String
+
data Event = MouseEvent Point
fromString :: Read a => String -> Tree a
+
          | KeyEvent {keyPressed::Char} deriving (Show)
 +
...
 +
  | Button {name :: String, position :: Position, text :: String, onClick :: Maybe (Event -> String)}
 +
...
 +
</syntaxhighlight>
 +
<translate>
 +
<!--T:12-->
 +
Our '''onClick''' is a function, that will be fired when the button is clicked on. The parameter of this function is data describing the firing event.
 +
</translate>
 +
 
 +
<syntaxhighlight lang="Haskell">
 +
...
 +
[ Button "btn_new" (Position (Point 0 0) 100 20) "New" (Just (\event -> "Clicked on new button.")),
 +
  Button "btn_open" (Position (Point 100 0) 100 20) "Open" Nothing,
 +
  Button "btn_close" (Position (Point 200 0) 100 20) "Close" (Just (\event -> "Clicked on close button.")) ]
 +
...
 +
</syntaxhighlight>
 +
<translate>
 +
<!--T:13-->
 +
* Create a function <code>clickOnButton</code> that will take our GUI and an event. If it is a mouse event, and the position where we have clicked is inside some of the buttons from the gui, then it evaluates the coresponding <code>onClick</code> function and the result will be produced string. In all other cases, the result will be <code>Nothing</code>.
 +
</translate>
 +
 
 +
<syntaxhighlight lang="Haskell">
 +
clickOnButton :: Component -> Event -> Maybe String
 +
</syntaxhighlight>
 +
 
 +
<syntaxhighlight lang="Haskell" class="myDark">
 +
ghci> clickOnButton gui (MouseEvent (Point 5 5))
 +
Just "Clicked on new button."
 +
ghci> clickOnButton gui (MouseEvent (Point 205 5))
 +
Just "Clicked on close button."
 +
ghci> clickOnButton gui (MouseEvent (Point 205 50))
 +
Nothing
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
<div class="mw-collapsible mw-collapsed" data-collapsetext="Hide solution" data-expandtext="Show solution">
 
<div class="mw-collapsible mw-collapsed" data-collapsetext="Hide solution" data-expandtext="Show solution">
 
<syntaxhighlight lang="Haskell">
 
<syntaxhighlight lang="Haskell">
toString :: Show a => Tree a -> String
+
isInside :: Point -> Position -> Bool
toString (Leaf x) = show x
+
isInside (Point pCol pRow) (Position (Point cornerCol cornerRow) width height) =
toString (Branch x l r) = show x ++ "(" ++ (toString l) ++ "," ++ (toString r) ++ ")"
+
    cornerCol <= pCol && pCol <= cornerCol + width && cornerRow <= pRow && pRow <= cornerRow + height
  
fromString :: Read a => String -> Tree a
+
getFirstOrNothing :: [Maybe a] -> Maybe a
fromString inp = fst (fromString' inp) where
+
getFirstOrNothing [] = Nothing
  fromString' :: Read a => String -> (Tree a,String)
+
getFirstOrNothing (Nothing:xs) = getFirstOrNothing xs
  fromString' inp =
+
getFirstOrNothing (Just x: xs) = Just x
    let
+
 
      before = takeWhile (\x ->  x /='(' &&  x /=',' &&  x/=')') inp
+
clickOnButton :: Component -> Event -> Maybe String
      after = dropWhile (\x ->  x /='(' &&  x /=',' && x/=')') inp
+
clickOnButton (Button {position = pos, onClick = (Just func)}) (MouseEvent point) | isInside point pos = Just (func (MouseEvent point))
      value = read before
+
clickOnButton (Container {children=inner}) event = getFirstOrNothing [clickOnButton c event |c<-inner]
    in if null after || head after /= '(' then (Leaf value, after) else
+
clickOnButton _ _ = Nothing
        let
 
          (l,after') = fromString' (tail after)
 
          (r,after'') = fromString' (tail after')
 
        in (Branch value l r, tail after'')
 
 
</syntaxhighlight>
 
</syntaxhighlight>
 
</div>
 
</div>
 
<div style="clear:both"></div>
 
<div style="clear:both"></div>
  
 +
<translate>
 +
== Additional exercises == <!--T:14-->
 +
</translate>
  
 
<translate>
 
<translate>
== Additional exercises ==
+
<!--T:15-->
* Implement a function that counts all leaves in the tree.
+
* Consider the following definition and the example of the m-ary tree.
 
</translate>
 
</translate>
 +
 
<syntaxhighlight lang="Haskell">
 
<syntaxhighlight lang="Haskell">
leafCount :: Tree a -> Int
+
data MTree a = MTree a [MTree a]
 
</syntaxhighlight>
 
</syntaxhighlight>
  
<translate>
 
* Implement a function that counts all branches in the tree.
 
</translate>
 
 
<syntaxhighlight lang="Haskell">
 
<syntaxhighlight lang="Haskell">
branchCount :: Tree a -> Int
+
testTree1 :: MTree Int          
 +
testTree1 = MTree 1 [(MTree 2 [(MTree 3 []),(MTree 4 [(MTree 5 []),(MTree 6 [])]), (MTree 7 []),(MTree 8 [])]), (MTree 9 [])]
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
<translate>
 
<translate>
* Implement a function that checks whether a given element is stored in the tree.  
+
<!--T:16-->
 +
* Create a function that sums all values stored in the m-ary tree.
 
</translate>
 
</translate>
 +
 
<syntaxhighlight lang="Haskell">
 
<syntaxhighlight lang="Haskell">
contains :: Eq a => Tree a -> a -> Bool
+
msum :: MTree Int -> Int
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
<translate>
 
<translate>
* Implement a function that finds a maximum value stored in the tree.
+
<!--T:17-->
 +
* Create a function that extracts all values from the m-ary tree into a list.
 
</translate>
 
</translate>
 +
 
<syntaxhighlight lang="Haskell">
 
<syntaxhighlight lang="Haskell">
maxTree :: Ord a => Tree a -> a
+
mToList :: MTree a -> [a]
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
<translate>
 
<translate>
* Implement a function that returns a number of elements greater than a given value.
+
<!--T:18-->
 +
* Create a function that counts all leaves in the m-ary tree.
 
</translate>
 
</translate>
 
<syntaxhighlight lang="Haskell">
 
<syntaxhighlight lang="Haskell">
greaterThan :: Ord a => Tree a -> a -> Int
+
mLeafCount :: MTree a -> Int
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
<translate>
 
<translate>
* Implement a function that returns the depth of a tree.
+
<!--T:19-->
 +
* Create a function that finds a maximum value stored in the m-ary tree.
 
</translate>
 
</translate>
 +
 
<syntaxhighlight lang="Haskell">
 
<syntaxhighlight lang="Haskell">
depthTree :: Tree a -> Int
+
mMaxTree :: Ord a => MTree a -> a
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
<translate>
 
<translate>
* Consider the following alternative definition of the binary tree.
+
<!--T:20-->
 +
* Create a function that checks whether a given element is stored in the m-ary tree.  
 
</translate>
 
</translate>
  
 
<syntaxhighlight lang="Haskell">
 
<syntaxhighlight lang="Haskell">
data Tree2 a = Null | Branch a (Tree2 a) (Tree2 a)
+
mContains :: Eq a => MTree a -> a -> Bool
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 
<translate>
 
<translate>
Is this definition equivalent to the previous one? If not, explain why and give an example of a tree that can be constructed with one definition but not with the second one.
+
<!--T:21-->
 +
* Create a function that returns a number of elements greater than a given value.
 
</translate>
 
</translate>
 +
<syntaxhighlight lang="Haskell">
 +
mGreaterThan :: Ord a => MTree a -> a -> Int
 +
</syntaxhighlight>

Latest revision as of 11:30, 19 November 2024

Complex data structure

Consider following data structure representing some kind of GUI.

data Point = Point {column::Int,row::Int} deriving (Show)

data Position = Position {leftTopCorner :: Point, width :: Int, height :: Int} 

data Component
  = TextBox {name :: String, position :: Position, text :: String}
  | Button {name :: String, position :: Position, text :: String}
  | Container {name :: String, children :: [Component]}

As an example, we can use following data structure.

gui :: Component
gui =
  Container "My App"
    [ Container "Menu"
        [ Button "btn_new" (Position (Point 0 0) 100 20) "New",
          Button "btn_open" (Position (Point 100 0) 100 20) "Open",
          Button "btn_close" (Position (Point 200 0) 100 20) "Close"
        ],
      Container "Body" [TextBox "textbox_1" (Position (Point 0 20) 300 500) "Some text goes here"],
      Container "Footer" []
    ]
  • Add the data type Component into the type class Show.

The result for our data from previous example should be something like this.

ghci> gui
Container - My App
        Container - Menu
                (0,0)[100,20] Button[btn_new]: New
                (100,0)[100,20] Button[btn_open]: Open
                (200,0)[100,20] Button[btn_close]: Close
        Container - Body
                (0,20)[300,500] TextBox[textbox_1]: Some text goes here
        Container - Footer
instance Show Position where
    show (Position (Point col row) width height) = "(" ++ show col ++ "," ++ show row ++ ")["++ show width++","++ show height++"]"

instance Show Component where
    show :: Component -> String
    show gui = showIndent "" gui where
        showIndent ind (TextBox name position text) = ind ++ show position ++ " TextBox[" ++ name ++ "]: " ++ text ++"\n" 
        showIndent ind (Button name position text) = ind ++ show position ++ " Button[" ++ name ++ "]: " ++ text ++"\n"
        showIndent ind (Container name children) = let 
            inner = concat[showIndent (ind++"\t") c |c<-children]
            in ind ++ "Container - " ++ name ++ "\n" ++ inner
  • Cerate a function insertInto, it will insert an element into the existing container from a GUI. The functions parameters will be:
    • first parameter will be the GUI, where we are inserting the new element;
    • second parameter is the name of the container, where we insert the new element, you can safely assume, that it will always exist. The element will be placed as last in the container;
    • last parameter is the inserted element.
insertInto :: Component -> String -> Component -> Component
ghci> insertInto gui "Footer" (TextBox "Done" (Position (Point 0 500) 300 10) "We are done!")
Container - My App
        Container - Menu
                (0,0)[100,20] Button[btn_new]: New
                (100,0)[100,20] Button[btn_open]: Open
                (200,0)[100,20] Button[btn_close]: Close
        Container - Body
                (0,20)[300,500] TextBox[textbox_1]: Some text goes here
        Container - Footer
                (0,500)[300,10] TextBox[Done]: We are done!
insertInto :: Component -> String -> Component -> Component
insertInto (Container cName children ) toName element 
    | cName == toName = Container cName (children++[element]) 
    | otherwise = Container cName [insertInto c toName element  |c<-children]
insertInto x toName element = x
  • Create a function deleteFrom, it will delete an element from the existing container from a GUI. The functions parameters will be:
    • first parameter will be the GUI, where we are deleting the element;
    • second parameter is the name of the component, that will be removed, you can safely assume, that it will always exist;
deleteFrom :: Component -> String -> Component
ghci> deleteFrom gui "btn_close"            
Container - My App
  Container - Menu
    (0,0)[100,20] Button[btn_new]: New
    (100,0)[100,20] Button[btn_open]: Open
  Container - Body
    (0,20)[300,500] TextBox[textbox_1]: Some text goes here
  Container - Footer
deleteFrom :: Component -> String -> Component
deleteFrom (Container x child) target = Container x [deleteFrom c target |c<-child, name c /=target ]
deleteFrom c _ = c
  • Extend the definition of a button in our GUI as follows.
data Event = MouseEvent Point
           | KeyEvent {keyPressed::Char} deriving (Show)
...
  | Button {name :: String, position :: Position, text :: String, onClick :: Maybe (Event -> String)}
...

Our onClick is a function, that will be fired when the button is clicked on. The parameter of this function is data describing the firing event.

...
[ Button "btn_new" (Position (Point 0 0) 100 20) "New" (Just (\event -> "Clicked on new button.")),
  Button "btn_open" (Position (Point 100 0) 100 20) "Open" Nothing,
  Button "btn_close" (Position (Point 200 0) 100 20) "Close" (Just (\event -> "Clicked on close button.")) ]
...
  • Create a function clickOnButton that will take our GUI and an event. If it is a mouse event, and the position where we have clicked is inside some of the buttons from the gui, then it evaluates the coresponding onClick function and the result will be produced string. In all other cases, the result will be Nothing.
clickOnButton :: Component -> Event -> Maybe String
ghci> clickOnButton gui (MouseEvent (Point 5 5))
Just "Clicked on new button."
ghci> clickOnButton gui (MouseEvent (Point 205 5))
Just "Clicked on close button."
ghci> clickOnButton gui (MouseEvent (Point 205 50))
Nothing
isInside :: Point -> Position -> Bool
isInside (Point pCol pRow) (Position (Point cornerCol cornerRow) width height) =
     cornerCol <= pCol && pCol <= cornerCol + width && cornerRow <= pRow && pRow <= cornerRow + height

getFirstOrNothing :: [Maybe a] -> Maybe a
getFirstOrNothing [] = Nothing
getFirstOrNothing (Nothing:xs) = getFirstOrNothing xs
getFirstOrNothing (Just x: xs) = Just x

clickOnButton :: Component -> Event -> Maybe String
clickOnButton (Button {position = pos, onClick = (Just func)}) (MouseEvent point) | isInside point pos = Just (func (MouseEvent point))
clickOnButton (Container {children=inner}) event = getFirstOrNothing [clickOnButton c event |c<-inner]
clickOnButton _ _ = Nothing

Additional exercises

  • Consider the following definition and the example of the m-ary tree.
data MTree a = MTree a [MTree a]
testTree1 :: MTree Int            
testTree1 = MTree 1 [(MTree 2 [(MTree 3 []),(MTree 4 [(MTree 5 []),(MTree 6 [])]), (MTree 7 []),(MTree 8 [])]), (MTree 9 [])]
  • Create a function that sums all values stored in the m-ary tree.
msum :: MTree Int -> Int
  • Create a function that extracts all values from the m-ary tree into a list.
mToList :: MTree a -> [a]
  • Create a function that counts all leaves in the m-ary tree.
mLeafCount :: MTree a -> Int
  • Create a function that finds a maximum value stored in the m-ary tree.
mMaxTree :: Ord a => MTree a -> a
  • Create a function that checks whether a given element is stored in the m-ary tree.
mContains :: Eq a => MTree a -> a -> Bool
  • Create a function that returns a number of elements greater than a given value.
mGreaterThan :: Ord a => MTree a -> a -> Int