Differences
This shows you the differences between two versions of the page.
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 order, to follow best practices encapsulate your code in functions and classes | + | - Python works like a scripting language, code is interpreted in textual order; to 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 a 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 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. |