Differences

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

Link to this comparison view

Next revision
Previous revision
lfa:2024:lab02 [2024/10/06 21:30]
stefan.sterea created
lfa:2024:lab02 [2024/10/12 01:40] (current)
stefan.sterea
Line 14: Line 14:
 If you are completely unfamiliar with Python, some of the biggest differences to a language like **C** are: 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     - 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**+    - variables **DO NOT** have associated types (although ​in newer version there are ways to annotate typing information that can be used by static type checkers, more on that later), they are generic containers for values of any type 
-    - Python works like a scripting language, ​every line of code is interpreted in orderto follow best practices encapsulate your code in functions and classes+    - Python works like a scripting language, code is interpreted in textual ​orderto follow best practices encapsulate your code in functions and classes
     - comments are made using the `#` character     - comments are made using the `#` character
  
Line 37: Line 37:
 ==== Lists in python ==== ==== Lists in python ====
  
-Python does **NOT** offer fixed-sized arrays, but they offer lists out of the box.+Python does not have fixed-sized arrays, but it offers ​lists out of the box.
  
 <code python> <code python>
Line 125: Line 125:
 ==== Dictionaries ==== ==== 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:+Python dictionaries are actually **hash maps** (or **hash tables**). The keys ''​k''​ are hashable ​values. Every key is associated to a value. Some examples of dictionary usage:
  
 <code python> <code python>
 # create an empty dictionary # create an empty dictionary
 d = {} d = {}
 +
 +# create a dictionary with 2 entries (key-value pairs)
 +d1 = { "​key1":​ 10, "​key2":​ [] }
  
 # add or modify a key-value # add or modify a key-value
Line 145: Line 148:
 ==== Sets ==== ==== 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'':​+Sets are collections where each element is unique. Sets can be traversed, but they can not be indexed. They are initialised using the constructor ''​set'':​
 <code python> <code python>
 # the empty set: # the empty set:
Line 158: Line 161:
 # adding a new element to the set: # adding a new element to the set:
 s.add(4) s.add(4)
 +
 +# checking whether an element is in a set:
 +3 in s  # this operation has O(1) amortized cost
 </​code>​ </​code>​
  
-==== Tuples === +==== Tuples ====
-=+
  
-Tuples are very similar to lists and can be substituted by the latter in many cases:+Tuples are similar to lists, but they are immutable (can not be changed)
  
 <code python> <code python>
Line 176: Line 181:
 (fst, snd) = p (fst, snd) = p
  
 +# the tuple constructor is actually the comma; the brackets are not neccessary in many contexts
 +fst, snd = p
 +p = 1, 2     # p is the pair (1, 2)
 +x, y = y, x  # interchange x and y
 </​code>​ </​code>​
  
Line 199: Line 208:
     ...  # iterates through [5, 7, 9]      ...  # iterates through [5, 7, 9] 
  
-# enumerate will keep index with the element+# enumerate will keep an index with the element
 for idx, e in enumerate(l):​ for idx, e in enumerate(l):​
     ...     ...
 +# the above will actually iterate only once through the list, so it's efficient
  
 x = [1, 2, 3, 4] x = [1, 2, 3, 4]
Line 208: Line 218:
 for ex, ey in zip(x, y): for ex, ey in zip(x, y):
    ... iterates through [(1, 5), (2, 6), (3, 7)]    ... iterates through [(1, 5), (2, 6), (3, 7)]
 +# same as above, this only iterates through the two lists once
 </​code>​ </​code>​
  
 ==== Functions ==== ==== Functions ====
  
-Functions are very similar ​with *C* functions, but you don't need to add static typing to arguments or return ​values.+Functions are defined ​similar ​to other languages, but unlike in C, they are values ​like any other value.
  
 <code python> <code python>
Line 222: Line 232:
     ​     ​
 result = f(5, 8) result = f(5, 8)
-</​code>​ 
  
-You can also provide default values for parameters: +# you can also provide default values for parameters:​ 
-<code python> +def g(x, y=0, name="​no name provided"​):​
-def f(x, y=0, name="​no name provided"​):​+
    ...    ...
 +   
 +# `rest' will catch all other arguments passed into a tuple
 +def h(x, y, *rest):
 +    return rest
 +    ​
 +h(1, 2, 3, 4, 5)  # (3, 4, 5)
 +
 +# you can also spread a list into arguments
 +h(*[1, 2, 3, 4, 5])  # the same as the call above
 </​code>​ </​code>​
  
Line 260: Line 277:
  
 The definition ''​class Void(Tree)''​ tells us that class ''​Void''​ inherits ''​Tree''​. Note that this //​contract//​ is not binding in Python. 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''​.+The program will be interpreted even if ''​Void''​ does not correctly implement the methods in ''​Tree'' ​(though there are ways to enforce it at runtime. See [[https://​blog.teclado.com/​python-abc-abstract-base-classes/​|here]]).
  
-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 class constructor ​is the ''​%%__init__(self):​%%''​ method. Also note the mandatory presence of ''​self'',​ which is the Python equivalent of ''​this''​. Each method will receive the object it is called on as the first argument.
  
-The method ''​def __str__(self):''​ is the Python equivalent for ''​toString()''​. An object can be displayed using the function ''​str''​.+The method ''​%%def __str__(self):​%%''​ is the Python equivalent for ''​toString()''​. An object can be displayed using the function ''​str''​.
  
 ===== The functional style ===== ===== The functional style =====
Line 331: Line 348:
 # [3, 4] # [3, 4]
 </​code>​ </​code>​
 +Similarly, there also exist dictionary comprehensions,​ set comprehensions and a related concept, generator expressions (see [[https://​www.geeksforgeeks.org/​generator-expressions/​|here]] and [[https://​peps.python.org/​pep-0289/​|here]]) (there are no tuple comrehensions)
  
 ===== Python typing annotations ===== ===== Python typing annotations =====
Line 345: Line 363:
 z: str = 'hello world' z: str = 'hello world'
 t: MyCustomType t: MyCustomType
 +d: dict             # dictionary of unknown types for keys and values
 +d1: dict[str, int]  # dictionary with string keys and integer values
 </​code>​ </​code>​
-  ​* for functions/​methods:​ ''​def <​func_name>​([<​param>:​ <​type>,​ ...]) -> <​return_type>''​:+ 
 +  ​* for functions/​methods:​ ''​def <​func_name>​([<​param>:​ <​type>,​ ...]) -> <​return_type>''​ 
 +  * you can also type a variable with ''​%%Callable[[<​param_type1>,​ ...], <​return_type>​]%%''​ to hold functions or lambdas 
 <code python> <code python>
 def hello() -> str: def hello() -> str:
Line 356: Line 379:
         ...         ...
 </​code>​ </​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):+ 
 +* 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> <code python>
 from typing import TypeVar, Generic from typing import TypeVar, Generic
  
 StateType = TypeVar("​StateType"​) # !!! important: type variables must be declared and initialised before they are used StateType = TypeVar("​StateType"​) # !!! important: type variables must be declared and initialised before they are used
-class DFA(Generic[StateType]):​+class DFA(Generic[StateType]): ​  # you can also write 'class DFA[StateType]:'​ instead
     def next_state(self,​ from_state: StateType, token: str) -> StateType:     def next_state(self,​ from_state: StateType, token: str) -> StateType:
         ...         ...
Line 384: Line 409:
  
 Useful type hints: Useful type hints:
-  * ''​int,​ str, bool, float etc.''​ : the basic datatypes (charaters ​are hinted as strings, as python does not make the distinction) +  * ''​int,​ str, bool, float etc.''​ : the basic datatypes (characters ​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]]) +  * list[type], set[type], frozenset[type],​ dict[key_type,​ val_type]: basic data collections 
-  * ''​Any'':​ a type is compatible with any other type (if no type hint is specified, this is what type checkers usually default to) +  ​* ''​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+  * ''​%%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   * ''<​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   * ''​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]]+  * The official typing ​documentation [[https://​docs.python.org/​3/​library/​typing.html|here]]
  
-===== Exercises ​=====+===== Practice ​=====
  
-**1.1.** Write function ''​max_elem(lstart, stop)'' that determines the maximum element between the start and stop positions.+The task of this lab will be to implement the accepting procedure of DFA in Pythonin order to get familiar with Python's language constructs and get a start into working with DFAs in code.
  
-**1.2.** Write function ''​longest_positive_sequence(l)''​ that determines the longest sequence ​of positive integers from 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]''​.+Reading ​textual representation ​of a DFA and a word, output whether or not the DFA accepts the word. 
 +The DFA will be represented as:
  
-**1.3.** Write a function ''​is_palindrome(l)''​ that checks if a list is a palindrome (it returns True/​False).+**#states**
  
-**1.4.** Write a function ''​chr_list(s)''​ that determines ​the list of characters which occur in string. For example for the string ''"​limbaje formale"''​ it returns ''"​limbaje for"''​.+//the labels ​of the states, each on separate line//
  
-**1.5.** Write function ''​get_frequency(s)'' ​that returns ​dictionary with the frequency of every character that appears ​in the string.+**#initial** 
 + 
 +//the label of the initial state// 
 + 
 +**#​accepting** 
 + 
 +//the labels of the final states, each on separate line// 
 + 
 +**#​alphabet** 
 + 
 +//the symbols of the alphabet of the DFA, each on a separate line// 
 + 
 +**#​transitions** 
 + 
 +//source state// **:** //read character// **>** //​destination state// ​  //(each transition on a separate line)// 
 + 
 +Example: 
 + 
 +<​code>​ 
 +#states 
 +s0 
 +s1 
 +s2 
 +#initial 
 +s0 
 +#​accepting 
 +s1 
 +#alphabet 
 +
 +
 +
 +#​transitions 
 +s0:​b>​s2 
 +s0:​c>​s0 
 +s1:​a>​s2 
 +s1:​c>​s1 
 +s2:​a>​s1 
 +s2:​b>​s1 
 +s2:​c>​s2 
 +</​code>​ 
 + 
 +represents the following DFA: 
 +{{:​lfa:​2024:​lab2-dfa-example.png?​200|}} 
 + 
 +You should write a DFA class to keep the internal state and input left unconsumed and implement an ''​accept''​ method. 
 + 
 +You may choose how to take input: you can read the DFA from file, you can receive ​the path to the file as an argument to the Python program, you can take the word as another argument or in the same file as the DFA. 
 + 
 +Example of reading command line arguments and working with files: 
 +<code python>​ 
 +import sys 
 + 
 +def read_lines(file_path:​ str) -> list[str]:​ 
 +    try: 
 +        # '​with'​ creates a context manager which handles file closing 
 +        with open(file_path,​ '​r'​) as file: 
 +            return file.readlines() 
 +    except FileNotFoundError:​ 
 +        print(f"​Error:​ File '​{file_path}'​ not found."​) 
 + 
 +def main(): 
 +    if len(sys.argv) != 2: 
 +        print(f"​Usage:​ python3 <​file_path>"​) 
 +        sys.exit(1) 
 + 
 +    file_path = sys.argv[1] 
 +    lines = read_file(file_path) 
 +    print(lines) 
 + 
 +if __name__ == "​__main__":​ 
 +    main() 
 +</​code>​
  
-**1.6.** Starting from the example in the **Classes and inheritance** section, implemented the ''​Node''​ class (that inherits from ''​Tree''​) and it's methods.