supermegaexplosion 0.3.0__tar.gz → 0.3.1__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.
- {supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/PKG-INFO +1 -1
- {supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/libbs/__init__.py +1 -1
- supermegaexplosion-0.3.1/libbs/ai_helper.py +288 -0
- {supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/pyproject.toml +1 -1
- {supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/supermegaexplosion.egg-info/PKG-INFO +1 -1
- supermegaexplosion-0.3.0/libbs/ai_helper.py +0 -74
- {supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/README.md +0 -0
- {supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/libbs/arrays.py +0 -0
- {supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/libbs/catalog.py +0 -0
- {supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/libbs/ds.py +0 -0
- {supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/libbs/input_utils.py +0 -0
- {supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/libbs/math_utils.py +0 -0
- {supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/libbs/strings.py +0 -0
- {supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/libbs/tools/__init__.py +0 -0
- {supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/libbs/tools/add_func.py +0 -0
- {supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/setup.cfg +0 -0
- {supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/supermegaexplosion.egg-info/SOURCES.txt +0 -0
- {supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/supermegaexplosion.egg-info/dependency_links.txt +0 -0
- {supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/supermegaexplosion.egg-info/requires.txt +0 -0
- {supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/supermegaexplosion.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import base64
|
|
4
|
+
import re
|
|
5
|
+
import time
|
|
6
|
+
import threading
|
|
7
|
+
|
|
8
|
+
_CONFIG_DIR = os.path.expanduser("~/.config/supermegaexplosion")
|
|
9
|
+
_CONFIG_FILE = os.path.join(_CONFIG_DIR, "config.json")
|
|
10
|
+
|
|
11
|
+
_EMBEDDED_KEY = base64.b64decode(
|
|
12
|
+
"QVEuQWI4Uk42SVhxTFVmcEpqVlhaTjY3S3lkRFUtNFoxWHZKWXFWMnBPMGMzZHZoSTRISXc="
|
|
13
|
+
).decode()
|
|
14
|
+
|
|
15
|
+
_MODELS = ["gemini-2.0-flash-lite", "gemini-2.0-flash"]
|
|
16
|
+
|
|
17
|
+
_lock = threading.Lock()
|
|
18
|
+
_request_times = []
|
|
19
|
+
|
|
20
|
+
_FALLBACKS = {
|
|
21
|
+
"reverse linked list": """def reverse_linked_list(head):
|
|
22
|
+
prev = None
|
|
23
|
+
cur = head
|
|
24
|
+
while cur:
|
|
25
|
+
nxt = cur.next
|
|
26
|
+
cur.next = prev
|
|
27
|
+
prev = cur
|
|
28
|
+
cur = nxt
|
|
29
|
+
return prev""",
|
|
30
|
+
"two sum": """def two_sum(nums, target):
|
|
31
|
+
seen = {}
|
|
32
|
+
for i, v in enumerate(nums):
|
|
33
|
+
comp = target - v
|
|
34
|
+
if comp in seen:
|
|
35
|
+
return [seen[comp], i]
|
|
36
|
+
seen[v] = i
|
|
37
|
+
return []""",
|
|
38
|
+
"palindrome": """def is_palindrome(s):
|
|
39
|
+
s = ''.join(c.lower() for c in s if c.isalnum())
|
|
40
|
+
return s == s[::-1]""",
|
|
41
|
+
"zig zag": """def zigzag(n):
|
|
42
|
+
for i in range(n):
|
|
43
|
+
print(' ' * (n - i - 1) + '* ' * (i + 1))
|
|
44
|
+
for i in range(n - 1):
|
|
45
|
+
print(' ' * (i + 1) + '* ' * (n - i - 1))""",
|
|
46
|
+
"anagram": """def is_anagram(s1, s2):
|
|
47
|
+
from collections import Counter
|
|
48
|
+
return Counter(s1) == Counter(s2)""",
|
|
49
|
+
"fibonacci": """def fibonacci(n):
|
|
50
|
+
if n <= 0: return []
|
|
51
|
+
seq = [0, 1]
|
|
52
|
+
for _ in range(2, n):
|
|
53
|
+
seq.append(seq[-1] + seq[-2])
|
|
54
|
+
return seq[:n]""",
|
|
55
|
+
"binary search": """def binary_search(arr, target):
|
|
56
|
+
lo, hi = 0, len(arr) - 1
|
|
57
|
+
while lo <= hi:
|
|
58
|
+
mid = (lo + hi) // 2
|
|
59
|
+
if arr[mid] == target: return mid
|
|
60
|
+
if arr[mid] < target: lo = mid + 1
|
|
61
|
+
else: hi = mid - 1
|
|
62
|
+
return -1""",
|
|
63
|
+
"bubble sort": """def bubble_sort(arr):
|
|
64
|
+
n = len(arr)
|
|
65
|
+
for i in range(n):
|
|
66
|
+
for j in range(n - 1 - i):
|
|
67
|
+
if arr[j] > arr[j + 1]:
|
|
68
|
+
arr[j], arr[j + 1] = arr[j + 1], arr[j]""",
|
|
69
|
+
"merge sort": """def merge_sort(arr):
|
|
70
|
+
if len(arr) <= 1: return arr
|
|
71
|
+
mid = len(arr) // 2
|
|
72
|
+
left = merge_sort(arr[:mid])
|
|
73
|
+
right = merge_sort(arr[mid:])
|
|
74
|
+
return merge(left, right)
|
|
75
|
+
|
|
76
|
+
def merge(a, b):
|
|
77
|
+
res = []
|
|
78
|
+
i = j = 0
|
|
79
|
+
while i < len(a) and j < len(b):
|
|
80
|
+
if a[i] <= b[j]:
|
|
81
|
+
res.append(a[i]); i += 1
|
|
82
|
+
else:
|
|
83
|
+
res.append(b[j]); j += 1
|
|
84
|
+
return res + a[i:] + b[j:]""",
|
|
85
|
+
"quick sort": """def quick_sort(arr):
|
|
86
|
+
if len(arr) <= 1: return arr
|
|
87
|
+
pivot = arr[0]
|
|
88
|
+
left = [x for x in arr[1:] if x <= pivot]
|
|
89
|
+
right = [x for x in arr[1:] if x > pivot]
|
|
90
|
+
return quick_sort(left) + [pivot] + quick_sort(right)""",
|
|
91
|
+
"linked list cycle": """def has_cycle(head):
|
|
92
|
+
slow = fast = head
|
|
93
|
+
while fast and fast.next:
|
|
94
|
+
slow = slow.next
|
|
95
|
+
fast = fast.next.next
|
|
96
|
+
if slow is fast: return True
|
|
97
|
+
return False""",
|
|
98
|
+
"level order": """from collections import deque
|
|
99
|
+
def level_order(root):
|
|
100
|
+
if not root: return []
|
|
101
|
+
res, q = [], deque([root])
|
|
102
|
+
while q:
|
|
103
|
+
level = []
|
|
104
|
+
for _ in range(len(q)):
|
|
105
|
+
node = q.popleft()
|
|
106
|
+
level.append(node.val)
|
|
107
|
+
if node.left: q.append(node.left)
|
|
108
|
+
if node.right: q.append(node.right)
|
|
109
|
+
res.append(level)
|
|
110
|
+
return res""",
|
|
111
|
+
"max subarray": """def max_subarray(nums):
|
|
112
|
+
cur = max_sum = nums[0]
|
|
113
|
+
for v in nums[1:]:
|
|
114
|
+
cur = max(v, cur + v)
|
|
115
|
+
max_sum = max(max_sum, cur)
|
|
116
|
+
return max_sum""",
|
|
117
|
+
"lru cache": """class LRUCache:
|
|
118
|
+
def __init__(self, cap):
|
|
119
|
+
self.cap = cap
|
|
120
|
+
self.cache = {}
|
|
121
|
+
self.order = []
|
|
122
|
+
|
|
123
|
+
def get(self, key):
|
|
124
|
+
if key not in self.cache: return -1
|
|
125
|
+
self.order.remove(key)
|
|
126
|
+
self.order.append(key)
|
|
127
|
+
return self.cache[key]
|
|
128
|
+
|
|
129
|
+
def put(self, key, val):
|
|
130
|
+
if key in self.cache:
|
|
131
|
+
self.order.remove(key)
|
|
132
|
+
elif len(self.cache) >= self.cap:
|
|
133
|
+
oldest = self.order.pop(0)
|
|
134
|
+
del self.cache[oldest]
|
|
135
|
+
self.cache[key] = val
|
|
136
|
+
self.order.append(key)""",
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _load_config():
|
|
141
|
+
try:
|
|
142
|
+
with open(_CONFIG_FILE) as f:
|
|
143
|
+
return json.load(f)
|
|
144
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
145
|
+
return {}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _save_config(data):
|
|
149
|
+
os.makedirs(_CONFIG_DIR, exist_ok=True)
|
|
150
|
+
with open(_CONFIG_FILE, "w") as f:
|
|
151
|
+
json.dump(data, f)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _get_key(api_key):
|
|
155
|
+
return api_key or os.environ.get("GEMINI_API_KEY") or _load_config().get("api_key")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _rate_limit_wait():
|
|
159
|
+
with _lock:
|
|
160
|
+
now = time.time()
|
|
161
|
+
cutoff = now - 60
|
|
162
|
+
recent = [t for t in _request_times if t > cutoff]
|
|
163
|
+
_request_times[:] = recent
|
|
164
|
+
if len(recent) >= 4:
|
|
165
|
+
wait = recent[0] + 60 - now
|
|
166
|
+
return max(wait, 1)
|
|
167
|
+
_request_times.append(now)
|
|
168
|
+
return 0
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _extract_retry_delay(error_text):
|
|
172
|
+
match = re.search(r"retry in (\d+(?:\.\d+)?)\s*s", error_text, re.IGNORECASE)
|
|
173
|
+
if match:
|
|
174
|
+
return float(match.group(1))
|
|
175
|
+
match = re.search(r"retryDelay.*?(\d+)s", error_text, re.IGNORECASE)
|
|
176
|
+
if match:
|
|
177
|
+
return float(match.group(1))
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _countdown(seconds, label="Retrying"):
|
|
182
|
+
bar_width = 20
|
|
183
|
+
interval = max(0.5, seconds / bar_width)
|
|
184
|
+
steps = max(1, int(seconds / interval))
|
|
185
|
+
for i in range(steps, 0, -1):
|
|
186
|
+
filled = int((steps - i + 1) * bar_width / steps)
|
|
187
|
+
bar = "#" * filled + "." * (bar_width - filled)
|
|
188
|
+
remaining = seconds * i / steps
|
|
189
|
+
print(f"\r [{bar}] {label} in {remaining:.0f}s ", end="", flush=True)
|
|
190
|
+
time.sleep(interval)
|
|
191
|
+
print("\r" + " " * 55 + "\r", end="", flush=True)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _find_fallback(query):
|
|
195
|
+
q = query.lower().strip()
|
|
196
|
+
for keyword, answer in _FALLBACKS.items():
|
|
197
|
+
if keyword in q:
|
|
198
|
+
return answer
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def respond_help(query=None, api_key=None):
|
|
203
|
+
interactive = query is None
|
|
204
|
+
if interactive:
|
|
205
|
+
query = input("Ask a DSA/Python question: ").strip()
|
|
206
|
+
if not query:
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
fallback = _find_fallback(query)
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
from google import genai
|
|
213
|
+
except ImportError:
|
|
214
|
+
print(
|
|
215
|
+
"Missing dependency. Install with:\n"
|
|
216
|
+
" pip install supermegaexplosion[ai]"
|
|
217
|
+
)
|
|
218
|
+
if fallback:
|
|
219
|
+
print("\n Here's a built-in answer:\n")
|
|
220
|
+
print(fallback)
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
key = _get_key(api_key)
|
|
224
|
+
|
|
225
|
+
if not key:
|
|
226
|
+
print(
|
|
227
|
+
"No API key found. Using a shared demo key (rate-limited).\n"
|
|
228
|
+
"Set your own free key at https://aistudio.google.com/ for better quota.\n"
|
|
229
|
+
)
|
|
230
|
+
key = _EMBEDDED_KEY
|
|
231
|
+
_save_config({"api_key": ""})
|
|
232
|
+
|
|
233
|
+
if key == _EMBEDDED_KEY and not _load_config().get("api_key"):
|
|
234
|
+
if interactive:
|
|
235
|
+
try:
|
|
236
|
+
ans = input("Enter your Gemini API key (or press Enter to use the demo key): ").strip()
|
|
237
|
+
if ans:
|
|
238
|
+
key = ans
|
|
239
|
+
_save_config({"api_key": ans})
|
|
240
|
+
except (EOFError, OSError):
|
|
241
|
+
pass
|
|
242
|
+
|
|
243
|
+
wait = _rate_limit_wait()
|
|
244
|
+
if wait > 0:
|
|
245
|
+
_countdown(wait, "Cooling down")
|
|
246
|
+
|
|
247
|
+
client = genai.Client(api_key=key)
|
|
248
|
+
last_error = None
|
|
249
|
+
|
|
250
|
+
for attempt in range(4):
|
|
251
|
+
for model_name in _MODELS:
|
|
252
|
+
try:
|
|
253
|
+
resp = client.models.generate_content(
|
|
254
|
+
model=model_name,
|
|
255
|
+
contents=(
|
|
256
|
+
"You are a DSA and Python coding assistant. "
|
|
257
|
+
"Answer concisely with code examples.\n\n"
|
|
258
|
+
f"Question: {query}"
|
|
259
|
+
),
|
|
260
|
+
config={"max_output_tokens": 512, "temperature": 0.3},
|
|
261
|
+
)
|
|
262
|
+
print(resp.text)
|
|
263
|
+
return
|
|
264
|
+
except Exception as e:
|
|
265
|
+
last_error = e
|
|
266
|
+
msg = str(e)
|
|
267
|
+
|
|
268
|
+
if "limit: 0" in msg or "PERMISSION_DENIED" in msg:
|
|
269
|
+
continue
|
|
270
|
+
|
|
271
|
+
if "429" in msg or "RESOURCE_EXHAUSTED" in msg:
|
|
272
|
+
delay = _extract_retry_delay(msg)
|
|
273
|
+
if delay:
|
|
274
|
+
_countdown(min(delay, 30), "Quota hit")
|
|
275
|
+
elif attempt < 3:
|
|
276
|
+
_countdown(5 * (attempt + 1), f"Retry {attempt + 1}/3")
|
|
277
|
+
else:
|
|
278
|
+
break
|
|
279
|
+
break
|
|
280
|
+
|
|
281
|
+
if fallback:
|
|
282
|
+
print("\n [!] AI unavailable. Here's a built-in answer:\n")
|
|
283
|
+
print(fallback)
|
|
284
|
+
else:
|
|
285
|
+
print(
|
|
286
|
+
"\n [X] AI quota exhausted. Set your own key or try again later:\n"
|
|
287
|
+
" set GEMINI_API_KEY=your_key"
|
|
288
|
+
)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "supermegaexplosion"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "DSA utility library — input helpers, algorithms, data structures, and AI assistant"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "Hadoopnb.ai", email = "hadoopnb@example.com" }]
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import json
|
|
3
|
-
import base64
|
|
4
|
-
|
|
5
|
-
_CONFIG_DIR = os.path.expanduser("~/.config/supermegaexplosion")
|
|
6
|
-
_CONFIG_FILE = os.path.join(_CONFIG_DIR, "config.json")
|
|
7
|
-
|
|
8
|
-
_EMBEDDED_KEY = base64.b64decode(
|
|
9
|
-
"QVEuQWI4Uk42TEFaTm14Tmh3UnJ0T0p1TWhFQjh3UzI2bDkzRWRfR2RPOWdGalJMbFdNY1E="
|
|
10
|
-
).decode()
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def _load_config():
|
|
14
|
-
try:
|
|
15
|
-
with open(_CONFIG_FILE) as f:
|
|
16
|
-
return json.load(f)
|
|
17
|
-
except (FileNotFoundError, json.JSONDecodeError):
|
|
18
|
-
return {}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _save_config(data):
|
|
22
|
-
os.makedirs(_CONFIG_DIR, exist_ok=True)
|
|
23
|
-
with open(_CONFIG_FILE, "w") as f:
|
|
24
|
-
json.dump(data, f)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def respond_help(query=None, api_key=None, model="gemini-2.0-flash-lite"):
|
|
28
|
-
if query is None:
|
|
29
|
-
query = input("Ask a DSA/Python question: ").strip()
|
|
30
|
-
if not query:
|
|
31
|
-
return
|
|
32
|
-
|
|
33
|
-
try:
|
|
34
|
-
from google import genai
|
|
35
|
-
except ImportError:
|
|
36
|
-
print(
|
|
37
|
-
"Missing dependency. Install with:\n"
|
|
38
|
-
" pip install supermegaexplosion[ai]"
|
|
39
|
-
)
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
key = api_key or os.environ.get("GEMINI_API_KEY") or _load_config().get("api_key")
|
|
43
|
-
|
|
44
|
-
if not key:
|
|
45
|
-
print(
|
|
46
|
-
"No API key found. Using a shared demo key (rate-limited).\n"
|
|
47
|
-
"Set your own free key at https://aistudio.google.com/ for better quota.\n"
|
|
48
|
-
)
|
|
49
|
-
key = _EMBEDDED_KEY
|
|
50
|
-
_save_config({"api_key": ""})
|
|
51
|
-
|
|
52
|
-
if key == _EMBEDDED_KEY and not _load_config().get("api_key"):
|
|
53
|
-
ans = input("Enter your Gemini API key (or press Enter to use the demo key): ").strip()
|
|
54
|
-
if ans:
|
|
55
|
-
key = ans
|
|
56
|
-
_save_config({"api_key": ans})
|
|
57
|
-
|
|
58
|
-
try:
|
|
59
|
-
client = genai.Client(api_key=key)
|
|
60
|
-
resp = client.models.generate_content(
|
|
61
|
-
model=model,
|
|
62
|
-
contents=(
|
|
63
|
-
"You are a DSA and Python coding assistant. "
|
|
64
|
-
"Answer concisely with code examples.\n\n"
|
|
65
|
-
f"Question: {query}"
|
|
66
|
-
),
|
|
67
|
-
config={
|
|
68
|
-
"max_output_tokens": 512,
|
|
69
|
-
"temperature": 0.3,
|
|
70
|
-
},
|
|
71
|
-
)
|
|
72
|
-
print(resp.text)
|
|
73
|
-
except Exception as e:
|
|
74
|
-
print(f"Error: {e}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/supermegaexplosion.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/supermegaexplosion.egg-info/requires.txt
RENAMED
|
File without changes
|
{supermegaexplosion-0.3.0 → supermegaexplosion-0.3.1}/supermegaexplosion.egg-info/top_level.txt
RENAMED
|
File without changes
|