Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
lfa:2024:lab02 [2024/10/11 19:31] stefan.sterea |
lfa:2024:lab02 [2024/10/12 01:40] (current) stefan.sterea |
||
---|---|---|---|
Line 279: | Line 279: | ||
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 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 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 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 366: | Line 366: | ||
d1: dict[str, int] # dictionary with string keys and integer 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 376: | 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 404: | 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 a function ''max_elem(l, start, 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 a DFA in Python, in order to get familiar with Python's language constructs and get a start into working with DFAs in code. |
- | **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]''. | + | Reading a 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 a string. For example for the string ''"limbaje formale"'' it returns ''"limbaje for"''. | + | //the labels of the states, each on a separate line// |
- | **1.5.** Write a function ''get_frequency(s)'' that returns a 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 a 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 | ||
+ | a | ||
+ | b | ||
+ | c | ||
+ | #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 a 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. |