14.6. Function Usecase

14.6.1. Assignments

"""
* Assignment: Function Usecase AsTuple
* Type: class assignment
* Complexity: easy
* Lines of code: 2 lines
* Time: 2 min

English:
    1. Define function `astuple`:
       a. takes `data: dict`
       b. returns tuple with `data` values
    2. Run doctests - all must succeed

Polish:
    1. Zdefiniuj funkcję `astuple`:
       a. przyjmuje `data: dict`
       b. zwraca tuplę z wartościami z `data`
    2. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isfunction

    >>> assert astuple is not Ellipsis, \
    'Write solution inside `astuple` function'
    >>> assert isfunction(astuple), \
    'Object `astuple` must be a function'

    >>> astuple({'a':1, 'b':2, 'c':3})
    (1, 2, 3)

    >>> astuple({'firstname':'Mark', 'lastname':'Watney'})
    ('Mark', 'Watney')
"""

# Define function `astuple`:
# - takes `data: list[int|float]`
# - returns tuple with min and max value from `data`
# Do not use built-in `min()` and `max()` functions
# type: Callable[[dict], tuple]
...


"""
* Assignment: Function Usecase Min
* Type: class assignment
* Complexity: easy
* Lines of code: 6 lines
* Time: 5 min

English:
    1. Define function `mymin`:
       a. takes `data: list[int|float]`
       b. returns lowest value in `data`
    2. Do not use built-in function `min()`
    3. Run doctests - all must succeed

Polish:
    1. Zdefiniuj funkcję `mymin`:
       a. przyjmuje `data: list[int|float]`
       b. zwraca tuplę z najmniejszą wartością z `data`
    2. Nie używaj wbudowanej funkcji `min()`
    3. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isfunction

    >>> assert mymin is not Ellipsis, \
    'Write solution inside `mymin` function'
    >>> assert isfunction(mymin), \
    'Object `mymin` must be a function'

    >>> mymin([3,1,2])
    1

    >>> mymin([3,1,2,6,4,5])
    1
"""

# Define function `mymin`:
# - takes `data: list[int|float]`
# - returns tuple with min and max value from `data`
# Do not use built-in `min()` and `max()` functions
# type: Callable[[list[int|float]], int]
...


"""
* Assignment: Function Usecase Max
* Type: class assignment
* Complexity: easy
* Lines of code: 6 lines
* Time: 5 min

English:
    1. Define function `mymax`:
       a. takes `data: list[int|float]`
       b. returns the greatest value in `data`
    2. Do not use built-in function `max()`
    3. Run doctests - all must succeed

Polish:
    1. Zdefiniuj funkcję `mymax`:
       a. przyjmuje `data: list[int|float]`
       b. zwraca tuplę z największą wartością z `data`
    2. Nie używaj wbudowanej funkcji `max()`
    3. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isfunction

    >>> assert mymax is not Ellipsis, \
    'Write solution inside `mymax` function'
    >>> assert isfunction(mymax), \
    'Object `mymax` must be a function'

    >>> mymax([3,1,2])
    3

    >>> mymax([3,1,2,6,4,5])
    6
"""

# Define function `mymax`:
# - takes `data: list[int|float]`
# - returns tuple with min and max value from `data`
# Do not use built-in `min()` and `max()` functions
# type: Callable[[list[int|float]], int]
...


"""
* Assignment: Function Usecase Len
* Type: class assignment
* Complexity: easy
* Lines of code: 5 lines
* Time: 5 min

English:
    1. Define function `mylen`:
       a. takes `data: list`
       b. returns number of elements in `data`
    2. Do not use built-in `len()` function
    3. Run doctests - all must succeed

Polish:
    1. Zdefiniuj funkcję `mylen`:
       a. przyjmuje `data: list`
       b. zwraca liczbe elementów w `data`
    2. Nie używaj wbudowanej funkcji `len`
    3. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isfunction

    >>> assert mylen is not Ellipsis, \
    'Write solution inside `mylen` function'
    >>> assert isfunction(mylen), \
    'Object `mylen` must be a function'

    >>> mylen([1,2,3])
    3

    >>> mylen([1,2,3,4,5,6])
    6
"""

# Define function `mylen`:
# - takes `data: list`
# - returns number of elements in `data`
# Do not use built-in `len()` function
# type: Callable[[list], int]
...


"""
* Assignment: Function Usecase Enumerate
* Type: class assignment
* Complexity: easy
* Lines of code: 7 lines
* Time: 5 min

English:
    1. Define function `myenumerate`:
        a. takes `data: list`
        b. returns tuple list with consecutive number and value from `data`
    2. Example:
        a. input: ['red', 'green', 'blue']
        b. output: [(0, 'red'), (1, 'green'), (2, 'blue')]
    3. Do not use built-in `enumerate()` function
    4. Run doctests - all must succeed

Polish:
    1. Zdefiniuj funkcję `myenumerate`:
       a. przyjmuje `data: list`
       b. zwraca listę tupli z kolejnym numerem i wartością z `data`
    2. Przykład:
        a. input: ['red', 'green', 'blue']
        b. output: [(0, 'red'), (1, 'green'), (2, 'blue')]
    3. Nie używaj wbudowanej funkcji `enumerate`
    4. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isfunction

    >>> assert myenumerate is not Ellipsis, \
    'Write solution inside `myenumerate` function'
    >>> assert isfunction(myenumerate), \
    'Object `myenumerate` must be a function'

    >>> myenumerate(['red', 'green', 'blue'])
    [(0, 'red'), (1, 'green'), (2, 'blue')]
"""

# Define function `myenumerate`:
# - takes `data: list`
# - returns number of elements in `data`
# Do not use built-in `len()` function
# type: Callable[[list], int]
...


"""
* Assignment: Function Usecase Zip
* Type: class assignment
* Complexity: easy
* Lines of code: 8 lines
* Time: 8 min

English:
    1. Define function `myzip`:
        a. takes two parameters `a: list`, `b: list`
        b. pairs `a` elements with `b` elements in form of `list[tuple]`
        c. first from `a` is paired with first from `b`, etc.
        d. collect number of pairs equal to the count of shortest list
    2. Example:
        a. input: ['Mark', 'Melissa'] and ['Watney', 'Lewis']
        b. output: [('Mark', 'Watney'), ('Melissa', 'Lewis')]
    3. Do not use built-in `zip()` function
    4. Run doctests - all must succeed

Polish:
    1. Zdefiniuj funkcję `myzip`:
       a. przyjmuje `data: list`
       b. paruje elementy `a` z elementami z `b` w formie `list[tuple]`
       c. pierwszy z `a` ma być sparowany z pierwszym z `b`, itd.
       d. zbierz tyle par ile bło wartości w najkrótszej liście
    2. Przykład:
        a. input: ['Mark', 'Melissa'] i ['Watney', 'Lewis']
        b. output: [('Mark', 'Watney'), ('Melissa', 'Lewis')]
    3. Nie używaj wbudowanej funkcji `zip`
    4. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isfunction

    >>> assert myzip is not Ellipsis, \
    'Write solution inside `myzip` function'
    >>> assert isfunction(myzip), \
    'Object `myzip` must be a function'

    >>> firstnames = ['Mark', 'Melissa', 'Rick']
    >>> lastnames = ['Watney', 'Lewis', 'Martinez']
    >>> myzip(firstnames, lastnames)
    [('Mark', 'Watney'), ('Melissa', 'Lewis'), ('Rick', 'Martinez')]

    >>> firstnames = ['Mark', 'Melissa', 'Rick']
    >>> lastnames = ['Watney', 'Lewis']
    >>> myzip(firstnames, lastnames)
    [('Mark', 'Watney'), ('Melissa', 'Lewis')]
"""

# Define function `myzip`:
# - takes two parameters `a: list`, `b: list`
# - pairs `a` elements with `b` elements in form of `list[tuple]`
# - first from `a` is paired with first from `b`, etc.
# - collect number of pairs equal to the count of shortest list
# Do not use built-in `len()` function
# type: Callable[[list], int]
...


"""
* Assignment: Function Usecase EtcPasswdUsername
* Type: homework
* Complexity: medium
* Lines of code: 11 lines
* Time: 13 min

English:
    1. Create function `get_username`:
        a. parameter: `uid` (user ID)
        b. return: `username`
    2. Run doctests - all must succeed

Polish:
    1. Stwórz funkcję `get_username`:
        a. parametr: `uid` (user ID)
        b. zwraca: `username`
    2. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> get_username(0)
    'root'
    >>> get_username(1000)
    'mwatney'
"""


DATA = """##
# Structure of `/etc/passwd` file:
# - user_login: username
# - user_password: 'x' indicates that shadow passwords are used (skip this field)
# - user_uid: user ID number
# - user_gid: group ID number
# - user_comment: comment ie. full name
# - user_home: path to home directory
# - user_shell: path to shell
##
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
nobody:x:99:99:Nobody:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
mwatney:x:1000:1000:Mark Watney:/home/mwatney:/bin/bash
mlewis:x:1001:1001:Melissa Lewis:/home/mlewis:/bin/bash
rmartinez:x:1002:1002:Rick Martinez:/home/rmartinez:/bin/bash
avogel:x:1003:1003:Alex Vogel:/home/avogel:/bin/bash
bjohanssen:x:1004:1004:Beth Johanssen:/home/bjohanssen:/bin/bash
cbeck:x:1005:1005:Chris Beck:/home/cbeck:/bin/bash
"""

# Create function `get_username`:
# - parameter: `uid` (user ID)
# - return: `username`
# type: Callable[[int], str]
def get_username(user_id):
    ...


"""
* Assignment: Function Usecase EtcShadow
* Type: homework
* Complexity: medium
* Lines of code: 15 lines
* Time: 13 min

English:
    1. Create function `parse_passwd`:
        a. parameter: `username: str`
        b. return: user data record as a dict
    2. Run doctests - all must succeed

Polish:
    1. Stwórz funkcję `parse_passwd`:
        a. parametr: `username: str`
        b. zwraca: rekord danych użytkownika jako dict
    2. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * len()
    * int()
    * str.splitlines()
    * str.isspace()
    * str.startswith()
    * str.split()

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from pprint import pprint

    >>> pprint(parse_passwd('mwatney'), sort_dicts=False)
    {'user_login': 'mwatney',
     'user_password': 'x',
     'user_uid': 1000,
     'user_gid': 1000,
     'user_comment': 'Mark Watney',
     'user_home': '/home/mwatney',
     'user_shell': '/bin/bash'}
"""

DATA = """##
# Structure of `/etc/passwd` file:
# - user_login: username
# - user_password: 'x' indicates that shadow passwords are used (skip this field)
# - user_uid: user ID number
# - user_gid: group ID number
# - user_comment: comment ie. full name
# - user_home: path to home directory
# - user_shell: path to shell
##
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
nobody:x:99:99:Nobody:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
mwatney:x:1000:1000:Mark Watney:/home/mwatney:/bin/bash
mlewis:x:1001:1001:Melissa Lewis:/home/mlewis:/bin/bash
rmartinez:x:1002:1002:Rick Martinez:/home/rmartinez:/bin/bash
avogel:x:1003:1003:Alex Vogel:/home/avogel:/bin/bash
bjohanssen:x:1004:1004:Beth Johanssen:/home/bjohanssen:/bin/bash
cbeck:x:1005:1005:Chris Beck:/home/cbeck:/bin/bash
"""


# Create function `parse_passwd`:
# - parameter: `username: str`
# - return: user data record as a dict
# type: Callable[[int], str]
def parse_passwd(username):
    ...


"""
* Assignment: Function Scope EtcShadow
* Type: homework
* Complexity: medium
* Lines of code: 35 lines
* Time: 34 min

English:
    1. Create function `parse_shadow`:
        a. parameter: `username: str`
        b. return: user password record as a dict
    2. Split password field by `$`:
        a. algorithm (algorithm number, e.g. `6` is SHA-512)
        b. salt (value added to password)
        c. password hash (encoded password)
    3. Run doctests - all must succeed

Polish:
    1. Stwórz funkcję `parse_shadow`:
        a. parametr: `username: str`
        b. zwraca: dane hasła użytkownika jako dict
    2. Podziel pole hasła po znaku `$`:
        a. algorytm (nr algorytmu, np. `6` to SHA-512)
        b. sól (wartość dodawana do hasła)
        c. hash hasła (zakodowane hasło)
    3. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * len()
    * list[...]
    * ... in tuple
    * str.splitlines()
    * str.isspace()
    * str.startswith()
    * str.split()
    * dict.get(...)

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from pprint import pprint

    >>> result = parse_shadow('mwatney')
    >>> pprint(result, sort_dicts=True)
    {'account_expiration_date': '',
     'account_locked': False,
     'login': 'mwatney',
     'password_algorithm': 'SHA-512',
     'password_hash': 'bXGOh7dIfOWpUb/Tuqr7yQVCqL3UkrJns9.7msfvMg4ZO/PsFC5Tbt32PXAw9qRFEBs1254aLimFeNM8YsYOv.',
     'password_inactivity_period': '',
     'password_last_changed': '16550',
     'password_max_age': '',
     'password_min_age': '',
     'password_salt': '5H0QpwprRiJQR19Y',
     'password_warning_period': '',
     'reserved': ''}

    >>> result = parse_shadow('mlewis')
    >>> pprint(result, sort_dicts=True)
    {'account_expiration_date': '',
     'account_locked': False,
     'login': 'mlewis',
     'password_algorithm': 'SHA-512',
     'password_hash': 'tgfvvFWJJ5FKmoXiP5rXWOjwoEBOEoAuBi3EphRbJqqjWYvhEM2wa67L9XgQ7W591FxUNklkDIQsk4kijuhE50',
     'password_inactivity_period': '',
     'password_last_changed': '16632',
     'password_max_age': '99999',
     'password_min_age': '0',
     'password_salt': 'P9zn0KwR',
     'password_warning_period': '7',
     'reserved': ''}
"""

DATA = """##
# Structure of `/etc/shadow` file:
#   - login: user login name
#   - password_encrypted: encrypted password (see below for more details)
#   - password_last_changed: days since 1970-01-01 password was last changed
#   - password_min_age: days before which password may not be changed
#   - password_max_age: days after which password must be changed
#   - password_warning_period: days before password is to expire that user is warned of pending password expiration
#   - password_inactivity_period: days after password expires that account is considered inactive and disabled
#   - account_expiration_date: days since 1970-01-01 when account will be disabled
#   - reserved: reserved for future use (skip this field)
#
# Password field (split by `$`):
#   - algorithm
#   - salt
#   - password hash
#
# Password algorithms:
#   - '1' - MD5
#   - '2a' - Blowfish
#   - '2y' - Blowfish
#   - '5' - SHA-256
#   - '6' - SHA-512
#
# Password special chars:
#   - ' '  - account unlocked, password is not required to log in
#   - '*'  - account locked, cannot be unlocked, and no password has ever been set
#   - '!!' - account locked, can be unlocked, waiting for initial password to be set by admin
#   - '!'  - account locked, can be unlocked, and no password has ever been set
#   - '!<password_hash>' - account locked, can be unlocked, but password is set
##
root:$6$Ke02nYgo.9v0SF4p$hjztYvo/M4buqO4oBX8KZTftjCn6fE4cV5o/I95QPekeQpITwFTRbDUBYBLIUx2mhorQoj9bLN8v.w6btE9xy1:16431:0:99999:7:::
bin:*:16431:0:99999:7:::
daemon:*:16431:0:99999:7:::
adm:*:16431:0:99999:7:::
shutdown:*:16431:0:99999:7:::
halt:*:16431:0:99999:7:::
nobody:*:16431:0:99999:7:::
sshd:*:16431:0:99999:7:::
mwatney:$6$5H0QpwprRiJQR19Y$bXGOh7dIfOWpUb/Tuqr7yQVCqL3UkrJns9.7msfvMg4ZO/PsFC5Tbt32PXAw9qRFEBs1254aLimFeNM8YsYOv.:16550::::::
mlewis:$6$P9zn0KwR$tgfvvFWJJ5FKmoXiP5rXWOjwoEBOEoAuBi3EphRbJqqjWYvhEM2wa67L9XgQ7W591FxUNklkDIQsk4kijuhE50:16632:0:99999:7:::
rmartinez:$1$.QKDPc5E$SWlkjRWexrXYgc98F.:12825:0:90:5:30:13096:
avogel:!!:16431:0:99999:7:::
bjohanssen:!:16431:0:99999:7:::
cbeck:*:16431:0:99999:7:::
"""

ALGORITHMS = {
    '1': 'MD5',
    '2a': 'Blowfish',
    '2y': 'Blowfish',
    '5': 'SHA-256',
    '6': 'SHA-512',
}


# Create function `parse_shadow`:
# - parameter: `username: str`
# - return: user password record as a dict
#
# Split password field by `$`:
# - algorithm (algorithm number, e.g. `6` is SHA-512)
# - salt (value added to password)
# - password hash (encoded password)
#
# type: Callable[[int], str]
def parse_shadow(username):
    ...