Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
lfa:2023:lab01 [2024/10/05 15:47]
stefan.sterea
lfa:2023:lab01 [2024/10/06 21:29] (current)
stefan.sterea old revision restored (2023/10/08 15:08)
Line 1: Line 1:
-====== ​1. Deterministic Finite Automata ​======+====== ​Programming introduction - Python ​======
  
-===== 2.1. DFA practice =====+**Python3** is a multi-paradigm language, that combines the **imperative** style with the **functional** style. It also supports **Object-Oriented** programming,​ albeit in a limited way.  
 + 
 +**Python3** offers a few programming constructs that: 
 +  * make life real easy for the programmer,  
 +  * yield efficient programs, 
 +  * sum-up to a programming **style** called **pythonic** (a blend of imperative and functional features).  
 + 
 +This lab will introduce some of these constructs. 
 + 
 +==== For Python beginners ​==== 
 + 
 +If you are completely unfamiliar with Python, some of the biggest differences to a language like **C** are: 
 +    - there are no curly braces, Python uses a combination of identation and colons (`:`) to inidicate blocks of code 
 +    - variables **DO NOT** have associated types, they are labels that point to objects (similar with pointers in **C**) 
 +    - Python works like a scripting language, every line of code is interpreted in order, to follow best practices encapsulate your code in functions and classes 
 +    - comments are made using the `#` character
  
 <​note>​ <​note>​
-  * Double-circle = final state +Any code that is outside of a function will get interpreted every time that file gets run, but also when it's imported in another project. 
-  * Hanging arrow entering state = initial state + 
-  * Sink state = a state that is not final; once reached, there is no transition ​that leaves ​it, thus the DFA will reject; there is a transition that loops in this state for each possible character+To avoid thisencapsulate your code using functions. 
 + 
 +To run the code **ONLY** when you want to run it directly, not when it gets imported ​in other files, you can use: 
 + 
 +<code python>​ 
 +def main(): 
 +    ...  # your code here 
 +     
 +if __name__ == "​__main__":​ 
 +    main() 
 +</​code>​ 
 </​note>​ </​note>​
  
 +==== Lists in python ====
 +
 +Python does **NOT** offer fixed-sized arrays, but they offer lists out of the box.
 +
 +<code python>
 +
 +l = [1, 2, 3, 4]  # defining a list
 +
 +l.append(5)
 +# l is now [1, 2, 3, 4, 5]
 +
 +l.extend([6,​ 7, 8])
 +# l is now [1, 2, 3, 4, 5, 6, 7, 8]
 +
 +# delete the second element
 +del l[2]
 +# l is now [1, 2, 4, 5, 6, 7, 8]
 +
 +# accesing lists
 +l[0] # 1
 +l[1] # 2
 +l[-1] # 8 (you can also acces them from the back)
 +
 +lr = l.reverse() ​ # [8, 7, 6, 5, 4, 2, 1]
 +
 +# checking if a element is in a list
 +2 in [1, 2, 4]  # true
 +3 in [1, 2, 4]  # false
 +
 +</​code>​
 +
 +==== Slicing lists ====
 +
 +Python supports various list slicing operations:
 +
 +<code python>
 +# the sublist from positions 0 to x of l:
 +l[0:x]
 +# the very same list:
 +l[:x]
 +# the last three elements of a list:
 +l[-3:]
 +# the slice from n-3 to n-1, where n is the number of elements from the list
 +l[-3:-1]
 +# every second element from the second to the seventh
 +l[2:7:2]
 +</​code>​
 +
 +==== Strings ====
 +
 +Strings can be treated like lists, you can access elements and use slicing, but there are also a few specific string functions.
 +
 +<code python>
 +
 +s = "​Limbaje formale si automate"​
 +
 +s[2] # "​m"​
 +
 +s[:7] # "​Limbaje"​
 +
 +len(s) ​ # 27
 +
 +s.split() ["​Limbaje",​ "​formale",​ "​si",​ "​automate"​]
 +
 +s.starswith("​Limbaje"​) ​ # True
 +
 +s.endswith("​automate"​) ​ # True
 +
 +s.replace("​formale",​ "​neformale"​) ​ # "​Limbaje neformale si automate"​
 +
 +</​code>​
 +
 +The most widely used datatypes in Python are lists and dictionaries. Additionally,​ at LFA, we will also use: **sets**, **stacks**.
 +
 +==== Stacks ====
 +
 +In Python, **lists** are also used as stacks:
 +<code python>
 +l = []
 +
 +l.append(x) ​  # add x at the TOP of the stack (this will be the last element of the list)
 +
 +l[-1]         # this is the top of the stack
 +
 +x = l.pop() ​  # removes and returns an element from the top of the stack.
 +
 +</​code>  ​
 +
 +==== Dictionaries ====
 +
 +Python dictionaries are actually **hash maps** (or **hash tables**). The keys ''​k''​ are **dispersed** using a hash-function into buckets. Each bucket will hold its respective values. Some examples of dictionary usage:
 +
 +<code python>
 +# create an empty dictionary
 +d = {}
 +
 +# add or modify a key-value
 +d[k] = v
 +
 +# search if a key k is defined in a dictionary d
 +if k in d:
 +  # do something
 +  ​
 +# get a key or a default value if key is not in dictionary
 +d.get(k, 0)
 +
 +</​code>​
 +
 +==== Sets ====
 +
 +Sets are collections where each element is unique. Sets can be traversed and indexed exactly as lists. They are initialised using the constructor ''​set'':​
 +<code python>
 +# the empty set:
 +s = set()
 +
 +# set constructed from a list:
 +s = set([1,​2,​3])
 +
 +# normal set construction (you cannot use {} to create an empty set as that would create an empty dict)
 +s = {1,2,3}
 +
 +# adding a new element to the set:
 +s.add(4)
 +</​code>​
 +
 +==== Tuples ===
 +=
 +
 +Tuples are very similar to lists and can be substituted by the latter in many cases:
 +
 +<code python>
 +p = (x, y)     # a pair where the first element is x and the second is y
 +t = (x, y, z)  # a tuple
 +s = (x,)       # a singleton (you have to add the comma, if not python will interpret it as a variable)
 +
 +fst = p[0]
 +snd = p[1]
 +
 +# tuple unpacking
 +(fst, snd) = p
 +
 +</​code>​
 +
 +==== For loops ====
 +
 +In Python for loops are different than the traditional **C** for loops. They simply iterate over sequences (like lists, strings, tuples).
 +
 +<code python>
 +l = [1, 2, 3, 4]
 +for e in l:
 +    ...
 +    ​
 +# some useful constructs to use with for loops in python
 +
 +# range will construct a list of integers to iterate over
 +for i in range(10):
 +    ...  # iterates through [0, 1, 2, 3, ..., 9]
 +    ​
 +for i in range(5, 10):
 +    ...  # iterates through [5, 6, 7, 8, 9] 
 +
 +for i in range(5, 10, 2):
 +    ...  # iterates through [5, 7, 9] 
 +
 +# enumerate will keep a index with the element
 +for idx, e in enumerate(l):​
 +    ...
 +
 +x = [1, 2, 3, 4]
 +y = [5, 6, 7]
 +
 +for ex, ey in zip(x, y):
 +   ... iterates through [(1, 5), (2, 6), (3, 7)]
  
-=== Write DFAs for the following languages: ===+</​code>​
  
-**2.1.1.** $ L=\{w \in \{0,1\}^* \text{ | w contains an odd number of ones} \} $+==== Functions ====
  
 +Functions are very similar with *C* functions, but you don't need to add static typing to arguments or return values.
  
-/*<hidden+<code python
-{{:lfa:​2022:​lfa2022_lab2_ex1.png?​400|}} +def f(x, y)
-  * State 0 currently counted an even number of 1s [initial state] +    ​a ​2 * x + y 
-  * State 1 currently counted an odd number of 1s [final state] +    ​b ​* a + 2 x * y 
-  ​When we read 0, we stay on the same state, as we are only counting ones. +    ​return b 
-  ​When we find a one: +    ​ 
-      If we were on state 0, it means we counted until the previous step an even number of ones. Now we have an odd number of ones, so we need to move to state 1. +result = f(58) 
-      * If we were on state 1, it means we counted until the previous step an odd number of ones. Now we have an even number of onesso we need to move to state 0. +</code>
-</hidden>*/+
  
 +You can also provide default values for parameters:
 +<code python>
 +def f(x, y=0, name="​no name provided"​):​
 +   ...
 +</​code>​
  
-**2.1.2.** The language of binary words which contain **exactly** two ones+==== Classes and inheritance ====
  
 +We discuss a few basics on classes and inheritance starting from the following example:
  
-/*<hidden+<code python
-{{:lfa:​2022:​lfa2022_lab2_ex2.png?​550|}} +class Tree
-  * State 0no ones have been found yet [initial state] +    def size(self)
-  * State 1: 1 one have been found so far +        pass 
-  * State 2: 2 ones have been found so far [final state] +         
-  * State 3more than 2 ones have been found so far [sink state] +    def contains(self,​ key)
-</​hidden>​*/​+        pass
  
 +class Void(Tree):
 +    def __init__(self,​ name):
 +        self.name = name
 +    ​
 +    def __str__(self):​
 +        return self.name
  
-**2.1.3.** The language of binary words which encode odd numbers ​(the last digit is least significative)+    def size(self)
 +        return 0 
 +         
 +    def contains(self,​ key): 
 +        return False
  
 +</​code>​
  
-/​*<​hidden>​ +In the previous example, the class ''​Tree''​ acts as an interface. Python does not natively support interfaces, but class inheritance is supported. The instruction ''​pass''​ does nothing, and it helps us defer the method implementation.
-{{:​lfa:​2022:​lfa2022_lab2_ex3.png?​300|}} +
-  * We pass through ​the wordmarking ​the parity of the last character that we have read. +
-</​hidden>​*/​+
  
 +The definition ''​class Void(Tree)''​ tells us that class ''​Void''​ inherits ''​Tree''​. Note that this //​contract//​ is not binding in Python.
 +The program will be interpreted even if ''​Void''​ does not correctly implement the methods in ''​Tree''​.
  
-**2.1.4.** ​(hardThe language ​of words which encode numbers divisible by 3+The class construct is the ''​__init__(self):''​ method. Also note the mandatory presence ​of ''​self'', ​which is the Python equivalent of ''​this''​. Each member class must mark as first argument ''​self'',​ otherwise it is not a proper member of the class, and just a nested function.
  
 +The method ''​def __str__(self):''​ is the Python equivalent for ''​toString()''​. An object can be displayed using the function ''​str''​.
  
-<​hidden>​ +===== The functional style =====
-{{:​lfa:​2022:​lfa2022_lab2_ex4.png?​400|}} +
-  * State 0: 3k [initial state and final state] +
-  * State 1: 3k + 1 +
-  * State 2: 3k + 2 +
-  * When we read a 0, the number we had so far is multiplied by 2:  +
-      * 3k --(*2)-->​ 3k +
-      * 3k + 1 --(*2)-->​ 3k + 2 +
-      * 3k + 2 --(*2)-->​ 3k + 1 +
-  * When we read a 1, the number we had so far is multiplied by 2 and we also add 1:  +
-      * 3k --(*2)-->​ 3k --(+1)-->​ 3k + 1 +
-      * 3k + 1 --(*2)-->​ 3k + 2 --(+1)-->​ 3k +
-      * 3k + 2 --(*2)-->​ 3k + 1 --(+1)-->​ 3k + 2 +
-</​hidden>​+
  
 +==== Lambda functions ====
  
-**2.1.5.** The language of words that encode a calendar date (DD/​MM/​YYYY) ​ +You can declare lambda functions with the keyword ​**lambda**: 
- (do not overthink about the actual number of days in a month)+<code python>
  
 +lambda x: x + 1
  
-/​*<​hidden>​ +lambda x,yx + y
-{{:lfa:​2022:​lfa2022_lab2_ex5.png?​600|}} +
-  * Note: Checking for months with 30 or 31 days can be done with a slightly bigger DFA. +
-  * Note: Even checking for bisect years (29 Feb) can still be done with a DFA, but it's too big to do as a seminar example. +
-</​hidden>​*/​+
  
 +lambda x,y: x if x < y else y
  
-**2.1.6.** (HARD) $ L=\{w \in \{0,​1,​2,​3\}^* \text{ | w follows the rule that every zero is immediately followed by a sequence of at least 2 consecutive threes and every one is immediately followed by a sequence of at most 2 consecutive twos} \} $+</​code>​
  
 +==== Higher-order functions ====
  
-<hidden>+The most used higher-order functions in Python are **map** and **reduce** (**reduce** is part of **functools** module): 
 +<code python>
  
-{{:​lfa:​2022:​lfa2022_lab2_ex6_v2.png?​400|}} +from functools import reduce
-  * injuraturile redirectati-le catre AU :)) (si MP) +
-  * All missing transitions lead to a sink state (where there is a transition that loops in that state for any character) +
-</​hidden>​+
  
 +# adds 1 to each element of a list
 +def allplus1(l):​
 +   ​return map(lambda x: x + 1, l)
  
-**2.1.7.** The set of all binary strings having ​the substring 00101+# computes ​the sum of elements of a list 
 +def sum_list(l):​ 
 +   # inner functions are very useful for defining local, reusable functionality 
 +   def plus(x, y): 
 +      return x + y 
 +   ​return reduce(plus,​ l)  # reduce is a little different from Haskell folds: it does not use an initial value 
 +     
 +def short_sum(l):​ 
 +   ​return reduce(lambda x,y: x + y, l) 
 +    
 +</​code>​
  
 +==== List comprehensions ====
  
-/​*<​hidden>​ +List comprehensions ​are widely used programming tools in Python. ​
-{{:​lfa:​2022:​ex_8_dfa.png?​400|}} +
-  * Every state corresponds to a certain current configuration (eg. state **B** translates to a sequence of **0**; **C** to a seq of **00**; **D** -> **001** and so on) +
-  * Analyze how each incoming character changes the current available sequence. For example, if we are in state **E** and we read character **1** we reach a final state, but if we read **0** we go back to state **C** since the available seq will be **00**  +
-</​hidden>​*/​+
  
 +Usage examples:
  
-=== Describe the language ​of the following DFA and simulate them on the given inputs: ===+<code python>​ 
 +# adding 1 to each element ​of a list 
 +l1 [x + 1 for x in [1, 2, 3]] 
 +# [2, 3, 4]
  
-All states are final+# packing elements into pairs  
 +l2 = [(x, x + 1) for x in [1, 2, 3]] 
 +# [(1, 2), (2, 3), (3, 4)]
  
-**2.1.8.** w<​sub>​1</​sub> ​10011110w<​sub>​2</​sub>​ = 00110010+# unpacking pairs in the for notation 
 +l3 [x + y for (x, y) in [(1, 2), (2, 3), (3, 4)]] 
 +# [3, 5, 7]
  
-{{:​lfa:​2022:​lfa2022_lab2_ex8.png?​400|}}+# combined list comprehensions 
 +l4 = [(x, y) for x in [1, 2, 3] for y in [4, 5, 6]] 
 +# [(1, 4), (1, 5), (1, 6), (2, 4), (2, 5), (2, 6), (3, 4), (3, 5), (3, 6)]
  
-/​*<​hidden>​ +# filters 
-  * The language of binary words where there are no sequences ​in which the same character repeats 3 or more times. +l5 = [x for x in [1, 234if x 2
-  * regex: $math[(11 \cup \cup \epsilon) ( (00 \cup 0) 1 (1 \cup \epsilon) )^* (00 \cup 0 \cup \epsilon)] +# [34
-  * (0**10011110**) |--<​sub>​A</​sub>​ (110011110) |--<​sub>​A</​sub>​ (01011110) |--<​sub>​A</​sub>​ (02, 11110) |--<​sub>​A</​sub>​ (11, 1110) |--<​sub>​A</​sub>​ (12, 110) [There is no transition, so we go to a sink state which is not figured on the drawing.|--<sub>A</​sub>​ (sink, 10) |--<​sub>​A</​sub>​ (sink, 0) |--<​sub>​A</​sub>​ (sink, $math[\epsilon])  The sink state is not a final state, so this word is **rejected**. +</code>
-  * (0**00110010**) |--<​sub>​A</​sub>​ (01, 0110010) |--<​sub>​A</​sub>​ (02, 110010) |--<​sub>​A</​sub>​ (11, 10010) |--<​sub>​A</​sub>​ (12, 0010) |--<​sub>​A</​sub>​ (01, 010) |--<​sub>​A</​sub>​ (02, 10) |--<​sub>​A</​sub>​ (11, 0) |--<​sub>​A</​sub>​ (01, $math[\epsilon]) The word is **accepted** by the DFA because we reached a final state (blue). +
-</hidden>*/+
  
-===== 2.2. Describing the language of DFAs =====+===== Python typing annotations ​=====
  
-Describe in natural speak the language accepted ​by these DFAsMake sure your description is fit to the DFA and not too broad (covers all words accepted ​and no other word).+Newer versions of python support type annotations and pre-runtime type checking (supported ​by some code editors). 
 +The typing module allows programmers ​to provide hints regarding ​the types of different objects, the signature ​(parameters ​and return value typesof functions and methods, and create generic classes.
  
 +To create such hints the syntax is as follows:
  
-**2.2.1**+  ​for variables: ''<​var_name>:​ <​type>​ [= expr]'':​ 
 +<code python>​ 
 +x: int 
 +y: int = 5 
 +z: str = 'hello world'​ 
 +t: MyCustomType 
 +</​code>​ 
 +  ​for functions/​methods:​ ''​def <​func_name>​([<​param>:​ <​type>, ​...]) -> <​return_type>'':​ 
 +<code python>​ 
 +def hello() -> str: 
 +    ... 
 +def get_item_at(index:​ int) -> Any: 
 +    ... 
 +class MyCustomType():​ 
 +    def method(self,​ param1: Any, param2: int) -> str 
 +        ... 
 +</​code>​ 
 +  ​for generic classes: extend either a generic class (such as list[_], set[_] or tuple[_]) or extend the Generic class (introduced by the typing module): 
 +<code python>​ 
 +from typing import TypeVar, Generic
  
-{{:lfa:2024:lab1-2_2_1.png?400|}} +StateType = TypeVar("​StateType"​) # !!! importanttype variables must be declared and initialised before they are used 
-/*  */+class DFA(Generic[StateType]): 
 +    def next_state(self,​ from_stateStateType, token: str) -> StateType:​ 
 +        ​... 
 +    ... 
 +     
 +dfa: DFA[int] = DFA[int](...) 
 +     
 +A = TypeVar("​A"​) 
 +B = TypeVar("​B"​) 
 +     
 +class TupleLinkedList(Generic[A,​B]):​ 
 +    value: tuple[A,​B] 
 +    next: '​TupleLinkedList[A,​B]' ​None # !!! important: in most python versions, if you need to use the 
 +                                        # ​               type of the class within its own definition, you 
 +                                        #                need to quote its name (this delays the evaluation 
 +                                        #                of the hint until after the class is fully defined) 
 +    ... 
 +     
 +class IntList(list[int]):​ 
 +    ... 
 +</code> ​
  
-**2.2.2**+Useful type hints: 
 +  ​''​int,​ str, bool, float etc.''​ : the basic datatypes (charaters are hinted as strings, as python does not make the distinction) 
 +  ​''​list[type],​ set[type], frozenset[type],​ dict[key_type,​ val_type]'':​ basic data collections (items introduced in ''​set''​s and ''​frozenset''​s and the keys of dictionaries need to be hashable: see the [[lfa:​dictionaries|appendix for python hashing]]) 
 +  * ''​Any'':​ a type is compatible with any other type (if no type hint is specified, this is what type checkers usually default to) 
 +  * ''​Callable[ [<​param_types>​...], <​return_type>​]'':​ this defines a function with a given signature 
 +  ​''<​type1>​ | <​type2>'':​ the ''​|''​ operator allows us to indicate that a given object can be one of two(or more) types 
 +  ​''​None'':​ represents the lack of a value (or an explicit None value); useful to mark functions which have no return value, or that a value may be purposefully missing (in combination with the ''​|''​ operator), and a None check may be necessary 
 +full documentation of the python typing module: [[https://​docs.python.org/​3/​library/​typing.html]]
  
-{{:​lfa:​2024:​lab1-2_2_2.png?​300|}} +===== Exercises =====
-/* Cuvinte nevide în care după un grup de 0-uri consecutive urmează fie nimic, fie un număr impar de 1-uri consecutive,​  +
-   și după un grup consecutiv de 1-uri urmează fie nimic, fie un număr par de 0-uri consecutive */+
  
-**2.2.3**+**1.1.** Write a function ''​max_elem(l,​ start, stop)''​ that determines the maximum element between the start and stop positions.
  
-{{:​lfa:​2024:​lab1-2_2_3.png?​500|}} +**1.2.** Write a function ''​longest_positive_sequence(l)''​ that determines the longest sequence of positive integers from a list. For example for the list ''​[1,3,-1,2,0,1,5,4,-2,​4,​5,​-3,​0,​1,​2]''​ it returns ''​[2,​0,​1,​5,​4]''​.
-/Cel puțin un grup de: 0 urmat de un număr impar de 1-uri urmat de un număr par de 0-uri */+
  
-**2.2.4**+**1.3.** Write a function ''​is_palindrome(l)''​ that checks if a list is a palindrome (it returns True/​False).
  
-{{:​lfa:​2024:​lab1-2_2_4.png?​300|}} +**1.4.** Write a function ''​chr_list(s)''​ that determines the list of characters which occur in a string. For example for the string ''"​limbaje formale"''​ it returns ''"​limbaje for"''​.
-/Oricâte grupuri de a sau ab sau abc */+
  
-**2.2.5**+**1.5.** Write a function ''​get_frequency(s)''​ that returns a dictionary with the frequency of every character that appears in the string.
  
-{{:​lfa:​2024:​lab1-2_2_5.png?​300|}} +**1.6.** Starting from the example in the **Classes and inheritance** section, implemented the ''​Node''​ class (that inherits from ''​Tree''​) and it's methods.
-/Cuvinte nevide peste {0,1}care nu încep și se termină cu aceași cifră ​*/+