stock_utils 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stock_utils-0.1.0/LICENSE +21 -0
- stock_utils-0.1.0/PKG-INFO +37 -0
- stock_utils-0.1.0/README.md +13 -0
- stock_utils-0.1.0/pyproject.toml +48 -0
- stock_utils-0.1.0/src/algo/__init__.py +0 -0
- stock_utils-0.1.0/src/algo/binary_search.py +20 -0
- stock_utils-0.1.0/src/algo/dijkstras_algo.py +96 -0
- stock_utils-0.1.0/src/algo/is_palendrome.py +16 -0
- stock_utils-0.1.0/src/algo/lexicographical_ordering.py +25 -0
- stock_utils-0.1.0/src/algo/longest_palindrome_substring.py +25 -0
- stock_utils-0.1.0/src/algo/reverse_in_place.py +18 -0
- stock_utils-0.1.0/src/algo/rotate_array.py +21 -0
- stock_utils-0.1.0/src/math/__init__.py +0 -0
- stock_utils-0.1.0/src/math/binomial-coefficient.py +31 -0
- stock_utils-0.1.0/src/math/camel_up.py +22 -0
- stock_utils-0.1.0/src/math/diffie_hellman.py +48 -0
- stock_utils-0.1.0/src/math/euclidean_distance.py +82 -0
- stock_utils-0.1.0/src/math/factorial.py +10 -0
- stock_utils-0.1.0/src/math/herons_formula_for_area.py +18 -0
- stock_utils-0.1.0/src/math/multiplication_table.py +15 -0
- stock_utils-0.1.0/src/math/sieve_of_eratosthenes.py +70 -0
- stock_utils-0.1.0/src/math/square_practice.py +52 -0
- stock_utils-0.1.0/src/stock_utils/__init__.py +9 -0
- stock_utils-0.1.0/src/stock_utils/__main__.py +4 -0
- stock_utils-0.1.0/src/stock_utils/cli.py +41 -0
- stock_utils-0.1.0/src/stock_utils/core.py +79 -0
- stock_utils-0.1.0/src/stock_utils/static/notes.txt +28 -0
- stock_utils-0.1.0/src/stock_utils/static/test_audio.mp3 +0 -0
- stock_utils-0.1.0/src/utils/__init__.py +0 -0
- stock_utils-0.1.0/src/utils/console.py +3 -0
- stock_utils-0.1.0/src/utils/days-between.py +28 -0
- stock_utils-0.1.0/src/utils/pic_search_generator.py +16 -0
- stock_utils-0.1.0/src/utils/random_num.py +32 -0
- stock_utils-0.1.0/src/utils/typwriter.py +17 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [year] [fullname]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: stock_utils
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Interactive examples of algorithms, mathematical concepts and utility functions.
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: utilities,algorithms,math,practice
|
|
8
|
+
Author: Jeff Stock
|
|
9
|
+
Author-email: jantonstock@gmail.com
|
|
10
|
+
Requires-Python: >=3.13
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Dist: mkdocs-material (>=9.7.7,<10.0.0)
|
|
18
|
+
Requires-Dist: playsound3 (>=3.3.1,<4.0.0)
|
|
19
|
+
Requires-Dist: python-dotenv (>=1.2.1,<2.0.0)
|
|
20
|
+
Requires-Dist: rich (>=15.0.0,<16.0.0)
|
|
21
|
+
Project-URL: Repository, https://github.com/janton42/stock_utils
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# Practice
|
|
25
|
+
|
|
26
|
+
Interative examples of algorithms, mathematical concepts and utility functions.
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
## License
|
|
35
|
+
|
|
36
|
+
MIT License. See `LICENSE`.
|
|
37
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "stock_utils"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Interactive examples of algorithms, mathematical concepts and utility functions."
|
|
5
|
+
authors = [
|
|
6
|
+
"Jeff Stock <jantonstock@gmail.com>",
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
repository = "https://github.com/janton42/stock_utils"
|
|
10
|
+
keywords = ["utilities", "algorithms", "math", "practice"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 3 - Alpha",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Programming Language :: Python :: 3.13",
|
|
16
|
+
]
|
|
17
|
+
license = "MIT"
|
|
18
|
+
readme = "README.md"
|
|
19
|
+
packages = [
|
|
20
|
+
{include="algo", from="src"},
|
|
21
|
+
{include="math", from="src"},
|
|
22
|
+
{include="stock_utils", from = "src" },
|
|
23
|
+
{include="utils", from="src"},
|
|
24
|
+
]
|
|
25
|
+
include = [
|
|
26
|
+
"src/disaster_factor/static/**"
|
|
27
|
+
]
|
|
28
|
+
[tool.poetry.scripts]
|
|
29
|
+
stock-utils = "stock_utils.cli:main"
|
|
30
|
+
|
|
31
|
+
[tool.poetry.dependencies]
|
|
32
|
+
python = ">=3.13"
|
|
33
|
+
rich = ">=15.0.0,<16.0.0"
|
|
34
|
+
playsound3 = ">=3.3.1,<4.0.0"
|
|
35
|
+
python-dotenv = "^1.2.1"
|
|
36
|
+
mkdocs-material = "^9.7.7"
|
|
37
|
+
|
|
38
|
+
[tool.poetry.group.dev]
|
|
39
|
+
optional = true
|
|
40
|
+
|
|
41
|
+
[tool.poetry.group.dev.dependencies]
|
|
42
|
+
pytest = ">=9.0.3,<10.0.0"
|
|
43
|
+
pydocstyle = "^6.3.0"
|
|
44
|
+
mkdocs = "^1.6.1"
|
|
45
|
+
|
|
46
|
+
[build-system]
|
|
47
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
48
|
+
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
def binary_search(target, space):
|
|
3
|
+
left = 0
|
|
4
|
+
right = len(space) - 1
|
|
5
|
+
|
|
6
|
+
while left <= right:
|
|
7
|
+
mid = (left + right) // 2
|
|
8
|
+
if space[mid] == target:
|
|
9
|
+
return True
|
|
10
|
+
elif space[mid] < target:
|
|
11
|
+
left = mid + 1
|
|
12
|
+
elif space[mid] > target:
|
|
13
|
+
right = mid -1
|
|
14
|
+
return False
|
|
15
|
+
|
|
16
|
+
if __name__ == '__main__':
|
|
17
|
+
space = list(input('Enter the SORTED list:'))
|
|
18
|
+
target = int(input('Enter a search target:'))
|
|
19
|
+
print(binary_search(target, space))
|
|
20
|
+
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
from heapq import heappush, heappop
|
|
3
|
+
|
|
4
|
+
# Priority queue implementation
|
|
5
|
+
class PriorityQueue:
|
|
6
|
+
def __init__(self):
|
|
7
|
+
self.pq = [] # List of entries arranged in a heap
|
|
8
|
+
self.entry_finder = {} # mapping of tassk to entries
|
|
9
|
+
# REMOVED = '<removed-task>' # placeholder for a removed task
|
|
10
|
+
self.counter = itertools.count() # unique sequence count
|
|
11
|
+
|
|
12
|
+
def __len__(self):
|
|
13
|
+
return len(self.pq)
|
|
14
|
+
|
|
15
|
+
def add_task(self, priority, task):
|
|
16
|
+
if task in self.entry_finder:
|
|
17
|
+
self.update_priority(priority, task)
|
|
18
|
+
return self
|
|
19
|
+
count = next(self.counter)
|
|
20
|
+
entry = [priority, count, task]
|
|
21
|
+
self.entry_finder[task] = entry
|
|
22
|
+
heappush(self.pq, entry)
|
|
23
|
+
|
|
24
|
+
def update_priority(self, priority, task):
|
|
25
|
+
entry = self.entry_finder[task]
|
|
26
|
+
count = next(self.counter)
|
|
27
|
+
entry[0], entry[1] = priority, count
|
|
28
|
+
|
|
29
|
+
def pop_task(self):
|
|
30
|
+
while self.pq:
|
|
31
|
+
priority, count, task = heappop(self.pq)
|
|
32
|
+
del self.entry_finder[task]
|
|
33
|
+
return priority, task
|
|
34
|
+
raise KeyError('YOU FAIL! pop from an empty priority queue')
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Graph:
|
|
38
|
+
def __init__(self, adjacency_list):
|
|
39
|
+
self.adjacency_list = adjacency_list
|
|
40
|
+
|
|
41
|
+
class Vertex:
|
|
42
|
+
def __init__(self, value):
|
|
43
|
+
self.value = value
|
|
44
|
+
|
|
45
|
+
class Edge:
|
|
46
|
+
def __init__(self, distance, vertex):
|
|
47
|
+
self.distance = distance
|
|
48
|
+
self.vertex = vertex
|
|
49
|
+
|
|
50
|
+
def dijkstra(graph, start, end):
|
|
51
|
+
previous = {v: None for v in graph.adjacency_list.keys()}
|
|
52
|
+
visited = {v: False for v in graph.adjacency_list.keys()}
|
|
53
|
+
distances = {v: float('inf') for v in graph.adjacency_list.keys()}
|
|
54
|
+
distances[start] = 0
|
|
55
|
+
queue = PriorityQueue()
|
|
56
|
+
queue.add_task(0, start)
|
|
57
|
+
path = []
|
|
58
|
+
while queue:
|
|
59
|
+
removed_distance, removed = queue.pop_task()
|
|
60
|
+
visited[removed] = True
|
|
61
|
+
if removed is end:
|
|
62
|
+
while previous[removed]:
|
|
63
|
+
path.append(removed.value)
|
|
64
|
+
removed = previous[removed]
|
|
65
|
+
path.append(start.value)
|
|
66
|
+
print(f'shortest distance to {end.value}: ', distances[end])
|
|
67
|
+
print(f'path to {end.value}: ', path[::-1])
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
for edge in graph.adjacency_list[removed]:
|
|
71
|
+
if visited[edge.vertex]:
|
|
72
|
+
continue
|
|
73
|
+
new_distance = removed_distance + edge.distance
|
|
74
|
+
if new_distance < distances[edge.vertex]:
|
|
75
|
+
distances[edge.vertex] = new_distance
|
|
76
|
+
previous[edge.vertex] = removed
|
|
77
|
+
queue.add_task(new_distance, edge.vertex)
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
# test
|
|
81
|
+
vertices = [Vertex('A'), Vertex('B'), Vertex('C'), Vertex('D'), Vertex('E'), Vertex('F'), Vertex('G'), Vertex('H')]
|
|
82
|
+
A, B, C, D, E, F, G, H = vertices
|
|
83
|
+
adj_list = {
|
|
84
|
+
A: [Edge(1.8, B), Edge(1.5, C), Edge(1.4, D)],
|
|
85
|
+
B: [Edge(1.8, A), Edge(1.6, E)],
|
|
86
|
+
C: [Edge(1.5, A), Edge(1.8, E), Edge(2.1, F)],
|
|
87
|
+
D: [Edge(1.4, A), Edge(2.7, F), Edge(2.4, G)],
|
|
88
|
+
E: [Edge(1.6, B), Edge(1.8, C), Edge(1.4, F), Edge(1.6, H)],
|
|
89
|
+
F: [Edge(2.1, C), Edge(2.7, D), Edge(1.4, E), Edge(1.3, G), Edge(1.2, H)],
|
|
90
|
+
G: [Edge(2.4, D), Edge(1.3, F), Edge(1.5, H)],
|
|
91
|
+
H: [Edge(1.6, E), Edge(1.2, F), Edge(1.5, G)]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
test_graph = Graph(adj_list)
|
|
95
|
+
|
|
96
|
+
dijkstra(test_graph, start=A, end=H)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
def is_palindrome(s: str) -> bool:
|
|
2
|
+
left = 0
|
|
3
|
+
right = len(s) - 1
|
|
4
|
+
|
|
5
|
+
while left < right:
|
|
6
|
+
if s[left] != s[right]:
|
|
7
|
+
return False
|
|
8
|
+
left += 1
|
|
9
|
+
right -= 1
|
|
10
|
+
|
|
11
|
+
return True
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if __name__ == '__main__':
|
|
15
|
+
word = input('Enter a word: ')
|
|
16
|
+
print(is_palindrome(word))
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
def compare_strings(s1: str, s2: str) -> int:
|
|
2
|
+
# return -1 if s1 < s2, 0 if s1 == s2, 1 if s1 > s2
|
|
3
|
+
if s1 == s2:
|
|
4
|
+
return 0
|
|
5
|
+
i, j = 0, 0
|
|
6
|
+
while i < len(s1) and j < len(s2):
|
|
7
|
+
if s1[i] < s2[j]:
|
|
8
|
+
return -1
|
|
9
|
+
elif s1[i] > s2[i]:
|
|
10
|
+
return 1
|
|
11
|
+
i += 1
|
|
12
|
+
j += 1
|
|
13
|
+
|
|
14
|
+
if len(s1) < len(s2):
|
|
15
|
+
return -1
|
|
16
|
+
else:
|
|
17
|
+
return 1
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
if __name__ == '__main__':
|
|
22
|
+
s1 = input('Enter the first string to compare:')
|
|
23
|
+
s2 = input('Enter the second string to compare:')
|
|
24
|
+
|
|
25
|
+
print(compare_strings(s1,s2))
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
def find_longest_palindrome(s):
|
|
2
|
+
longest = ''
|
|
3
|
+
len_long = 0
|
|
4
|
+
for i in range(len(s)):
|
|
5
|
+
left, right = i, i
|
|
6
|
+
while left >= 0 and right < len(s) and s[left] == s[right]:
|
|
7
|
+
if (right - left + 1) > len_long:
|
|
8
|
+
longest = s[left:right + 1]
|
|
9
|
+
len_long = right - left + 1
|
|
10
|
+
left -= 1
|
|
11
|
+
right += 1
|
|
12
|
+
|
|
13
|
+
left, right = i, i + 1
|
|
14
|
+
while left >= 0 and right < len(s) and s[left] == s[right]:
|
|
15
|
+
if (right - left + 1) > len_long:
|
|
16
|
+
longest = s[left:right + 1]
|
|
17
|
+
len_long = right - left + 1
|
|
18
|
+
left -= 1
|
|
19
|
+
right += 1
|
|
20
|
+
|
|
21
|
+
return longest
|
|
22
|
+
|
|
23
|
+
if __name__ == '__main__':
|
|
24
|
+
word = input('Enter a word:')
|
|
25
|
+
print(find_longest_palindrome(word))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
def reverse_in_place(arr):
|
|
2
|
+
left = 0
|
|
3
|
+
right = len(arr) - 1
|
|
4
|
+
|
|
5
|
+
while left < right:
|
|
6
|
+
temp = arr[left]
|
|
7
|
+
arr[left] = arr[right]
|
|
8
|
+
arr[right] = temp
|
|
9
|
+
left += 1
|
|
10
|
+
right -= 1
|
|
11
|
+
|
|
12
|
+
return arr
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if __name__ == '__main__':
|
|
16
|
+
arr = input('Input values for the array as one input without spaces:')
|
|
17
|
+
arr_reversed = reverse_in_place(list(arr))
|
|
18
|
+
print(arr_reversed)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
def rotate_array(nums: list[int], k: int) -> list[int]:
|
|
2
|
+
if not nums or k == 0:
|
|
3
|
+
return nums[:]
|
|
4
|
+
|
|
5
|
+
# normalize k
|
|
6
|
+
n = len(nums)
|
|
7
|
+
k %= n
|
|
8
|
+
# set the index, at which to split
|
|
9
|
+
split = n - k
|
|
10
|
+
# rotating by slicing
|
|
11
|
+
## combine the last k elements + the first k elements
|
|
12
|
+
return nums[split:] + nums[:split]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
if __name__=='__main__':
|
|
17
|
+
nums = list(input('Enter items for the list as a string:'))
|
|
18
|
+
k = int(input('Enter a number of times to rotate (integer):'))
|
|
19
|
+
print(rotate_array(nums, k))
|
|
20
|
+
|
|
21
|
+
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
def factorial(n):
|
|
2
|
+
if n == 1:
|
|
3
|
+
return n
|
|
4
|
+
else:
|
|
5
|
+
return n * factorial(n-1)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main(n, k):
|
|
9
|
+
# The binomial coefficient can also be called the "n choose k" formula
|
|
10
|
+
# n is the total number of available things
|
|
11
|
+
# k is the number of items to choose out of n
|
|
12
|
+
# example: in a 5-card poker game with a standard deck, k = 5, n = 52
|
|
13
|
+
a = factorial(n)
|
|
14
|
+
b = factorial(k)
|
|
15
|
+
c = factorial(n-k)
|
|
16
|
+
|
|
17
|
+
return a / (b*c)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__=='__main__':
|
|
21
|
+
print('*** Welcome to Binomial Coefficient! ***')
|
|
22
|
+
print()
|
|
23
|
+
print('The binomial coefficient can also be called the "n choose k" formula')
|
|
24
|
+
print('n is the total number of available things')
|
|
25
|
+
print('k is the number of items to choose out of n')
|
|
26
|
+
print('example: in a 5-card poker game with a standard deck, k = 5, n = 52')
|
|
27
|
+
n = int(input('Enter an (n) value: '))
|
|
28
|
+
k = int(input('Enter a (k) value: '))
|
|
29
|
+
num = main(n, k)
|
|
30
|
+
print(f'{num:,}')
|
|
31
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
def calculate_prob(x, r):
|
|
2
|
+
n = 7 - r
|
|
3
|
+
p_die_chosen = 1/n
|
|
4
|
+
p_dice_face = 0.333
|
|
5
|
+
p_event = p_die_chosen * p_dice_face
|
|
6
|
+
prob = 0
|
|
7
|
+
while x > 0:
|
|
8
|
+
prob += p_event
|
|
9
|
+
x -= 1
|
|
10
|
+
return prob
|
|
11
|
+
|
|
12
|
+
def calculate_odds(p):
|
|
13
|
+
return p/(1-p)
|
|
14
|
+
|
|
15
|
+
if __name__ == '__main__':
|
|
16
|
+
x = int(input('Enter the total number of positive outcomes: '))
|
|
17
|
+
r = int(input('Which round of the game is it? '))
|
|
18
|
+
prob = calculate_prob(x, r)
|
|
19
|
+
odds = calculate_odds(prob)
|
|
20
|
+
output = f'Probability: {prob:.4f}\nOdds: {odds:.4f}:1'
|
|
21
|
+
print(output)
|
|
22
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import random
|
|
2
|
+
random.seed(42)
|
|
3
|
+
|
|
4
|
+
def generate_shared_key(h, exp, p):
|
|
5
|
+
k = (h ** exp) % p
|
|
6
|
+
return k
|
|
7
|
+
|
|
8
|
+
class Initiator:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.p = random.randint(1, 50) # prime
|
|
11
|
+
self.g = random.randint(1, 50) # base
|
|
12
|
+
self.secret_x = random.randint(1, 50)
|
|
13
|
+
|
|
14
|
+
def calculate_ha(self):
|
|
15
|
+
self.ha = (self.g ** self.secret_x) % self.p
|
|
16
|
+
|
|
17
|
+
def send_p_and_g(self):
|
|
18
|
+
return self.p, self.g
|
|
19
|
+
|
|
20
|
+
def make_shared_key(self, hb):
|
|
21
|
+
self.shared_key = generate_shared_key(hb, self.secret_x, self.p)
|
|
22
|
+
|
|
23
|
+
class Receiver:
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self.secret_y = random.randint(1, 50)
|
|
26
|
+
|
|
27
|
+
def calculate_hb(self, **kwargs):
|
|
28
|
+
self.g = kwargs['g']
|
|
29
|
+
self.p = kwargs['p']
|
|
30
|
+
self.hb = (self.g ** self.secret_y) % self.p
|
|
31
|
+
|
|
32
|
+
def make_shared_key(self, ha):
|
|
33
|
+
self.shared_key = generate_shared_key(ha, self.secret_y, self.p)
|
|
34
|
+
|
|
35
|
+
def main():
|
|
36
|
+
i = Initiator()
|
|
37
|
+
r = Receiver()
|
|
38
|
+
i.calculate_ha()
|
|
39
|
+
r.calculate_hb(g=i.g, p=i.p)
|
|
40
|
+
i.make_shared_key(r.hb)
|
|
41
|
+
r.make_shared_key(i.ha)
|
|
42
|
+
print(f'Initiator Diffie Hellman: {i.ha}')
|
|
43
|
+
print(f'Receiver Diffie Hellman: {r.hb}')
|
|
44
|
+
print(f'Shared key match?')
|
|
45
|
+
return i.shared_key == r.shared_key
|
|
46
|
+
|
|
47
|
+
if __name__ == '__main__':
|
|
48
|
+
print(main())
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import math as m
|
|
2
|
+
|
|
3
|
+
def calculate_euclidean_distance(p: tuple, q: tuple):
|
|
4
|
+
return abs(m.sqrt(((p[0] - q[0])**2) + ((p[1]-q[1])**2)))
|
|
5
|
+
|
|
6
|
+
cities = [
|
|
7
|
+
{
|
|
8
|
+
'name': 'San Jose',
|
|
9
|
+
'coord': (37.3387, -121.8853),
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
'name': 'San Francisco',
|
|
13
|
+
'coord': (37.78, -122.42),
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
'name': 'South San Francisco',
|
|
17
|
+
'coord': (37.647, -122.4077),
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
'name': 'Los Angeles',
|
|
21
|
+
'coord': (34.0549, -118.2426),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
'name': 'Monterey',
|
|
25
|
+
'coord': (36.5973, -121.8978),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
'name': 'Napa',
|
|
29
|
+
'coord': (38.2975, -122.2869),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
'name': 'Redding',
|
|
33
|
+
'coord': (40.5754, -122.3836),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
'name': 'Sacramento',
|
|
37
|
+
'coord': (38.5781, -121.4944),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
'name': 'Reno',
|
|
41
|
+
'coord': (39.5299, -119.8143),
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
'name': 'Concord',
|
|
45
|
+
'coord': (37.9817, -122.0259),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
'name': 'Fairfield',
|
|
49
|
+
'coord': (38.2492, -122.0405)
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
'name': 'Dixon',
|
|
53
|
+
'coord': (38.4455, -121.8233)
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
'name': 'Davis',
|
|
57
|
+
'coord': (38.5449, -121.7405)
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
e = (37.99, -122.35)
|
|
62
|
+
|
|
63
|
+
e_distances = []
|
|
64
|
+
|
|
65
|
+
for c in cities:
|
|
66
|
+
d = calculate_euclidean_distance(e, c['coord'])
|
|
67
|
+
c['d'] = d
|
|
68
|
+
e_distances.append(c)
|
|
69
|
+
|
|
70
|
+
sd = sorted(e_distances, key=lambda city: city['d'])
|
|
71
|
+
|
|
72
|
+
t1 = 'Euclidean Distance'
|
|
73
|
+
spacer = '*' * (25 - len(t1))
|
|
74
|
+
|
|
75
|
+
print(f'{spacer} {t1} {spacer}')
|
|
76
|
+
|
|
77
|
+
for city in sd:
|
|
78
|
+
multiplier = int(4 + (city['d'] * 10))
|
|
79
|
+
s = "-" * multiplier + ">"
|
|
80
|
+
ns = ' ' * (80 - multiplier)
|
|
81
|
+
print(f'Richmond{s}{ns}{city["name"]} ({city["d"]:.2f})')
|
|
82
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
def main(a:int, b:int, c:int)->int:
|
|
2
|
+
s = (a + b + c)/2
|
|
3
|
+
area = (s * ((s-a)*(s-b)*(s-c))) ** 0.5
|
|
4
|
+
return area
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
if __name__=='__main__':
|
|
9
|
+
a = int(input(f'Enter the first length: '))
|
|
10
|
+
b = int(input(f'Enter the second length: ' ))
|
|
11
|
+
c = int(input(f'Enter a third length (must be less than {a + b}): '))
|
|
12
|
+
print(f'a = {type(a)}, b = {type(b)}, c = {type(c)}')
|
|
13
|
+
while c > (a + b):
|
|
14
|
+
print(f'The value {c} is too large')
|
|
15
|
+
c = int(input(f'Enter a length less than {a + b}: '))
|
|
16
|
+
area_of_a_triangle = main(a,b,c)
|
|
17
|
+
output = f'The area is {area_of_a_triangle}'
|
|
18
|
+
print(output)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
def multiplication_table(n):
|
|
2
|
+
table = []
|
|
3
|
+
for i in range(n):
|
|
4
|
+
row = []
|
|
5
|
+
for j in range(n):
|
|
6
|
+
row.append((i + 1) * (j + 1))
|
|
7
|
+
table.append(row)
|
|
8
|
+
return table
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if __name__ == '__main__':
|
|
12
|
+
num = int(input('Enter an integer:'))
|
|
13
|
+
table = multiplication_table(num)
|
|
14
|
+
for k in range(num):
|
|
15
|
+
print(k + 1, table[k])
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
def prime_finder(upper_bound):
|
|
2
|
+
primes = set()
|
|
3
|
+
composites = set()
|
|
4
|
+
n = 2
|
|
5
|
+
while n < upper_bound+1:
|
|
6
|
+
if n not in composites:
|
|
7
|
+
primes.add(n)
|
|
8
|
+
first_composite = n ** 2
|
|
9
|
+
composites.add(first_composite)
|
|
10
|
+
for x in range(n+1, upper_bound + 1):
|
|
11
|
+
c = x * n
|
|
12
|
+
if c <= upper_bound:
|
|
13
|
+
composites.add(c)
|
|
14
|
+
n += 1
|
|
15
|
+
|
|
16
|
+
return primes
|
|
17
|
+
|
|
18
|
+
def sieve_of_eratosthenes(upper_bound):
|
|
19
|
+
full_list = list()
|
|
20
|
+
filtered_list = list()
|
|
21
|
+
if upper_bound % 10 == 0:
|
|
22
|
+
num_rows = int(upper_bound / 10) + 1
|
|
23
|
+
else:
|
|
24
|
+
num_rows = int(upper_bound / 10) + 2
|
|
25
|
+
|
|
26
|
+
for j in range(1, num_rows):
|
|
27
|
+
m = list()
|
|
28
|
+
for a in range(1, 11):
|
|
29
|
+
val = (j-1) *10 + a
|
|
30
|
+
m.append(val)
|
|
31
|
+
|
|
32
|
+
primes = prime_finder(upper_bound)
|
|
33
|
+
k = list()
|
|
34
|
+
for x in range(1,11):
|
|
35
|
+
val = (j-1) * 10 + x
|
|
36
|
+
if val not in primes:
|
|
37
|
+
val = '.'
|
|
38
|
+
k.append(val)
|
|
39
|
+
|
|
40
|
+
filtered_list.append(k)
|
|
41
|
+
full_list.append(m)
|
|
42
|
+
|
|
43
|
+
output = {
|
|
44
|
+
'full_list': full_list,
|
|
45
|
+
'filtered_list': filtered_list,
|
|
46
|
+
}
|
|
47
|
+
return output
|
|
48
|
+
|
|
49
|
+
if __name__ == '__main__':
|
|
50
|
+
limit = int(input('Enter an upper boundary: '))
|
|
51
|
+
print('Sieve of Eratosthenes')
|
|
52
|
+
print()
|
|
53
|
+
print('https://share.google/images/HMAxLTOtUsFByKiyv')
|
|
54
|
+
|
|
55
|
+
matrix = sieve_of_eratosthenes(limit)
|
|
56
|
+
print('All numbers in given range:')
|
|
57
|
+
|
|
58
|
+
for i in range(len(matrix['full_list'])):
|
|
59
|
+
print(matrix['full_list'][i])
|
|
60
|
+
|
|
61
|
+
print()
|
|
62
|
+
print('Only the primes within the given range')
|
|
63
|
+
print()
|
|
64
|
+
for j in range(len(matrix['filtered_list'])):
|
|
65
|
+
print(matrix['filtered_list'][j])
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
from utils.console import console
|
|
4
|
+
|
|
5
|
+
class SquareTester:
|
|
6
|
+
def __init__(self):
|
|
7
|
+
self.roots = range(1, 26)
|
|
8
|
+
self.results = None
|
|
9
|
+
# self.squares = [x**2 for x in self.roots]
|
|
10
|
+
|
|
11
|
+
def test_squares_loop(self):
|
|
12
|
+
correct = 0
|
|
13
|
+
seen = {}
|
|
14
|
+
while len(self.roots) > 0:
|
|
15
|
+
if len(self.roots) == 1:
|
|
16
|
+
r = self.roots[0]
|
|
17
|
+
else:
|
|
18
|
+
r_pos = random.randint(0, len(self.roots) - 1)
|
|
19
|
+
r = self.roots[r_pos]
|
|
20
|
+
console.print(f'Square {r}')
|
|
21
|
+
response = int(input(''))
|
|
22
|
+
self.roots = [x for x in self.roots if x != r]
|
|
23
|
+
if response == r ** 2:
|
|
24
|
+
console.print('Correct!')
|
|
25
|
+
correct += 1
|
|
26
|
+
else:
|
|
27
|
+
console.print('Incorrect!')
|
|
28
|
+
|
|
29
|
+
self.results = (correct/25) * 100
|
|
30
|
+
|
|
31
|
+
def display_results(self):
|
|
32
|
+
if self.results:
|
|
33
|
+
console.print(f'Your last test session results: {self.results:.2f}%')
|
|
34
|
+
else:
|
|
35
|
+
console.print('You have no results')
|
|
36
|
+
|
|
37
|
+
def intro(self):
|
|
38
|
+
self.display_results()
|
|
39
|
+
console.print('Do you want to review?')
|
|
40
|
+
choice = int(input(''))
|
|
41
|
+
if choice == 1:
|
|
42
|
+
self.test_squares_loop()
|
|
43
|
+
self.intro()
|
|
44
|
+
elif choice == 0:
|
|
45
|
+
console.print('Good bye.')
|
|
46
|
+
else:
|
|
47
|
+
console.print('Invalid Choice', style='red bold')
|
|
48
|
+
self.intro()
|
|
49
|
+
|
|
50
|
+
if __name__=='__main__':
|
|
51
|
+
tester = SquareTester()
|
|
52
|
+
tester.intro()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import importlib.metadata
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from . import __version__
|
|
7
|
+
from .core import Orchestrator
|
|
8
|
+
|
|
9
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
10
|
+
prog = importlib.metadata.metadata("practice")["Name"]
|
|
11
|
+
parser = argparse.ArgumentParser(prog=prog, description=(__doc__ or ''))
|
|
12
|
+
parser.add_argument(
|
|
13
|
+
'--version',
|
|
14
|
+
action='version',
|
|
15
|
+
version=f'{prog} {__version__}',
|
|
16
|
+
help='Show version and exit',
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
'--silent',
|
|
21
|
+
action='store_true',
|
|
22
|
+
help='Disable audio.'
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
return parser
|
|
26
|
+
|
|
27
|
+
def main(argv: list[str]|None=None)->int:
|
|
28
|
+
argv=sys.argv[1:] if argv is None else argv
|
|
29
|
+
parser = _build_parser()
|
|
30
|
+
|
|
31
|
+
args=parser.parse_args(argv)
|
|
32
|
+
if hasattr(args, "func"):
|
|
33
|
+
return int(args.func(args) or 0)
|
|
34
|
+
o = Orchestrator('Orchestrator')
|
|
35
|
+
o.menu_loop(args.silent)
|
|
36
|
+
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__=='__main__':
|
|
41
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from utils.console import console
|
|
4
|
+
from rich.prompt import Prompt, IntPrompt
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
from rich.align import Align
|
|
7
|
+
from playsound3 import playsound
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _discover_scripts():
|
|
11
|
+
src_dir = Path(__file__).resolve().parent.parent
|
|
12
|
+
modules = ("algo", "math", "utils")
|
|
13
|
+
discovered = {}
|
|
14
|
+
|
|
15
|
+
for module in modules:
|
|
16
|
+
module_dir = src_dir / module
|
|
17
|
+
scripts = []
|
|
18
|
+
|
|
19
|
+
if module_dir.is_dir():
|
|
20
|
+
for file_path in sorted(module_dir.glob("*.py")):
|
|
21
|
+
if file_path.name == "__init__.py":
|
|
22
|
+
continue
|
|
23
|
+
scripts.append(f"{module}.{file_path.stem}")
|
|
24
|
+
|
|
25
|
+
discovered[module] = scripts
|
|
26
|
+
|
|
27
|
+
return discovered
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _chooser(options) -> int:
|
|
31
|
+
table = Table(title="Options")
|
|
32
|
+
table.add_column('#', justify='center', style='cyan', no_wrap=True)
|
|
33
|
+
table.add_column('Title', justify='center', style='cyan')
|
|
34
|
+
choices = ['0']
|
|
35
|
+
for i, j in enumerate(options):
|
|
36
|
+
choice_string = str(i + 1)
|
|
37
|
+
table.add_row(choice_string, j)
|
|
38
|
+
choices.append(choice_string)
|
|
39
|
+
centered_table = Align.center(table, vertical='middle')
|
|
40
|
+
console.print(centered_table)
|
|
41
|
+
choice_message = Align.center('Enter your choice:', vertical='middle')
|
|
42
|
+
console.print(choice_message)
|
|
43
|
+
choice = IntPrompt.ask('', choices=choices, default=0)
|
|
44
|
+
return choice
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Orchestrator:
|
|
48
|
+
def __init__(self, name):
|
|
49
|
+
self.name = name
|
|
50
|
+
self.scripts = _discover_scripts()
|
|
51
|
+
|
|
52
|
+
def menu_loop(self, silent=False):
|
|
53
|
+
sound = None
|
|
54
|
+
if not silent:
|
|
55
|
+
src_dir = Path(__file__).resolve().parent.parent
|
|
56
|
+
audio = src_dir / 'practice/static/test_audio.mp3'
|
|
57
|
+
sound = playsound(audio, block=False)
|
|
58
|
+
status = 1
|
|
59
|
+
while status == 1:
|
|
60
|
+
mod_options = ('algo', 'math', 'utils')
|
|
61
|
+
mod_choice = _chooser(mod_options)
|
|
62
|
+
mod_choice -= 1
|
|
63
|
+
if mod_choice == -1 or mod_choice > len(mod_options) - 1:
|
|
64
|
+
status = mod_choice
|
|
65
|
+
else:
|
|
66
|
+
script_options = self.scripts[mod_options[mod_choice]]
|
|
67
|
+
script_choice = _chooser(script_options)
|
|
68
|
+
script_choice -= 1
|
|
69
|
+
if script_choice == -1 or script_choice > len(script_options):
|
|
70
|
+
status = script_choice
|
|
71
|
+
else:
|
|
72
|
+
s = self.scripts[mod_options[mod_choice]][script_choice]
|
|
73
|
+
module, stem = s.split(".")
|
|
74
|
+
script_path = Path(__file__).resolve().parent.parent / module / f"{stem}.py"
|
|
75
|
+
subprocess.run(["python", str(script_path)])
|
|
76
|
+
|
|
77
|
+
if sound:
|
|
78
|
+
sound.stop()
|
|
79
|
+
return status
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Growth
|
|
2
|
+
Linear: x
|
|
3
|
+
- Could be cx, z+x or any other arbitrary arithmetic function preformed on x, and it will still be linear
|
|
4
|
+
|
|
5
|
+
Polynomial: x ^ y
|
|
6
|
+
- x raised to some power of x
|
|
7
|
+
|
|
8
|
+
Exponential: 2 ^ x, e ^ x, 10 ^ x
|
|
9
|
+
- Some base raised to the power of x
|
|
10
|
+
|
|
11
|
+
Factorial: x!, x ^ x
|
|
12
|
+
- Very fast growth rate
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
How to use logarithms
|
|
16
|
+
|
|
17
|
+
I have some quantity (y) when I have the input (x), but I don't know the function that gets x to y.
|
|
18
|
+
|
|
19
|
+
I think it might be this:
|
|
20
|
+
y = Ax^n
|
|
21
|
+
|
|
22
|
+
Graphing this function would yield a picture that would make it impossible to detirmine if n was 1.5 or 2. Taking the log of this function will help.
|
|
23
|
+
|
|
24
|
+
* when multiplying, as in the (Ax) of our function, add the logs
|
|
25
|
+
|
|
26
|
+
log y = log A + log x ^ n which is the same as log y = log A + n log x
|
|
27
|
+
|
|
28
|
+
|
|
Binary file
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
|
|
3
|
+
def days_between(start, end):
|
|
4
|
+
d0 = date(start.year, start.month, start.day)
|
|
5
|
+
d1 = date(end.year, end.month, end.day)
|
|
6
|
+
delta = d1 - d0
|
|
7
|
+
return delta.days
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DateObject:
|
|
11
|
+
|
|
12
|
+
def __init__(self, **kwargs):
|
|
13
|
+
self.year = kwargs['year']
|
|
14
|
+
self.month = kwargs['month']
|
|
15
|
+
self.day = kwargs['day']
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if __name__ == '__main__':
|
|
19
|
+
start_in = input('Enter a start date as comma-separated integers (YYYY, MM, DD): ')
|
|
20
|
+
end_in = input('Enter an end date as comma-separated integers (YYYY, MM, DD): ')
|
|
21
|
+
start_ints = start_in.split(',')
|
|
22
|
+
end_ints =end_in.split(',')
|
|
23
|
+
start = DateObject(year = int(start_ints[0]), month = int(start_ints[1]), day = int(start_ints[2]))
|
|
24
|
+
end = DateObject(year = int(end_ints[0]), month = int(end_ints[1]), day = int(end_ints[2]))
|
|
25
|
+
diff = days_between(start, end)
|
|
26
|
+
print(f'There are {diff} days between {start.year}, {start.month}, {start.day} and {end.year}, {end.month}, {end.day}')
|
|
27
|
+
|
|
28
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
main_subjects = ['cat', 'dog', 'police car', 'tree', 'banana', 'cell phone', 'smart phone']
|
|
4
|
+
|
|
5
|
+
backgrounds = ['outer space', 'a green field', 'a cold steel cage', 'a beach']
|
|
6
|
+
|
|
7
|
+
def gen_rand_int(x: list):
|
|
8
|
+
maximum = len(x) - 1
|
|
9
|
+
random_int = random.randint(0, maximum)
|
|
10
|
+
return random_int
|
|
11
|
+
|
|
12
|
+
def gen_phrase():
|
|
13
|
+
return f'{main_subjects[gen_rand_int(main_subjects)]} in {backgrounds[gen_rand_int(backgrounds)]}'
|
|
14
|
+
|
|
15
|
+
if __name__ == '__main__':
|
|
16
|
+
print(gen_phrase())
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
def display_rand_int(minimum, maximum):
|
|
4
|
+
random_int = random.randint(minimum, maximum)
|
|
5
|
+
print(f'The number is: {random_int}')
|
|
6
|
+
return random_int
|
|
7
|
+
|
|
8
|
+
def gen_random_float(mi, ma):
|
|
9
|
+
random_float = random.uniform(mi, ma)
|
|
10
|
+
print(f'The number is: {random_float}')
|
|
11
|
+
return random_float
|
|
12
|
+
|
|
13
|
+
if __name__ == '__main__':
|
|
14
|
+
options = ['Random Integer', 'Random Float']
|
|
15
|
+
for i,ch in enumerate(options):
|
|
16
|
+
print(f'{i+1}: {ch}')
|
|
17
|
+
choice = int(input('Enter your choice: '))
|
|
18
|
+
mi = input("Enter a min: ")
|
|
19
|
+
ma = input("Enter a max: ")
|
|
20
|
+
if choice == 1:
|
|
21
|
+
mi = int(mi)
|
|
22
|
+
ma = int(ma)
|
|
23
|
+
display_rand_int(mi, ma)
|
|
24
|
+
elif choice == 2:
|
|
25
|
+
mi = float(mi)
|
|
26
|
+
ma = float(ma)
|
|
27
|
+
gen_random_float(mi, ma)
|
|
28
|
+
else:
|
|
29
|
+
print('Womp Womp. Invalid entry, try again')
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import random
|
|
3
|
+
|
|
4
|
+
def typwriter_print(text: str):
|
|
5
|
+
for char in text:
|
|
6
|
+
delay = random.uniform(0.09, 0.13)
|
|
7
|
+
print(char, end='', flush=True)
|
|
8
|
+
time.sleep(delay)
|
|
9
|
+
print()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
if __name__ == '__main__':
|
|
13
|
+
typwriter_print('Welcome to Typwriter Print Demo!')
|
|
14
|
+
time.sleep(2)
|
|
15
|
+
typwriter_print('Enter some text below to try it yourself!')
|
|
16
|
+
text = input('Input: ')
|
|
17
|
+
typwriter_print(text)
|