stock-utils 0.2.3__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.
Files changed (34) hide show
  1. stock_utils-0.2.3/LICENSE +21 -0
  2. stock_utils-0.2.3/PKG-INFO +42 -0
  3. stock_utils-0.2.3/README.md +13 -0
  4. stock_utils-0.2.3/pyproject.toml +48 -0
  5. stock_utils-0.2.3/src/stock_utils/__init__.py +14 -0
  6. stock_utils-0.2.3/src/stock_utils/__main__.py +6 -0
  7. stock_utils-0.2.3/src/stock_utils/algo/__init__.py +1 -0
  8. stock_utils-0.2.3/src/stock_utils/algo/binary_search.py +32 -0
  9. stock_utils-0.2.3/src/stock_utils/algo/diffie_hellman.py +97 -0
  10. stock_utils-0.2.3/src/stock_utils/algo/dijkstras_algo.py +164 -0
  11. stock_utils-0.2.3/src/stock_utils/algo/is_palindrome.py +27 -0
  12. stock_utils-0.2.3/src/stock_utils/algo/lexicographical_ordering.py +38 -0
  13. stock_utils-0.2.3/src/stock_utils/algo/longest_palindrome_substring.py +37 -0
  14. stock_utils-0.2.3/src/stock_utils/algo/reverse_in_place.py +29 -0
  15. stock_utils-0.2.3/src/stock_utils/algo/rotate_array.py +34 -0
  16. stock_utils-0.2.3/src/stock_utils/cli.py +59 -0
  17. stock_utils-0.2.3/src/stock_utils/core.py +110 -0
  18. stock_utils-0.2.3/src/stock_utils/math/__init__.py +1 -0
  19. stock_utils-0.2.3/src/stock_utils/math/binomial_coefficient.py +50 -0
  20. stock_utils-0.2.3/src/stock_utils/math/camel_up.py +44 -0
  21. stock_utils-0.2.3/src/stock_utils/math/euclidean_distance.py +94 -0
  22. stock_utils-0.2.3/src/stock_utils/math/factorial.py +22 -0
  23. stock_utils-0.2.3/src/stock_utils/math/herons_formula_for_area.py +31 -0
  24. stock_utils-0.2.3/src/stock_utils/math/multiplication_table.py +27 -0
  25. stock_utils-0.2.3/src/stock_utils/math/sieve_of_eratosthenes.py +93 -0
  26. stock_utils-0.2.3/src/stock_utils/math/square_practice.py +62 -0
  27. stock_utils-0.2.3/src/stock_utils/static/notes.txt +28 -0
  28. stock_utils-0.2.3/src/stock_utils/static/test_audio.mp3 +0 -0
  29. stock_utils-0.2.3/src/stock_utils/utils/__init__.py +1 -0
  30. stock_utils-0.2.3/src/stock_utils/utils/console.py +5 -0
  31. stock_utils-0.2.3/src/stock_utils/utils/days_between.py +46 -0
  32. stock_utils-0.2.3/src/stock_utils/utils/pic_search_generator.py +34 -0
  33. stock_utils-0.2.3/src/stock_utils/utils/random_num.py +55 -0
  34. stock_utils-0.2.3/src/stock_utils/utils/typewriter.py +25 -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,42 @@
1
+ Metadata-Version: 2.4
2
+ Name: stock-utils
3
+ Version: 0.2.3
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.10
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.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Requires-Dist: mkdocs-material (>=9.7.7,<10.0.0)
21
+ Requires-Dist: mkdocs-rtd-dropdown (>=1.0.2,<2.0.0)
22
+ Requires-Dist: mkdocstrings[python] (>=1.0.6,<2.0.0)
23
+ Requires-Dist: playsound3 (>=3.3.1,<4.0.0)
24
+ Requires-Dist: python-dotenv (>=1.2.1,<2.0.0)
25
+ Requires-Dist: rich (>=15.0.0,<16.0.0)
26
+ Project-URL: Repository, https://github.com/janton42/stock_utils
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Stock Utils
30
+ Interactive examples of algorithms, mathematical concepts and utility functions.
31
+
32
+ Not for 'stocks', but made by a guy named Stock.
33
+ ## Requirements
34
+ `python >=3.10`
35
+
36
+ ## Installation
37
+ `pip install stock_utils`
38
+
39
+ ## License
40
+
41
+ MIT License. See `LICENSE`.
42
+
@@ -0,0 +1,13 @@
1
+ # Stock Utils
2
+ Interactive examples of algorithms, mathematical concepts and utility functions.
3
+
4
+ Not for 'stocks', but made by a guy named Stock.
5
+ ## Requirements
6
+ `python >=3.10`
7
+
8
+ ## Installation
9
+ `pip install stock_utils`
10
+
11
+ ## License
12
+
13
+ MIT License. See `LICENSE`.
@@ -0,0 +1,48 @@
1
+ [tool.poetry]
2
+ name = "stock-utils"
3
+ version = "0.2.3"
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="stock_utils", from = "src" },
21
+ ]
22
+
23
+ include = [
24
+ "src/disaster_factor/static/**"
25
+ ]
26
+ [tool.poetry.scripts]
27
+ stock-utils = "stock_utils.cli:main"
28
+
29
+ [tool.poetry.dependencies]
30
+ python = ">=3.10"
31
+ rich = ">=15.0.0,<16.0.0"
32
+ playsound3 = ">=3.3.1,<4.0.0"
33
+ python-dotenv = "^1.2.1"
34
+ mkdocs-material = "^9.7.7"
35
+ mkdocstrings = {extras = ["python"], version = "^1.0.6"}
36
+ mkdocs-rtd-dropdown = "^1.0.2"
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"
@@ -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__"]
@@ -0,0 +1,6 @@
1
+ """Command-line entry point for the stock_utils package."""
2
+
3
+ from .cli import main
4
+
5
+ if __name__=='__main__':
6
+ raise SystemExit(main())
@@ -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
+
@@ -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())