Skip to content
This repository was archived by the owner on Jul 2, 2024. It is now read-only.

Commit b969453

Browse files
committed
Added basic dict operations
Included "swap keys-values" function. Added dict filtering functions for the "students scores" challenge. Changed existing functions signatures. Added more explanations for the "filter_by_values" function. Signed-off-by: Serhii Horodilov <sgorodil@gmail.com>
1 parent 71a07a5 commit b969453

File tree

2 files changed

+184
-17
lines changed

2 files changed

+184
-17
lines changed

src/datasets/__init__.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,25 @@
22
Dataset challenges
33
==================
44
5+
Swap dictionary keys and values
6+
-------------------------------
7+
8+
.. autofunction:: swap_dict_loop
9+
10+
.. autofunction:: swap_dict
11+
512
"""
613

714
__all__ = [
15+
"swap_dict",
16+
"get_low_student",
17+
"get_top_student",
18+
"get_both_top_low_students",
819
"get_bricks_count",
920
"get_position_frequency",
1021
"get_least_bricks_position",
1122
"get_least_bricks_count",
1223
"filter_by_values",
1324
]
1425

15-
from datasets.func import (filter_by_values, get_bricks_count,
16-
get_least_bricks_count, get_least_bricks_position,
17-
get_position_frequency)
26+
from datasets.func import *

src/datasets/func.py

Lines changed: 172 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,149 @@
33
44
"""
55

6-
from typing import Any, Dict, List, Optional, Set
6+
from typing import Any, Dict, Hashable, List, Optional, Set, Tuple, Union
7+
8+
9+
# BASIC OPERATIONS
10+
# ================
11+
12+
def swap_dict_loop(origin: Dict[Hashable, Hashable]
13+
) -> Dict[Hashable, Hashable]:
14+
"""
15+
Return a new dictionary with swapped keys and values from the original
16+
17+
:param origin: the dictionary to swap the keys and values of
18+
:type origin: dict
19+
:return: a new dictionary with the swapped keys and values
20+
:rtype: dict
21+
22+
The function iterates through the dictionary keys using a ``for`` loop.
23+
On each iteration step the value assigned at the original dictionary key
24+
is accessed. After that the values and key are written down to a new
25+
dictionary, but not using the value as key and vice versa.
26+
27+
This is the most descriptive algorithm to solve the swap task.
28+
29+
Example usage:
30+
31+
>>> swap_dict_loop({1: "one", 2: "two", 3: "three"})
32+
{"one": 1, "two": 2, "three": 3}
33+
34+
"""
35+
36+
result = {}
37+
38+
for key in origin:
39+
value = origin[key]
40+
result[value] = key
41+
42+
return result
43+
44+
45+
def swap_dict(origin: Dict[Hashable, Hashable]) -> Dict[Hashable, Hashable]:
46+
"""
47+
Return a new dictionary with swapped keys and values from the original
48+
49+
:param origin: the dictionary to swap the keys and values of
50+
:type origin: dict
51+
:return: a new dictionary with the swapped keys and values
52+
:rtype: dict
53+
54+
Works similar to ``swap_dict_loop`` function, but uses dictionary
55+
comprehension instead of the ``for`` loop.
56+
57+
Example usage:
58+
59+
>>> swap_dict({1: "one", 2: "two", 3: "three"})
60+
{"one": 1, "two": 2, "three": 3}
61+
62+
"""
63+
64+
return {value: key for key, value in origin.items()}
65+
66+
67+
# define typing hints aliases
68+
StudentData = Dict[str, Union[str, List[int]]] # student data type alias
69+
StudentsList = List[StudentData] # students data type alias
70+
71+
72+
def get_avg_score(student_data: StudentData) -> float:
73+
"""Return average score of a student"""
74+
75+
scores: List[int] = student_data["scores"] # type: ignore
76+
77+
return round(sum(scores) / len(scores))
78+
79+
80+
def get_top_student(students_data: StudentsList) -> StudentData:
81+
"""
82+
Return a student who has the highest average score
83+
84+
:param students_data: a list of students data (names and scores)
85+
:type students_data: list
86+
87+
:return: student data entity
88+
:rtype: dict
89+
90+
"""
91+
92+
threshold = float("-inf") # negative infinite, less that any number
93+
result = {"name": "", "scores": []}
94+
95+
for student in students_data:
96+
avg_score = get_avg_score(student)
97+
if avg_score > threshold:
98+
threshold = avg_score
99+
result = student # type: ignore
100+
101+
return result # type: ignore
102+
103+
104+
def get_low_student(students_data: StudentsList) -> StudentData:
105+
"""
106+
Return the student who has the lowest average score
107+
108+
:param students_data: a list of students data (names and scores)
109+
:type students_data: list
110+
111+
:return: student data entity
112+
:rtype: dict
113+
114+
"""
115+
116+
threshold = float("+inf") # positive infinite, bigger that any number
117+
result = {"name": "", "scores": []}
118+
119+
for student in students_data:
120+
avg_score = get_avg_score(student)
121+
if avg_score < threshold:
122+
threshold = avg_score
123+
result = student # type: ignore
124+
125+
return result # type: ignore
126+
127+
128+
def get_both_top_low_students(students_data: StudentsList
129+
) -> Tuple[StudentData, StudentData]:
130+
min_score_threshold, max_score_threshold = float("+inf"), float("-inf")
131+
top_student = {"name": "", "scores": []}
132+
low_student = {"name": "", "scores": []}
133+
134+
for student in students_data:
135+
avg_score = get_avg_score(student)
136+
if avg_score > max_score_threshold:
137+
max_score_threshold = avg_score
138+
top_student = student # type: ignore
139+
if avg_score < min_score_threshold:
140+
min_score_threshold = avg_score
141+
low_student = student # type: ignore
142+
143+
return top_student, low_student # type: ignore
7144

8145

9146
# BRICK WALL CHALLENGE
10147
# ====================
148+
11149
def get_bricks_count(structure: List[List[int]]) -> int:
12150
"""Return number of bricks in the wall structure
13151
@@ -17,11 +155,11 @@ def get_bricks_count(structure: List[List[int]]) -> int:
17155
:return: total number of bricks in the entire wall structure
18156
:rtype: int
19157
20-
Usage:
158+
Usage examples:
21159
22-
>>> get_bricks_count([[1], [1], [1]]) == 3
23-
>>> get_bricks_count([[1, 1, 1], [1, 1, 1]]) == 6
24-
>>> get_bricks_count([[2, 1, 2], [1, 1, 1, 1, 1]]) == 8
160+
>>> assert get_bricks_count([[1], [1], [1]]) == 3
161+
>>> assert get_bricks_count([[1, 1, 1], [1, 1, 1]]) == 6
162+
>>> assert get_bricks_count([[2, 1, 2], [1, 1, 1, 1, 1]]) == 8
25163
26164
"""
27165

@@ -37,7 +175,7 @@ def get_position_frequency(structure: List[List[int]]) -> Dict[int, int]:
37175
:return: the position - frequency matrix
38176
:rtype: dict[int, int]
39177
40-
Usage:
178+
Usage examples:
41179
42180
>>> assert get_position_frequency([[1], [1], [1]]) == {}
43181
>>> assert get_position_frequency([[1, 2], [2, 1], [3]]) = {1: 1, 2: 1}
@@ -70,7 +208,7 @@ def get_least_bricks_position(structure: List[List[int]]) -> int:
70208
This function uses helper function ``get_structure_matrix`` to build
71209
the matrix of distances from the left edge of the "wall".
72210
73-
Usage:
211+
Usage examples:
74212
75213
>>> assert get_least_bricks_position([[1], [1], [1]]) == 0
76214
>>> assert get_least_bricks_position([[1, 1, 1], [1, 1, 1]]) == 1
@@ -93,7 +231,7 @@ def get_least_bricks_count(structure: List[List[int]]) -> int:
93231
:return: minimum number of bricks in a line
94232
:rtype: int
95233
96-
Usage:
234+
Usage examples:
97235
98236
>>> assert get_least_bricks_count([[1], [1], [1]]) == 3
99237
>>> assert get_least_bricks_count([[1, 2], [2, 1], [3], [1, 1, 1]]) == 2
@@ -112,8 +250,9 @@ def get_least_bricks_count(structure: List[List[int]]) -> int:
112250
# FILTER DATASET CHALLENGE
113251
# ========================
114252

115-
def filter_by_values(origin: List[Dict[str, Any]],
116-
keys: Optional[List[str]] = None) -> List[Dict[str, Any]]:
253+
def filter_by_values(origin: List[Dict[str, Hashable]],
254+
keys: Optional[List[str]] = None
255+
) -> List[Dict[str, Any]]:
117256
"""Return a filtered datasets by unique values in a given keys sets
118257
119258
:param origin: an original dataset with entries to filter
@@ -125,33 +264,52 @@ def filter_by_values(origin: List[Dict[str, Any]],
125264
:rtype: list
126265
127266
The origin dataset is a list of dictionaries. All the dictionaries have
128-
the same set of keys of a string type. At least one entry with at least
129-
one key is granted.
267+
the same set of keys of a string type. All dictionaries values are of
268+
a hashable type.
269+
270+
In case no data provided (``origin`` is an empty list) the function will
271+
return an empty list.
130272
131273
The keys parameter is used to set the dictionary keys to filter unique
132274
values. Keys list is considered to be validated before passing to this
133275
function, all values (if any) are valid. In case this parameter is
134276
omitted - all available keys should be used.
135277
136-
Usage:
278+
Inputs are considered to be pre-validated as described above,
279+
no need for additional checks.
280+
281+
Usage examples:
137282
138283
>>> ds = [{"x": 1, "y": 2, "z": 3}, {"x": 0, "y": 2, "z": 3}]
284+
>>> assert filter_by_values([]) == [] # no data to filter
285+
>>> assert filter_by_values(ds) == ds # the same as origin
139286
>>> assert filter_by_values(ds, ["x"]) == ds # the same as origin
140287
>>> assert filter_by_values(ds, ["x", "z"]) == ds # the same as origin
141288
>>> assert filter_by_values(ds, ["y"]) == [{"x": 1, "y": 2, "z": 3}]
142289
>>> assert filter_by_values(ds, ["y", "z"]) == [{"x": 1, "y": 2, "z": 3}]
143290
144291
"""
145292

146-
filtered_dataset: List[Dict[str, Any]] = []
293+
# check base cases
294+
if not origin:
295+
return []
296+
297+
# declare variables to store runtime data
298+
filtered_dataset: List[Dict[str, Hashable]] = []
147299
filtered_values: Set[int] = set()
148300

301+
# in case no keys provided get all keys from the first one dictionary
149302
keys = keys or origin[0].keys() # type: ignore
303+
304+
# iterate though the list and perform de-duplication task
150305
for entry in origin:
151306
entry_values = hash(tuple(map(entry.get, keys))) # type: ignore
307+
# in case the values has been filtered already
308+
# proceed to the next iteration
152309
if entry_values in filtered_values:
153310
continue
154311

312+
# update filtered values
155313
filtered_values.add(entry_values)
156314
filtered_dataset.append(entry)
157315

0 commit comments

Comments
 (0)