stock-utils 0.2.3__py3-none-any.whl
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/__init__.py +14 -0
- stock_utils/__main__.py +6 -0
- stock_utils/algo/__init__.py +1 -0
- stock_utils/algo/binary_search.py +32 -0
- stock_utils/algo/diffie_hellman.py +97 -0
- stock_utils/algo/dijkstras_algo.py +164 -0
- stock_utils/algo/is_palindrome.py +27 -0
- stock_utils/algo/lexicographical_ordering.py +38 -0
- stock_utils/algo/longest_palindrome_substring.py +37 -0
- stock_utils/algo/reverse_in_place.py +29 -0
- stock_utils/algo/rotate_array.py +34 -0
- stock_utils/cli.py +59 -0
- stock_utils/core.py +110 -0
- stock_utils/math/__init__.py +1 -0
- stock_utils/math/binomial_coefficient.py +50 -0
- stock_utils/math/camel_up.py +44 -0
- stock_utils/math/euclidean_distance.py +94 -0
- stock_utils/math/factorial.py +22 -0
- stock_utils/math/herons_formula_for_area.py +31 -0
- stock_utils/math/multiplication_table.py +27 -0
- stock_utils/math/sieve_of_eratosthenes.py +93 -0
- stock_utils/math/square_practice.py +62 -0
- stock_utils/static/notes.txt +28 -0
- stock_utils/static/test_audio.mp3 +0 -0
- stock_utils/utils/__init__.py +1 -0
- stock_utils/utils/console.py +5 -0
- stock_utils/utils/days_between.py +46 -0
- stock_utils/utils/pic_search_generator.py +34 -0
- stock_utils/utils/random_num.py +55 -0
- stock_utils/utils/typewriter.py +25 -0
- stock_utils-0.2.3.dist-info/METADATA +42 -0
- stock_utils-0.2.3.dist-info/RECORD +35 -0
- stock_utils-0.2.3.dist-info/WHEEL +4 -0
- stock_utils-0.2.3.dist-info/entry_points.txt +3 -0
- stock_utils-0.2.3.dist-info/licenses/LICENSE +21 -0
stock_utils/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Top-level package for stock_utils.
|
|
2
|
+
|
|
3
|
+
This module exposes the package version metadata used by the CLI and tooling.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from importlib.metadata import PackageNotFoundError, version as _pkg_version
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
__version__=_pkg_version("stock_utils")
|
|
10
|
+
|
|
11
|
+
except PackageNotFoundError:
|
|
12
|
+
__version__="0+unknown"
|
|
13
|
+
|
|
14
|
+
__all__=["__version__"]
|
stock_utils/__main__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Algorithm implementations and utility functions for stock_utils."""
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
|
|
2
|
+
"""Binary search implementation for sorted collections."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def binary_search(target, space):
|
|
6
|
+
"""Return whether a target value exists in a sorted sequence.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
target: Value to look for in the sequence.
|
|
10
|
+
space: Sorted sequence of comparable values.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
True if the target is present; otherwise False.
|
|
14
|
+
"""
|
|
15
|
+
left = 0
|
|
16
|
+
right = len(space) - 1
|
|
17
|
+
|
|
18
|
+
while left <= right:
|
|
19
|
+
mid = (left + right) // 2
|
|
20
|
+
if space[mid] == target:
|
|
21
|
+
return True
|
|
22
|
+
elif space[mid] < target:
|
|
23
|
+
left = mid + 1
|
|
24
|
+
elif space[mid] > target:
|
|
25
|
+
right = mid - 1
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
if __name__ == '__main__':
|
|
29
|
+
space = list(input('Enter the SORTED list:'))
|
|
30
|
+
target = int(input('Enter a search target:'))
|
|
31
|
+
print(binary_search(target, space))
|
|
32
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Diffie-Hellman key exchange example implementation."""
|
|
2
|
+
|
|
3
|
+
import random
|
|
4
|
+
random.seed(42)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def generate_shared_key(h, exp, p):
|
|
8
|
+
"""Return the shared secret computed via modular exponentiation.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
h: Public value received from the peer.
|
|
12
|
+
exp: Secret exponent used to derive the shared value.
|
|
13
|
+
p: Prime modulus used in the exchange.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
The computed shared key value.
|
|
17
|
+
"""
|
|
18
|
+
k = (h ** exp) % p
|
|
19
|
+
return k
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Initiator:
|
|
23
|
+
"""Diffie-Hellman initiator that generates a public value and key."""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
"""Initialize the initiator with a random modulus, base, and secret."""
|
|
27
|
+
self.p = random.randint(1, 50) # prime
|
|
28
|
+
self.g = random.randint(1, 50) # base
|
|
29
|
+
self.secret_x = random.randint(1, 50)
|
|
30
|
+
|
|
31
|
+
def calculate_ha(self):
|
|
32
|
+
"""Compute the initiator's public value."""
|
|
33
|
+
self.ha = (self.g ** self.secret_x) % self.p
|
|
34
|
+
|
|
35
|
+
def send_p_and_g(self):
|
|
36
|
+
"""Return the prime modulus and generator for the peer.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
A tuple of ``(p, g)`` values.
|
|
40
|
+
"""
|
|
41
|
+
return self.p, self.g
|
|
42
|
+
|
|
43
|
+
def make_shared_key(self, hb):
|
|
44
|
+
"""Compute the shared secret using the receiver's public value.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
hb: Public value sent by the receiver.
|
|
48
|
+
"""
|
|
49
|
+
self.shared_key = generate_shared_key(hb, self.secret_x, self.p)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Receiver:
|
|
53
|
+
"""Diffie-Hellman receiver that derives a shared secret."""
|
|
54
|
+
|
|
55
|
+
def __init__(self):
|
|
56
|
+
"""Initialize the receiver with a random secret exponent."""
|
|
57
|
+
self.secret_y = random.randint(1, 50)
|
|
58
|
+
|
|
59
|
+
def calculate_hb(self, **kwargs):
|
|
60
|
+
"""Compute the receiver's public value from the shared parameters.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
**kwargs: Keyword arguments containing ``g`` and ``p``.
|
|
64
|
+
"""
|
|
65
|
+
self.g = kwargs['g']
|
|
66
|
+
self.p = kwargs['p']
|
|
67
|
+
self.hb = (self.g ** self.secret_y) % self.p
|
|
68
|
+
|
|
69
|
+
def make_shared_key(self, ha):
|
|
70
|
+
"""Compute the shared secret using the initiator's public value.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
ha: Public value sent by the initiator.
|
|
74
|
+
"""
|
|
75
|
+
self.shared_key = generate_shared_key(ha, self.secret_y, self.p)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def main():
|
|
79
|
+
"""Run a simple Diffie-Hellman exchange and report whether keys match.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
True if the initiator and receiver compute the same shared key.
|
|
83
|
+
"""
|
|
84
|
+
i = Initiator()
|
|
85
|
+
r = Receiver()
|
|
86
|
+
i.calculate_ha()
|
|
87
|
+
r.calculate_hb(g=i.g, p=i.p)
|
|
88
|
+
i.make_shared_key(r.hb)
|
|
89
|
+
r.make_shared_key(i.ha)
|
|
90
|
+
print(f'Initiator Diffie Hellman: {i.ha}')
|
|
91
|
+
print(f'Receiver Diffie Hellman: {r.hb}')
|
|
92
|
+
print(f'Shared key match?')
|
|
93
|
+
return i.shared_key == r.shared_key
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
if __name__ == '__main__':
|
|
97
|
+
print(main())
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Dijkstra's shortest path algorithm implementation."""
|
|
2
|
+
|
|
3
|
+
import itertools
|
|
4
|
+
from heapq import heappush, heappop
|
|
5
|
+
|
|
6
|
+
# Priority queue implementation
|
|
7
|
+
class PriorityQueue:
|
|
8
|
+
"""Priority queue used by Dijkstra's shortest-path algorithm.
|
|
9
|
+
|
|
10
|
+
Entries are stored as ``[priority, count, task]`` tuples so tasks can be
|
|
11
|
+
compared by priority while preserving insertion order for ties.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
"""Initialize the priority queue."""
|
|
16
|
+
self.pq = [] # List of entries arranged in a heap
|
|
17
|
+
self.entry_finder = {} # mapping of tassk to entries
|
|
18
|
+
# REMOVED = '<removed-task>' # placeholder for a removed task
|
|
19
|
+
self.counter = itertools.count() # unique sequence count
|
|
20
|
+
|
|
21
|
+
def __len__(self):
|
|
22
|
+
"""Return the number of queued tasks."""
|
|
23
|
+
return len(self.pq)
|
|
24
|
+
|
|
25
|
+
def add_task(self, priority, task):
|
|
26
|
+
"""Add a task to the queue or update its priority when present.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
priority: Relative priority for the task.
|
|
30
|
+
task: Task identifier to add or update.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
The queue instance for chaining convenience.
|
|
34
|
+
"""
|
|
35
|
+
if task in self.entry_finder:
|
|
36
|
+
self.update_priority(priority, task)
|
|
37
|
+
return self
|
|
38
|
+
count = next(self.counter)
|
|
39
|
+
entry = [priority, count, task]
|
|
40
|
+
self.entry_finder[task] = entry
|
|
41
|
+
heappush(self.pq, entry)
|
|
42
|
+
|
|
43
|
+
def update_priority(self, priority, task):
|
|
44
|
+
"""Update the priority for an existing queued task.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
priority: New priority value.
|
|
48
|
+
task: Task identifier to update.
|
|
49
|
+
"""
|
|
50
|
+
entry = self.entry_finder[task]
|
|
51
|
+
count = next(self.counter)
|
|
52
|
+
entry[0], entry[1] = priority, count
|
|
53
|
+
|
|
54
|
+
def pop_task(self):
|
|
55
|
+
"""Remove and return the next task with the lowest priority.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
A tuple of ``(priority, task)`` for the next task.
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
KeyError: If the queue is empty.
|
|
62
|
+
"""
|
|
63
|
+
while self.pq:
|
|
64
|
+
priority, count, task = heappop(self.pq)
|
|
65
|
+
del self.entry_finder[task]
|
|
66
|
+
return priority, task
|
|
67
|
+
raise KeyError('YOU FAIL! pop from an empty priority queue')
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Graph:
|
|
71
|
+
"""Graph container backed by an adjacency list."""
|
|
72
|
+
|
|
73
|
+
def __init__(self, adjacency_list):
|
|
74
|
+
"""Initialize the graph with an adjacency list.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
adjacency_list: Mapping of vertices to their outgoing edges.
|
|
78
|
+
"""
|
|
79
|
+
self.adjacency_list = adjacency_list
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Vertex:
|
|
83
|
+
"""Graph vertex containing a value."""
|
|
84
|
+
|
|
85
|
+
def __init__(self, value):
|
|
86
|
+
"""Initialize a vertex.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
value: Data stored on the vertex.
|
|
90
|
+
"""
|
|
91
|
+
self.value = value
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class Edge:
|
|
95
|
+
"""Weighted edge connecting a vertex to another vertex."""
|
|
96
|
+
|
|
97
|
+
def __init__(self, distance, vertex):
|
|
98
|
+
"""Initialize an edge.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
distance: Cost of traversing the edge.
|
|
102
|
+
vertex: Destination vertex reached by the edge.
|
|
103
|
+
"""
|
|
104
|
+
self.distance = distance
|
|
105
|
+
self.vertex = vertex
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def dijkstra(graph, start, end):
|
|
109
|
+
"""Find and print the shortest path from ``start`` to ``end``.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
graph: Graph whose adjacency list contains weighted ``Edge`` objects.
|
|
113
|
+
start: Starting vertex.
|
|
114
|
+
end: Destination vertex.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
None. The shortest distance and path are printed to stdout.
|
|
118
|
+
"""
|
|
119
|
+
previous = {v: None for v in graph.adjacency_list.keys()}
|
|
120
|
+
visited = {v: False for v in graph.adjacency_list.keys()}
|
|
121
|
+
distances = {v: float('inf') for v in graph.adjacency_list.keys()}
|
|
122
|
+
distances[start] = 0
|
|
123
|
+
queue = PriorityQueue()
|
|
124
|
+
queue.add_task(0, start)
|
|
125
|
+
path = []
|
|
126
|
+
while queue:
|
|
127
|
+
removed_distance, removed = queue.pop_task()
|
|
128
|
+
visited[removed] = True
|
|
129
|
+
if removed is end:
|
|
130
|
+
while previous[removed]:
|
|
131
|
+
path.append(removed.value)
|
|
132
|
+
removed = previous[removed]
|
|
133
|
+
path.append(start.value)
|
|
134
|
+
print(f'shortest distance to {end.value}: ', distances[end])
|
|
135
|
+
print(f'path to {end.value}: ', path[::-1])
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
for edge in graph.adjacency_list[removed]:
|
|
139
|
+
if visited[edge.vertex]:
|
|
140
|
+
continue
|
|
141
|
+
new_distance = removed_distance + edge.distance
|
|
142
|
+
if new_distance < distances[edge.vertex]:
|
|
143
|
+
distances[edge.vertex] = new_distance
|
|
144
|
+
previous[edge.vertex] = removed
|
|
145
|
+
queue.add_task(new_distance, edge.vertex)
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
# test
|
|
149
|
+
vertices = [Vertex('A'), Vertex('B'), Vertex('C'), Vertex('D'), Vertex('E'), Vertex('F'), Vertex('G'), Vertex('H')]
|
|
150
|
+
A, B, C, D, E, F, G, H = vertices
|
|
151
|
+
adj_list = {
|
|
152
|
+
A: [Edge(1.8, B), Edge(1.5, C), Edge(1.4, D)],
|
|
153
|
+
B: [Edge(1.8, A), Edge(1.6, E)],
|
|
154
|
+
C: [Edge(1.5, A), Edge(1.8, E), Edge(2.1, F)],
|
|
155
|
+
D: [Edge(1.4, A), Edge(2.7, F), Edge(2.4, G)],
|
|
156
|
+
E: [Edge(1.6, B), Edge(1.8, C), Edge(1.4, F), Edge(1.6, H)],
|
|
157
|
+
F: [Edge(2.1, C), Edge(2.7, D), Edge(1.4, E), Edge(1.3, G), Edge(1.2, H)],
|
|
158
|
+
G: [Edge(2.4, D), Edge(1.3, F), Edge(1.5, H)],
|
|
159
|
+
H: [Edge(1.6, E), Edge(1.2, F), Edge(1.5, G)]
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
test_graph = Graph(adj_list)
|
|
163
|
+
|
|
164
|
+
dijkstra(test_graph, start=A, end=H)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Palindrome checking utilities."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def is_palindrome(s: str) -> bool:
|
|
5
|
+
"""Return whether a string reads the same forwards and backwards.
|
|
6
|
+
|
|
7
|
+
Args:
|
|
8
|
+
s: String to evaluate.
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
True if the string is a palindrome; otherwise False.
|
|
12
|
+
"""
|
|
13
|
+
left = 0
|
|
14
|
+
right = len(s) - 1
|
|
15
|
+
|
|
16
|
+
while left < right:
|
|
17
|
+
if s[left] != s[right]:
|
|
18
|
+
return False
|
|
19
|
+
left += 1
|
|
20
|
+
right -= 1
|
|
21
|
+
|
|
22
|
+
return True
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if __name__ == '__main__':
|
|
26
|
+
word = input('Enter a word: ')
|
|
27
|
+
print(is_palindrome(word))
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Lexicographical string comparison utilities."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def compare_strings(s1: str, s2: str) -> int:
|
|
5
|
+
"""Compare two strings lexicographically.
|
|
6
|
+
|
|
7
|
+
Args:
|
|
8
|
+
s1: First string to compare.
|
|
9
|
+
s2: Second string to compare.
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
-1 if ``s1`` sorts before ``s2``, 0 if they are equal, and 1 if ``s1``
|
|
13
|
+
sorts after ``s2``.
|
|
14
|
+
"""
|
|
15
|
+
# return -1 if s1 < s2, 0 if s1 == s2, 1 if s1 > s2
|
|
16
|
+
if s1 == s2:
|
|
17
|
+
return 0
|
|
18
|
+
i, j = 0, 0
|
|
19
|
+
while i < len(s1) and j < len(s2):
|
|
20
|
+
if s1[i] < s2[j]:
|
|
21
|
+
return -1
|
|
22
|
+
elif s1[i] > s2[i]:
|
|
23
|
+
return 1
|
|
24
|
+
i += 1
|
|
25
|
+
j += 1
|
|
26
|
+
|
|
27
|
+
if len(s1) < len(s2):
|
|
28
|
+
return -1
|
|
29
|
+
else:
|
|
30
|
+
return 1
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
if __name__ == '__main__':
|
|
35
|
+
s1 = input('Enter the first string to compare:')
|
|
36
|
+
s2 = input('Enter the second string to compare:')
|
|
37
|
+
|
|
38
|
+
print(compare_strings(s1,s2))
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Longest palindromic substring search implementation."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def find_longest_palindrome(s):
|
|
5
|
+
"""Return the longest palindromic substring within a string.
|
|
6
|
+
|
|
7
|
+
Args:
|
|
8
|
+
s: String to inspect.
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
The longest palindromic substring found in ``s``. If none exists, an
|
|
12
|
+
empty string is returned.
|
|
13
|
+
"""
|
|
14
|
+
longest = ''
|
|
15
|
+
len_long = 0
|
|
16
|
+
for i in range(len(s)):
|
|
17
|
+
left, right = i, i
|
|
18
|
+
while left >= 0 and right < len(s) and s[left] == s[right]:
|
|
19
|
+
if (right - left + 1) > len_long:
|
|
20
|
+
longest = s[left:right + 1]
|
|
21
|
+
len_long = right - left + 1
|
|
22
|
+
left -= 1
|
|
23
|
+
right += 1
|
|
24
|
+
|
|
25
|
+
left, right = i, i + 1
|
|
26
|
+
while left >= 0 and right < len(s) and s[left] == s[right]:
|
|
27
|
+
if (right - left + 1) > len_long:
|
|
28
|
+
longest = s[left:right + 1]
|
|
29
|
+
len_long = right - left + 1
|
|
30
|
+
left -= 1
|
|
31
|
+
right += 1
|
|
32
|
+
|
|
33
|
+
return longest
|
|
34
|
+
|
|
35
|
+
if __name__ == '__main__':
|
|
36
|
+
word = input('Enter a word:')
|
|
37
|
+
print(find_longest_palindrome(word))
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""In-place list reversal utility."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def reverse_in_place(arr):
|
|
5
|
+
"""Reverse a list in place.
|
|
6
|
+
|
|
7
|
+
Args:
|
|
8
|
+
arr: List to reverse.
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
The reversed list.
|
|
12
|
+
"""
|
|
13
|
+
left = 0
|
|
14
|
+
right = len(arr) - 1
|
|
15
|
+
|
|
16
|
+
while left < right:
|
|
17
|
+
temp = arr[left]
|
|
18
|
+
arr[left] = arr[right]
|
|
19
|
+
arr[right] = temp
|
|
20
|
+
left += 1
|
|
21
|
+
right -= 1
|
|
22
|
+
|
|
23
|
+
return arr
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if __name__ == '__main__':
|
|
27
|
+
arr = input('Input values for the array as one input without spaces:')
|
|
28
|
+
arr_reversed = reverse_in_place(list(arr))
|
|
29
|
+
print(arr_reversed)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Array rotation utilities."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def rotate_array(nums: list[int], k: int) -> list[int]:
|
|
5
|
+
"""Rotate a list to the right by ``k`` positions.
|
|
6
|
+
|
|
7
|
+
Args:
|
|
8
|
+
nums: Sequence of integers to rotate.
|
|
9
|
+
k: Number of positions to rotate. Values larger than the list length are
|
|
10
|
+
normalized modulo the list size.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
A new list with the elements rotated to the right by ``k`` positions.
|
|
14
|
+
"""
|
|
15
|
+
if not nums or k == 0:
|
|
16
|
+
return nums[:]
|
|
17
|
+
|
|
18
|
+
# normalize k
|
|
19
|
+
n = len(nums)
|
|
20
|
+
k %= n
|
|
21
|
+
# set the index, at which to split
|
|
22
|
+
split = n - k
|
|
23
|
+
# rotating by slicing
|
|
24
|
+
## combine the last k elements + the first k elements
|
|
25
|
+
return nums[split:] + nums[:split]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
if __name__=='__main__':
|
|
30
|
+
nums = list(input('Enter items for the list as a string:'))
|
|
31
|
+
k = int(input('Enter a number of times to rotate (integer):'))
|
|
32
|
+
print(rotate_array(nums, k))
|
|
33
|
+
|
|
34
|
+
|
stock_utils/cli.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Command-line interface for stock_utils."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import importlib.metadata
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from . import __version__
|
|
9
|
+
from .core import Orchestrator
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
13
|
+
"""Build the command-line parser for the stock_utils CLI.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
Configured ``ArgumentParser`` instance with version and silent options.
|
|
17
|
+
"""
|
|
18
|
+
prog = importlib.metadata.metadata("stock_utils")["Name"]
|
|
19
|
+
parser = argparse.ArgumentParser(prog=prog, description=(__doc__ or ''))
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
'--version',
|
|
22
|
+
action='version',
|
|
23
|
+
version=f'{prog} {__version__}',
|
|
24
|
+
help='Show version and exit',
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
parser.add_argument(
|
|
28
|
+
'--silent',
|
|
29
|
+
action='store_true',
|
|
30
|
+
help='Disable audio.'
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return parser
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def main(argv: list[str]|None=None)->int:
|
|
37
|
+
"""Run the stock_utils command-line entry point.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
argv: Optional list of command-line arguments to parse. If omitted,
|
|
41
|
+
``sys.argv[1:]`` is used.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Exit status code for the CLI.
|
|
45
|
+
"""
|
|
46
|
+
argv=sys.argv[1:] if argv is None else argv
|
|
47
|
+
parser = _build_parser()
|
|
48
|
+
|
|
49
|
+
args=parser.parse_args(argv)
|
|
50
|
+
if hasattr(args, "func"):
|
|
51
|
+
return int(args.func(args) or 0)
|
|
52
|
+
o = Orchestrator('Orchestrator')
|
|
53
|
+
o.menu_loop(args.silent)
|
|
54
|
+
|
|
55
|
+
return 0
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__=='__main__':
|
|
59
|
+
raise SystemExit(main())
|
stock_utils/core.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Core orchestration logic for launching project scripts."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from stock_utils.utils.console import console
|
|
6
|
+
from rich.prompt import Prompt, IntPrompt
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
from rich.align import Align
|
|
9
|
+
from playsound3 import playsound
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _discover_scripts():
|
|
13
|
+
"""Discover runnable Python scripts under the project modules.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
A dictionary keyed by module name containing sorted script names such as
|
|
17
|
+
``"algo.binary_search"``.
|
|
18
|
+
"""
|
|
19
|
+
src_dir = Path(__file__).resolve().parent
|
|
20
|
+
modules = ("algo", "math", "utils")
|
|
21
|
+
discovered = {}
|
|
22
|
+
|
|
23
|
+
for module in modules:
|
|
24
|
+
module_dir = src_dir / module
|
|
25
|
+
scripts = []
|
|
26
|
+
|
|
27
|
+
if module_dir.is_dir():
|
|
28
|
+
for file_path in sorted(module_dir.glob("*.py")):
|
|
29
|
+
if file_path.name == "__init__.py":
|
|
30
|
+
continue
|
|
31
|
+
scripts.append(f"{module}.{file_path.stem}")
|
|
32
|
+
|
|
33
|
+
discovered[module] = scripts
|
|
34
|
+
|
|
35
|
+
return discovered
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _chooser(options) -> int:
|
|
39
|
+
"""Display a selection table and return the user's choice index.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
options: Sequence of labels to show in the chooser.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
The selected option number, with ``0`` reserved for the exit choice.
|
|
46
|
+
"""
|
|
47
|
+
table = Table(title="Options")
|
|
48
|
+
table.add_column('#', justify='center', style='cyan', no_wrap=True)
|
|
49
|
+
table.add_column('Title', justify='center', style='cyan')
|
|
50
|
+
choices = ['0']
|
|
51
|
+
for i, j in enumerate(options):
|
|
52
|
+
choice_string = str(i + 1)
|
|
53
|
+
table.add_row(choice_string, j)
|
|
54
|
+
choices.append(choice_string)
|
|
55
|
+
centered_table = Align.center(table, vertical='middle')
|
|
56
|
+
console.print(centered_table)
|
|
57
|
+
choice_message = Align.center('Enter your choice:', vertical='middle')
|
|
58
|
+
console.print(choice_message)
|
|
59
|
+
choice = IntPrompt.ask('', choices=choices, default=0)
|
|
60
|
+
return choice
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Orchestrator:
|
|
64
|
+
"""Menu-driven orchestrator for launching project scripts."""
|
|
65
|
+
|
|
66
|
+
def __init__(self, name):
|
|
67
|
+
"""Initialize the orchestrator with a display name.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
name: Human-readable name for the orchestrator instance.
|
|
71
|
+
"""
|
|
72
|
+
self.name = name
|
|
73
|
+
self.scripts = _discover_scripts()
|
|
74
|
+
|
|
75
|
+
def menu_loop(self, silent=False):
|
|
76
|
+
"""Run the primary interactive menu until the user exits.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
silent: If ``True``, suppress the startup audio.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The exit status code chosen by the user.
|
|
83
|
+
"""
|
|
84
|
+
sound = None
|
|
85
|
+
if not silent:
|
|
86
|
+
src_dir = Path(__file__).resolve().parent.parent
|
|
87
|
+
audio = src_dir / 'stock_utils/static/test_audio.mp3'
|
|
88
|
+
sound = playsound(audio, block=False)
|
|
89
|
+
status = 1
|
|
90
|
+
while status == 1:
|
|
91
|
+
mod_options = ('algo', 'math', 'utils')
|
|
92
|
+
mod_choice = _chooser(mod_options)
|
|
93
|
+
mod_choice -= 1
|
|
94
|
+
if mod_choice == -1 or mod_choice > len(mod_options) - 1:
|
|
95
|
+
status = mod_choice
|
|
96
|
+
else:
|
|
97
|
+
script_options = self.scripts[mod_options[mod_choice]]
|
|
98
|
+
script_choice = _chooser(script_options)
|
|
99
|
+
script_choice -= 1
|
|
100
|
+
if script_choice == -1 or script_choice > len(script_options):
|
|
101
|
+
status = script_choice
|
|
102
|
+
else:
|
|
103
|
+
s = self.scripts[mod_options[mod_choice]][script_choice]
|
|
104
|
+
module, stem = s.split(".")
|
|
105
|
+
script_path = Path(__file__).resolve().parent / module / f"{stem}.py"
|
|
106
|
+
subprocess.run(["python", str(script_path)])
|
|
107
|
+
|
|
108
|
+
if sound:
|
|
109
|
+
sound.stop()
|
|
110
|
+
return status
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Mathematical utilities and sample exercises for stock_utils."""
|