algomanim 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Benedict Abub
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,21 @@
1
+ Metadata-Version: 2.3
2
+ Name: algomanim
3
+ Version: 0.1.0
4
+ Summary: Manim-powered algorithm tasks visualizations for LeetCode and beyond
5
+ License: MIT
6
+ Author: Benedict Abub
7
+ Author-email: fkhurkhd2123dfs@tutamail.com
8
+ Requires-Python: >=3.9,<4.0
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Requires-Dist: manim (>=0.19.0,<0.20.0)
17
+ Project-URL: Homepage, https://github.com/benabub/algomanim
18
+ Project-URL: Repository, https://github.com/benabub/algomanim
19
+ Description-Content-Type: text/markdown
20
+
21
+
File without changes
@@ -0,0 +1,17 @@
1
+ [tool.poetry]
2
+ name = "algomanim"
3
+ version = "0.1.0"
4
+ description = "Manim-powered algorithm tasks visualizations for LeetCode and beyond"
5
+ authors = ["Benedict Abub <fkhurkhd2123dfs@tutamail.com>"]
6
+ license = "MIT"
7
+ readme = "README.md"
8
+ repository = "https://github.com/benabub/algomanim"
9
+ homepage = "https://github.com/benabub/algomanim"
10
+
11
+ [tool.poetry.dependencies]
12
+ python = "^3.9"
13
+ manim = "^0.19.0"
14
+
15
+ [build-system]
16
+ requires = ["poetry-core>=1.0.0"]
17
+ build-backend = "poetry.core.masonry.api"
File without changes
@@ -0,0 +1,411 @@
1
+ import manim as mn
2
+
3
+
4
+ class Array(mn.VGroup):
5
+ def __init__(
6
+ self,
7
+ arr: list,
8
+ position: mn.Mobject,
9
+ bg_color=mn.DARK_GRAY
10
+ ):
11
+ """
12
+ Create a Manim array visualization as a VGroup.
13
+
14
+ Args:
15
+ arr (list): The array of values to visualize.
16
+ position (mn.Mobject): The position to place the array
17
+ on the screen.
18
+
19
+ Attributes:
20
+ arr (list): The data array.
21
+ sq_mob (mn.VGroup): Group of square mobjects for array cells.
22
+ num_mob (mn.VGroup): Group of text mobjects for array values.
23
+ """
24
+ # Call __init__ of the parent classes
25
+ super().__init__()
26
+ # Add class attributes
27
+ self.arr = arr
28
+ self.bg_color = bg_color
29
+
30
+ # Construction: Create square mobjects for each array element
31
+ # NB: if opacity is not specified, it will be set to None
32
+ # and some methods will break for unknown reasons
33
+ self.sq_mob = mn.VGroup(*[
34
+ mn.Square().set_fill(
35
+ self.bg_color, 1).set_width(0.7).set_height(0.7)
36
+ for _ in arr
37
+ ])
38
+ # Construction: Arrange squares in a row
39
+ self.sq_mob.arrange(mn.RIGHT, buff=0.1)
40
+ # Construction: Move array to the specified position
41
+ self.sq_mob.move_to(position)
42
+
43
+ # Construction: Create text mobjects and center them in squares
44
+ self.num_mob = mn.VGroup(*[
45
+ mn.Text(str(num)).move_to(square)
46
+ for num, square in zip(arr, self.sq_mob)
47
+ ])
48
+
49
+ # Create pointers as a list with top and bottom groups
50
+ self.pointers = [[], []] # [0] for top, [1] for bottom
51
+
52
+ for square in self.sq_mob:
53
+ # Create top triangles (3 per square)
54
+ top_tri_group = mn.VGroup(*[
55
+ mn.Triangle(
56
+ color=self.bg_color,
57
+ )
58
+ .scale([0.5, 1, 1])
59
+ .scale(0.1)
60
+ .rotate(mn.PI)
61
+ for _ in range(3)
62
+ ])
63
+ # Arrange top triangles horizontally above the square
64
+ top_tri_group.arrange(mn.RIGHT, buff=0.08)
65
+ top_tri_group.next_to(square, mn.UP, buff=0.15)
66
+ self.pointers[0].append(top_tri_group)
67
+
68
+ # Create bottom triangles (3 per square)
69
+ bottom_tri_group = mn.VGroup(*[
70
+ mn.Triangle(
71
+ color=self.bg_color,
72
+ )
73
+ .scale([0.5, 1, 1])
74
+ .scale(0.1)
75
+ for _ in range(3)
76
+ ])
77
+ # Arrange bottom triangles horizontally below the square
78
+ bottom_tri_group.arrange(mn.RIGHT, buff=0.08)
79
+ bottom_tri_group.next_to(square, mn.DOWN, buff=0.15)
80
+ self.pointers[1].append(bottom_tri_group)
81
+
82
+ # Adds local objects as instance attributes
83
+ self.add(self.sq_mob, self.num_mob)
84
+ self.add(*[ptr for group in self.pointers for ptr in group])
85
+
86
+ def first_appear(self, scene, time=0.5):
87
+ scene.play(mn.FadeIn(self), run_time=time)
88
+
89
+ def pointers_1(
90
+ self, i: int,
91
+ pos: int = 0,
92
+ i_color=mn.GREEN,
93
+ ):
94
+ """
95
+ Highlight a single pointer at one side (top | bottom) in the
96
+ array visualization.
97
+
98
+ Args:
99
+ i (int): Index of the block whose pointer to highlight.
100
+ pos (int): 0 for top pointers, 1 for bottom. Defaults to 0.
101
+ i_color: Color for the highlighted pointer. Defaults to mn.GREEN.
102
+ """
103
+ if pos not in (0, 1):
104
+ raise ValueError('pos must be 0 (top) or 1 (bottom)')
105
+ for idx, mob in enumerate(self.sq_mob):
106
+ self.pointers[pos][idx][1].set_color(
107
+ i_color if idx == i else self.bg_color)
108
+
109
+ # Highlight blocks for 1 index
110
+ def highlight_blocks_1(
111
+ self, i: int,
112
+ i_color=mn.GREEN,
113
+ ):
114
+ """
115
+ Highlight a single block in the array visualization.
116
+
117
+ Args:
118
+ i (int): Index of the block to highlight.
119
+ i_color: Color for the highlighted block.
120
+ """
121
+ for idx, mob in enumerate(self.sq_mob):
122
+ mob.set_fill(i_color if idx == i else self.bg_color)
123
+
124
+ def pointers_2(
125
+ self, i: int, j: int,
126
+ pos: int = 0,
127
+ i_color=mn.RED,
128
+ j_color=mn.BLUE,
129
+ ):
130
+ """
131
+ Highlight two pointers at one side (top | bottom) in the
132
+ array visualization.
133
+
134
+ Args:
135
+ i (int), j (int): Indices of the block whose pointer to highlight.
136
+ pos (int): 0 for top pointers, 1 for bottom. Defaults to 0.
137
+ i_color: Color for the highlighted pointer. Defaults to mn.GREEN.
138
+ """
139
+ if pos not in (0, 1):
140
+ raise ValueError('pos must be 0 (top) or 1 (bottom)')
141
+ for idx, mob in enumerate(self.sq_mob):
142
+ if idx == i == j:
143
+ self.pointers[pos][idx][0].set_color(i_color)
144
+ self.pointers[pos][idx][1].set_color(self.bg_color)
145
+ self.pointers[pos][idx][2].set_color(j_color)
146
+ elif idx == i:
147
+ self.pointers[pos][idx][0].set_color(self.bg_color)
148
+ self.pointers[pos][idx][1].set_color(i_color)
149
+ self.pointers[pos][idx][2].set_color(self.bg_color)
150
+ elif idx == j:
151
+ self.pointers[pos][idx][0].set_color(self.bg_color)
152
+ self.pointers[pos][idx][1].set_color(j_color)
153
+ self.pointers[pos][idx][2].set_color(self.bg_color)
154
+ else:
155
+ self.pointers[pos][idx][0].set_color(self.bg_color)
156
+ self.pointers[pos][idx][1].set_color(self.bg_color)
157
+ self.pointers[pos][idx][2].set_color(self.bg_color)
158
+
159
+ # Highlight blocks for 2 indices
160
+ def highlight_blocks_2(
161
+ self, i: int, j: int,
162
+ i_color=mn.RED,
163
+ j_color=mn.BLUE,
164
+ ij_color=mn.PURPLE,
165
+ ):
166
+ """
167
+ Highlight two blocks in the array visualization.
168
+ If indices coincide, use a special color.
169
+
170
+ Args:
171
+ i (int): First index to highlight.
172
+ j (int): Second index to highlight.
173
+ i_color: Color for the first index.
174
+ j_color: Color for the second index.
175
+ ij_color: Color if both indices are the same.
176
+ """
177
+ for idx, mob in enumerate(self.sq_mob):
178
+ if idx == i == j:
179
+ mob.set_fill(ij_color)
180
+ elif idx == i:
181
+ mob.set_fill(i_color)
182
+ elif idx == j:
183
+ mob.set_fill(j_color)
184
+ else:
185
+ mob.set_fill(self.bg_color)
186
+
187
+ def pointers_3(
188
+ self, i: int, j: int, k: int,
189
+ pos: int = 0,
190
+ i_color=mn.RED,
191
+ j_color=mn.GREEN,
192
+ k_color=mn.BLUE,
193
+ ):
194
+ """
195
+ Highlight two pointers at one side (top | bottom) in the
196
+ array visualization.
197
+
198
+ Args:
199
+ i (int), j (int), k (int): Indices of the block whose pointer
200
+ to highlight.
201
+ pos (int): 0 for top pointers, 1 for bottom. Defaults to 0.
202
+ i_color: Color for the highlighted pointer. Defaults to mn.GREEN.
203
+ """
204
+ for idx, mob in enumerate(self.sq_mob):
205
+ if idx == i == j == k:
206
+ self.pointers[pos][idx][0].set_color(i_color)
207
+ self.pointers[pos][idx][1].set_color(j_color)
208
+ self.pointers[pos][idx][2].set_color(k_color)
209
+ elif idx == i == j:
210
+ self.pointers[pos][idx][0].set_color(i_color)
211
+ self.pointers[pos][idx][1].set_color(self.bg_color)
212
+ self.pointers[pos][idx][2].set_color(j_color)
213
+ elif idx == i == k:
214
+ self.pointers[pos][idx][0].set_color(i_color)
215
+ self.pointers[pos][idx][1].set_color(self.bg_color)
216
+ self.pointers[pos][idx][2].set_color(k_color)
217
+ elif idx == k == j:
218
+ self.pointers[pos][idx][0].set_color(j_color)
219
+ self.pointers[pos][idx][1].set_color(self.bg_color)
220
+ self.pointers[pos][idx][2].set_color(k_color)
221
+ elif idx == i:
222
+ self.pointers[pos][idx][0].set_color(self.bg_color)
223
+ self.pointers[pos][idx][1].set_color(i_color)
224
+ self.pointers[pos][idx][2].set_color(self.bg_color)
225
+ elif idx == j:
226
+ self.pointers[pos][idx][0].set_color(self.bg_color)
227
+ self.pointers[pos][idx][1].set_color(j_color)
228
+ self.pointers[pos][idx][2].set_color(self.bg_color)
229
+ elif idx == k:
230
+ self.pointers[pos][idx][0].set_color(self.bg_color)
231
+ self.pointers[pos][idx][1].set_color(k_color)
232
+ self.pointers[pos][idx][2].set_color(self.bg_color)
233
+ else:
234
+ self.pointers[pos][idx][0].set_color(self.bg_color)
235
+ self.pointers[pos][idx][1].set_color(self.bg_color)
236
+ self.pointers[pos][idx][2].set_color(self.bg_color)
237
+
238
+ # Highlight blocks for 3 indices
239
+ def highlight_blocks_3(
240
+ self, i: int, j: int, k: int,
241
+ i_color=mn.RED,
242
+ j_color=mn.GREEN,
243
+ k_color=mn.BLUE,
244
+ ijk_color=mn.BLACK,
245
+ ij_color=mn.YELLOW_E,
246
+ ik_color=mn.PURPLE,
247
+ jk_color=mn.TEAL,
248
+ ):
249
+ """
250
+ Highlight three blocks in the array visualization.
251
+ Use special colors for index coincidences.
252
+
253
+ Args:
254
+ i (int): First index to highlight.
255
+ j (int): Second index to highlight.
256
+ k (int): Third index to highlight.
257
+ i_color: Color for the first index.
258
+ j_color: Color for the second index.
259
+ k_color: Color for the third index.
260
+ ijk_color: Color if all three indices are the same.
261
+ ij_color: Color if i and j are the same.
262
+ ik_color: Color if i and k are the same.
263
+ jk_color: Color if j and k are the same.
264
+ """
265
+ for idx, mob in enumerate(self.sq_mob):
266
+ if idx == i == j == k:
267
+ mob.set_fill(ijk_color)
268
+ elif idx == i == j:
269
+ mob.set_fill(ij_color)
270
+ elif idx == i == k:
271
+ mob.set_fill(ik_color)
272
+ elif idx == k == j:
273
+ mob.set_fill(jk_color)
274
+ elif idx == i:
275
+ mob.set_fill(i_color)
276
+ elif idx == j:
277
+ mob.set_fill(j_color)
278
+ elif idx == k:
279
+ mob.set_fill(k_color)
280
+ else:
281
+ mob.set_fill(self.bg_color)
282
+
283
+ # Animation of changing values in the array
284
+ def update_number_mobject(
285
+ self, scene, i: int,
286
+ add_arr: list, j: int):
287
+ """
288
+ Animate the change of a number in the array visualization.
289
+ The number at index i in num_mob is replaced with a new value
290
+ from add_arr[j], and the new text is positioned at the center
291
+ of the corresponding square.
292
+
293
+ Args:
294
+ i (int): Index in the array to update.
295
+ add_arr (list): Source array for the new value.
296
+ j (int): Index in add_arr to get the new value from.
297
+ """
298
+ # self.num_mob - group of text objects in the array
299
+ # .animate.become() - animation of transforming the receiver object
300
+ # into the argument
301
+ # mn.Text(str(arr[i])) - construction of a new text object
302
+ # self.arr_mob - group of graphical square objects
303
+ # .move_to(self.arr_mob[j]) - positioning the new text object
304
+ # in the same location, that self.arr_mob[k] has
305
+ # location in Manim is the center of mass
306
+
307
+ # Animate replacing the text at index i with new value from add_arr[j]
308
+ scene.play(
309
+ self.num_mob[i].animate.become(
310
+ mn.Text(
311
+ str(add_arr[j]),
312
+ ).move_to(self.sq_mob[i])),
313
+ # animation duration
314
+ run_time=0.2
315
+ )
316
+
317
+
318
+ class TopText(mn.VGroup):
319
+ def __init__(
320
+ self,
321
+ mob_center: mn.Mobject,
322
+ *vars: tuple,
323
+ font_size=40,
324
+ buff=0.7,
325
+ vector=mn.UP * 1.2,
326
+ ):
327
+ super().__init__()
328
+ self.mob_center = mob_center
329
+ self.vars = vars
330
+ self.font_size = font_size
331
+ self.buff = buff
332
+ self.vector = vector
333
+ self._refresh()
334
+
335
+ def _refresh(self):
336
+ self.submobjects = []
337
+ parts = [
338
+ mn.Text(f"{name} = {value()}",
339
+ font_size=self.font_size, color=color)
340
+ for name, value, color in self.vars
341
+ ]
342
+ top_text = mn.VGroup(*parts).arrange(mn.RIGHT, buff=self.buff)
343
+ top_text.move_to(self.mob_center.get_center() + self.vector)
344
+ self.add(*top_text)
345
+
346
+ def first_appear(self, scene, time=0.5):
347
+ scene.play(mn.FadeIn(self), run_time=time)
348
+
349
+ def update_text(self, scene, time=0.1):
350
+ # Create a new object with the same parameters
351
+ # (vars may be updated)
352
+ new_group = TopText(
353
+ self.mob_center,
354
+ *self.vars,
355
+ font_size=self.font_size,
356
+ buff=self.buff,
357
+ vector=self.vector,
358
+ )
359
+ scene.play(mn.Transform(self, new_group), run_time=time)
360
+
361
+
362
+ class CodeBlock(mn.VGroup):
363
+ def __init__(
364
+ self,
365
+ code_lines: list,
366
+ position: mn.Mobject,
367
+ font_size=25,
368
+ font="MesloLGS NF"
369
+ ):
370
+ """
371
+ Creates a code block visualization on the screen.
372
+
373
+ Args:
374
+ code_lines (list): List of code lines to display.
375
+ position (mn.Mobject): Position to place the code block.
376
+ font_size (int, optional): Font size for the code text.
377
+ font (str, optional): Font for the code text.
378
+ """
379
+ super().__init__()
380
+ # Construction
381
+ code_mobs = [
382
+ mn.Text(line, font=font, font_size=font_size)
383
+ for line in code_lines
384
+ ]
385
+ code_vgroup = mn.VGroup(
386
+ *code_mobs).arrange(mn.DOWN, aligned_edge=mn.LEFT)
387
+ code_vgroup.move_to(position)
388
+ self.code_vgroup = code_vgroup
389
+ # Construstion: add to scene
390
+ self.add(self.code_vgroup)
391
+
392
+ def first_appear(self, scene, time=0.5):
393
+ scene.play(mn.FadeIn(self), run_time=time)
394
+
395
+ def highlight_line(self, scene, i: int):
396
+ """
397
+ Highlights a single line of code in the code block by fading it
398
+ to yellow, instead of white.
399
+
400
+ Args:
401
+ scene (mn.Scene): The scene to play the animation in.
402
+ i (int): Index of the line to highlight.
403
+ """
404
+ scene.play(*[
405
+ mn.FadeToColor(
406
+ mob,
407
+ mn.YELLOW if k == i else mn.WHITE,
408
+ run_time=0.2
409
+ )
410
+ for k, mob in enumerate(self.code_vgroup)
411
+ ])