def construct(self): f0 = TexMobject("F_0 = 0") f1 = TexMobject("F_1 = 1") fn = TexMobject("F_n = F_{n-1} + F_{n-2}") fg = VGroup(f0, f1, fn) fg.arrange(RIGHT, buff=LARGE_BUFF).shift(UP) self.add(fg) fib_small_code = CodeBlock( 'Java', r""" public static int fib(int n) { if (n == 0) { return 0; } if (n == 1) { return 1; } return fib(n - 1) + fib(n - 2); } """, ) fib_code = CodeBlock( 'Java', r""" public static int fib(int n) { if (n == 0) { return 0; } if (n == 1) { return 1; } int fn1 = fib(n - 1); int fn2 = fib(n - 2); return fn1 + fn2; } """, ) fib_small_code.to_edge(RIGHT) f0.generate_target() f1.generate_target() fn.generate_target() fsc = fib_small_code.get_code() eq_code_buff = LARGE_BUFF * 1.5 f0.target.next_to(fsc.get_lines((2, 5)), LEFT, buff=eq_code_buff) f1.target.next_to(fsc.get_lines((5, 8)), LEFT, buff=eq_code_buff) fn.target.next_to(fsc.get_lines(8), LEFT, buff=eq_code_buff) self.play(*[MoveToTarget(o) for o in [f0, f1, fn]], ) self.play(FadeInFrom(fib_small_code, RIGHT), ) self.wait(duration=3) fib_small_code.generate_target() fib_small_code.target.move_to(fib_code, aligned_edge=UL) self.play(FadeOut(VGroup(f0, f1, fn)), MoveToTarget(fib_small_code)) adj_desc = TextMobject( "Let's make this a little easier to trace through...") adj_desc.to_edge(DOWN) self.play(FadeIn(adj_desc)) self.wait() fc = fib_code.get_code() self.play( fsc[-2].next_to, fc[-2], { 'direction': ORIGIN, 'index_of_submobject_to_align': 0, }, fsc[-1].move_to, fc[-1], {'aligned_edge': LEFT}, ) # 012 345 6 78901 2 345 # int fn1 = fib(n - 1); self.play(FadeIn(fc[-4][:7]), ) # 012345 67890 1 23 4 56789 0 123 # return fib(n - 1) + fib(n - 2); self.play( fsc[-2][6:14].move_to, fc[-4][7:15], ) # 012345 678 9 0123 # return fn1 + fn2; self.play( FadeIn(fc[-4][-1]), FadeIn(fc[-2][6:9]), fsc[-2][14].move_to, fc[-2][9], ) self.play(FadeIn(fc[-3][:7]), ) self.play( fsc[-2][15:23].move_to, fc[-3][7:15], ) self.play( FadeIn(fc[-3][-1]), FadeIn(fc[-2][10:13]), fsc[-2][-1].move_to, fc[-2][-1], ) self.clear() self.add(fib_code, adj_desc) self.wait() t1 = TextMobject("Okay, let's run it") t1.move_to(adj_desc) self.play(ReplacementTransform(adj_desc, t1)) self.wait(duration=2) self.play(FadeOut(t1))
def construct(self): # Look at power1() and highlight parts of it. Anatomy of a recursive function. # Always has two parts: base case and recursive step. code_scale = 0.75 power_code = CodeBlock( 'Java', r""" public static int power(int x, int n) { if (n == 0) { return 1; } int t = power(x, n - 1); return x * t; } """, code_scale=code_scale, ) power_code.to_edge(UP) self.add(power_code) self.wait() title = TextMobject('Anatomy of a Recursive Function').to_edge(UP) power_code.generate_target() power_code.target.next_to(title, DOWN, buff=MED_LARGE_BUFF) self.play( FadeInFrom(title, UP), MoveToTarget(power_code), ) self.wait() pc_groups = [ VGroup(power_code.get_code().get_lines(p)) for p in [(1, 2), (2, 5), (5, 7), (7, 8)] ] pc_t = [g.generate_target() for g in pc_groups] # Base Case # - All of these have a base case that ends the recursion and returns. # - Computed directly from the inputs. # - Base cases are often some variant of emptiness. Power1() is a good example, with n == 0. # What if you returned x for n == 1? base_shifts = [UP * 6, DOWN, DOWN * 6, DOWN * 6] power_code.save_state() for t, s in zip(pc_t, base_shifts): t.shift(s) t1 = TextMobject('Base Case').next_to(title, DOWN, buff=0.75) self.play( *[MoveToTarget(o) for o in pc_groups], FadeIn(t1), ) self.wait() points_dwell_time = 2.5 t2 = TextMobject( "- stops the recursion and returns\\\\", "- value is computed directly from the inputs, or constant\\\\", "- often it's some form of ``emptiness'' or ``singleness''\\\\", alignment="").next_to(title, DOWN, buff=LARGE_BUFF * 3.5) base_highlights = [ SurroundingRectangle(pc_groups[1][0][1][0:6]), SurroundingRectangle(pc_groups[1][0][1][6:7]), SurroundingRectangle(pc_groups[1][0][0][3:7]), ] for t, h, ph in zip(t2, base_highlights, [None] + base_highlights): opt_anim = [FadeOut(ph)] if ph else [] self.play( *opt_anim, FadeInFromDown(t), FadeIn(h), ) self.wait(duration=points_dwell_time) self.play(FadeOut(base_highlights[-1])) self.wait() self.play( FadeOut(t1), FadeOut(t2), power_code.restore, ) self.wait() # Recursive Step # The recursive step is some variant of decompose/reduce, call, recombine/compute. # - Recursive case: compute the result with the help of one or more recursive calls to the same function. # In this case, the data is reduced in some way in either size, complexity, or both. # - In this case, reducing n by 1 each time. pc_t = [g.generate_target() for g in pc_groups] recurse_shifts = [UP * 6, UP * 6, UP * 0.45, DOWN * 6] power_code.save_state() for t, s in zip(pc_t, recurse_shifts): if s is not None: t.shift(s) t1 = TextMobject('Recursive Step').next_to(title, DOWN, buff=0.75) self.play( *[MoveToTarget(o) for o in pc_groups], FadeIn(t1), ) self.wait() t2 = TextMobject( "- reduce: make the problem smaller, or break it into parts\\\\", "- call: one or more recursive calls\\\\", "- recombine: compute the result from the pieces\\\\", alignment="").next_to(title, DOWN, buff=LARGE_BUFF * 3.5) # 012345678901234567 # intt=power(x,n-1); # returnx*t; recurse_highlights = [ SurroundingRectangle(pc_groups[2][0][0][13:16]), SurroundingRectangle(pc_groups[2][0][0][5:17]), SurroundingRectangle(pc_groups[2][0][1][6:9]), ] for t, h, ph in zip(t2, recurse_highlights, [None, *recurse_highlights]): opt_anim = [FadeOut(ph)] if ph else [] self.play( *opt_anim, FadeInFromDown(t), FadeIn(h), ) self.wait(duration=points_dwell_time) self.play(FadeOut(recurse_highlights[-1])) self.wait() self.play( FadeOut(t1), FadeOut(t2), power_code.restore, ) self.wait(0.5) power_code.generate_target() power_code.target.to_edge(RIGHT) bcb = BraceLabel(power_code.target.get_code().get_lines((2, 5)), '\\textit{Base Case}', brace_direction=LEFT, label_constructor=TextMobject, buff=LARGE_BUFF) rsb = BraceLabel(power_code.target.get_code().get_lines((5, 7)), '\\textit{Recursive Step}', brace_direction=LEFT, label_constructor=TextMobject, buff=LARGE_BUFF) self.play(MoveToTarget(power_code), ) self.play( ShowCreation(bcb), ShowCreation(rsb), ) self.wait(0.5) t1 = TextMobject('Base Case', ':', ' stops the recursion') t2 = TextMobject('Recursive Step', ':', ' reduce - call - recombine') t2.next_to(t1.get_part_by_tex(':'), DOWN, submobject_to_align=t2.get_part_by_tex(':'), buff=MED_LARGE_BUFF) g = VGroup(t1, t2).shift(DOWN * 1.5) self.play(FadeIn(g)) self.wait(duration=points_dwell_time) t3 = TextMobject( "In Part 2 we'll look at making this function more efficient!" ).scale(0.75) t3.to_edge(DOWN).set_color(BLUE) self.play(Write(t3)) # self.play(*[FadeOut(o) for o in self.mobjects]) self.wait(duration=3) end_scale_group = VGroup(*self.mobjects) end_fade_group = VGroup(t3, title) self.animate_yt_end_screen(end_scale_group, end_fade_group, show_elements=False)
def construct(self): # - Now, let's change this a bit and break it! # - Now modify it (a <= 0) and call it with foo(0). # - switch it out for the broken one, and highlight and note the change. code_scale = 0.75 ss_good_code = CodeBlock( 'Java', r""" public static double StrangeSqrt(double a) { if (a < 0) { double b = StrangeSqrt(a * -1); return b; } return Math.sqrt(a); } """, code_scale=code_scale, ) ss_good_code.to_edge(UP) self.add(ss_good_code) t1 = TextMobject( "Let's break this code in a very small way and\\\\see what happens..." ) t1.next_to(ss_good_code, DOWN, buff=LARGE_BUFF) self.play(FadeIn(t1)) self.wait() gl2 = ss_good_code.get_code().get_lines(2) ghr = SurroundingRectangle(gl2[3:6]) self.play(ShowCreation(ghr)) self.wait() ss_bad_code = CodeBlock( 'Java', r""" public static double StrangeSqrt(double a) { if (a <= 0) { double b = StrangeSqrt(a * -1); return b; } return Math.sqrt(a); } """, code_scale=code_scale, ) ss_bad_code.to_edge(UP) bl2 = ss_bad_code.get_code().get_lines(2) bhr = SurroundingRectangle(bl2[3:7]) self.play( ReplacementTransform(ss_good_code, ss_bad_code), ReplacementTransform(ghr, bhr), ) self.wait() t2 = TextMobject( "This small change will have a big effect on one case: 0\\\\" "Let's run it and see.").next_to(ss_bad_code, DOWN, buff=LARGE_BUFF) self.play( FadeOut(t1), FadeIn(t2), ) self.wait(duration=3) self.play( FadeOut(t2), FadeOut(bhr), ) self.wait() # - Start stepping through this and see the stack start to grow forever. main_code = CodeBlock( 'Java', r""" public static void main(String[] args) { double n = StrangeSqrt(0); } """, line_offset=7, code_scale=code_scale - 0.1, ) frame_width = 3.5 main_frame = StackFrame(main_code, 'main()', 9, ['n'], width=frame_width) main_code.highlight_lines(9) VGroup(main_code, main_frame).arrange(RIGHT, buff=LARGE_BUFF).to_edge(DOWN) self.play( FadeInFromDown(main_frame), FadeInFromDown(main_code), ) self.wait() ss_frame = StackFrame(ss_bad_code, 'StrangeSqrt(0)', 1, ['a', 'b'], width=frame_width) ss_frame.next_to(main_frame, UP, buff=SMALL_BUFF) xi = main_code.pre_call(ss_bad_code, 1) self.play( *main_code.get_control_transfer_counterclockwise(xi), FadeInFrom(ss_frame, UP), ) ss_bad_code.post_control_transfer(xi, self) self.wait() self.play(ss_bad_code.highlight_lines, 2, ss_frame.set_line, 2, ss_frame.update_slot, 'a', 0) self.wait() self.play(ss_bad_code.highlight_lines, 3, ss_frame.set_line, 3) self.wait() def call_ss_again(stack_group, previous_frame, extras=None): wait_each_step = len(stack_group) < 3 runtime = 1.0 if len(stack_group) < 2 else 0.5 if len( stack_group) < 6 else 0.20 extra_anims = [] if len(stack_group) > 2: extra_anims.append( ApplyMethod( stack_group.shift, DOWN * previous_frame.get_height() + DOWN * SMALL_BUFF)) if extras: extra_anims.extend(extras) f = StackFrame(ss_bad_code, 'StrangeSqrt(0)', 1, ['a', 'b'], width=frame_width) f.next_to(previous_frame, UP, buff=SMALL_BUFF) self.play(*extra_anims, ss_bad_code.highlight_lines, 1, f.set_line, 1, FadeIn(f), MaintainPositionRelativeTo(f, previous_frame), run_time=runtime) if wait_each_step: self.wait() self.play(ss_bad_code.highlight_lines, 2, f.set_line, 2, f.update_slot, 'a', 0, run_time=runtime) if wait_each_step: self.wait() self.play(ss_bad_code.highlight_lines, 3, f.set_line, 3, run_time=runtime) if wait_each_step: self.wait() stack_group.add(f) return f sg = VGroup(main_frame, ss_frame) curr_ss_frame = ss_frame for i in range(3): curr_ss_frame = call_ss_again(sg, curr_ss_frame) # - When will this program end? Never? t1 = TextMobject("Hrm... when is this going to end??").next_to( main_code, UP, buff=LARGE_BUFF) extra_text = [FadeIn(t1)] for i in range(4): curr_ss_frame = call_ss_again(sg, curr_ss_frame, extra_text) extra_text = None t2 = TextMobject("Never?").next_to(main_code, UP, buff=LARGE_BUFF) extra_text = [FadeOut(t1), FadeIn(t2)] for i in range(4): curr_ss_frame = call_ss_again(sg, curr_ss_frame, extra_text) extra_text = None sg.save_state() t3 = TextMobject("The stack is getting quite deep!").next_to( main_code, UP, buff=LARGE_BUFF) self.play(sg.scale, 0.20, {'about_edge': TOP}, FadeOut(t2), FadeIn(t3)) self.wait(duration=2) self.play(sg.restore) extra_text = [FadeOut(t3)] for i in range(4): curr_ss_frame = call_ss_again(sg, curr_ss_frame, extra_text) extra_text = None # - Well, the stack is finite, so eventually you get a SO. # Use the Java SO exception and show it hit and stop. so_frame = StackFrame(ss_bad_code, 'StrangeSqrt(0)', 1, ['a', 'b'], width=frame_width) so_frame.slots().set_color(ORANGE) so_frame.header_line().set_color(ORANGE) so_frame.background_rect().set_fill(color=[BLACK, RED]) so_frame.next_to(curr_ss_frame, UP, buff=SMALL_BUFF) so_exception = TextMobject( '\\texttt{Exception in thread "main"\\\\java.lang.StackOverflowError}', ).set_color(YELLOW).scale(0.75).next_to(main_code, UP, buff=LARGE_BUFF) self.play(sg.shift, DOWN * so_frame.get_height() + DOWN * SMALL_BUFF, ss_bad_code.get_current_highlight().set_color, ORANGE, FadeIn(so_frame), MaintainPositionRelativeTo(so_frame, curr_ss_frame), Write(so_exception)) sg.add(so_frame) self.wait(duration=2) ss_bad_code.generate_target() so_exception.generate_target() ss_bad_code.target.scale(0.75) g = VGroup(so_exception.target, ss_bad_code.target).arrange(RIGHT, buff=LARGE_BUFF) g.to_edge(UP) sg.generate_target() sg.target.scale(0.15).next_to(g, DOWN).to_edge(RIGHT) t1 = TextMobject( "Space for the call stack is limited,\\\\so we can't make recursive calls forever!" ) self.play( *[MoveToTarget(t) for t in [so_exception, ss_bad_code, sg]], FadeOutAndShiftDown(main_code), FadeInFromDown(t1), ) self.wait(duration=2) t2 = TextMobject( "This program made {\\raise.17ex\\hbox{$\\scriptstyle\\sim$}}15,000 calls before crashing." ) t4 = TextMobject( "\\textit{Java SE 13.0.1 on macOS Catalina 10.15.4}").scale(0.5) t2.next_to(t1, DOWN, buff=MED_LARGE_BUFF) t4.to_edge(DL) self.play( FadeIn(t2), FadeInFromDown(t4), ) self.wait(duration=4) t5 = TextMobject('\\textit{Most common mistake with recursion.}') t6 = TextMobject("You'll see this a lot. Everyone does!") t6.next_to(t5, DOWN, buff=LARGE_BUFF) self.play( *[FadeOut(t) for t in [t1, t2, t4]], *[FadeIn(t) for t in [t5, t6]], ) self.wait(duration=3) # Transition t1 = TextMobject( "Alright, let's look at a more interesting function...") self.play( *[FadeOut(o) for o in self.mobjects], FadeIn(t1), ) self.wait() self.play(FadeOut(t1)) self.wait()
def construct(self): # Even case ef0 = TexMobject('x^n=', 'x', '\\times', 'x \\times ... \\times x') eb0 = BraceLabel(ef0[1:], '$n$ \\textit{times}', brace_direction=UP, label_constructor=TextMobject) ef0g = VGroup(ef0, eb0) ef1 = TexMobject('x^n=', 'x \\times ... \\times x', '\\times', 'x \\times ... \\times x') ef1.next_to(ef0, ORIGIN, index_of_submobject_to_align=0) eb1n = BraceLabel(ef1[1:], '$n$ \\textit{times}', brace_direction=UP, label_constructor=TextMobject) eb1l = BraceLabel(ef1[1:2], '$\\frac{n}{2}$ \\textit{times}', brace_direction=UP, label_constructor=TextMobject) eb1r = BraceLabel(ef1[3:4], '$\\frac{n}{2}$ \\textit{times}', brace_direction=UP, label_constructor=TextMobject) ef2 = TexMobject('x^n=', 'x \\times ... \\times x', '\\times', 'x^{\\frac{n}{2}}') ef2.next_to(ef0, ORIGIN, index_of_submobject_to_align=0) eb2r = BraceLabel(ef2[3:4], '$\\frac{n}{2}$ \\textit{times}', brace_direction=UP, label_constructor=TextMobject) ef3 = TexMobject('x^n=', 'x^{\\frac{n}{2}}', '\\times', 'x^{\\frac{n}{2}}') ef3.next_to(ef0, ORIGIN, index_of_submobject_to_align=0) eb3r = BraceLabel(ef3[3:4], '$\\frac{n}{2}$ \\textit{times}', brace_direction=UP, label_constructor=TextMobject) if_even = TextMobject('\\textit{if $n$ is even:}').next_to(ef0, LEFT) self.play(ShowCreation(ef0g)) self.wait() self.play(ReplacementTransform(ef0, ef1), ReplacementTransform(eb0, eb1n)) self.wait() self.play(ReplacementTransform(eb1n, eb1l), ShowCreation(eb1r), FadeIn(if_even)) self.wait(duration=2) self.play(ReplacementTransform(ef1, ef2), ReplacementTransform(eb1r, eb2r)) self.wait() self.play(ReplacementTransform(ef2, ef3), FadeOut(eb1l), ReplacementTransform(eb2r, eb3r)) self.wait() even_group = VGroup(if_even, ef3) self.play(FadeOutAndShift(eb3r, UL), even_group.to_edge, UL) self.wait() # Prime1 eq p1f = TexMobject('x^n=', 'x', '\\times', 'x^{n-1}') p1t = TextMobject('From our first version, we know') p1t.next_to(p1f, UP) self.play(FadeIn(p1t), ShowCreation(p1f)) self.wait() # Odd case ot1 = TextMobject('So if $n$ is odd, then $n-1$ is even... hmm...') ot1.next_to(p1f, UP) self.play(ReplacementTransform(p1t, ot1)) self.wait() of1 = TexMobject('x^n=', 'x', '\\times', 'x^{\\frac{n-1}{2}} \\times x^{\\frac{n-1}{2}}') of1.next_to(p1f, ORIGIN, index_of_submobject_to_align=0) self.play(ReplacementTransform(VGroup(ef3[1:]).copy(), of1[3]), FadeOut(p1f[3])) self.remove(*p1f[:3]) self.add(of1) self.wait() if_odd = TextMobject('\\textit{if $n$ is odd:}').next_to(of1[0], LEFT) self.play(FadeOut(ot1), FadeIn(if_odd)) self.wait() # Both originals = [if_even, ef3, if_odd, of1] for o in originals: o.generate_target() ef3.target.next_to(of1, UP, aligned_edge=LEFT) if_even.target.next_to(ef3.target[0], LEFT) VGroup(*[o.target for o in originals]).center().to_edge(TOP) self.play(*[MoveToTarget(o) for o in originals]) self.wait() eqg = VGroup(*originals) # Simplify with int division t1 = TextMobject( 'Fun Fact: if $n$ is odd, and we use \\texttt{int} as the datatype, then' ).next_to(eqg, DOWN, buff=LARGE_BUFF) div_code = CodeTextString('Java', 'n / 2 == (n - 1) / 2').next_to(t1, DOWN) self.play(FadeInFromDown(t1), FadeInFromDown(div_code)) self.wait() of2 = TexMobject('x^n=', 'x', '\\times', 'x^{\\frac{n}{2}} \\times x^{\\frac{n}{2}}') of2.next_to(of1, ORIGIN, index_of_submobject_to_align=0) self.play(ReplacementTransform(div_code, of2[3]), FadeOut(of1[3]), FadeOut(t1)) self.remove(*of1[:3]) self.add(of2) self.wait() originals = [if_even, ef3, if_odd, of2] for o in originals: o.generate_target() for p in [ef3.target[1:], of2.target[3]]: p.set_color(ORANGE) eqtg = VGroup(*[o.target for o in originals]).center().to_edge(TOP) t1 = TextMobject( 'Using \\texttt{int} for $n$ makes the equations very similar,\\\\' 'and the coding very simple!') t1.next_to(eqtg, DOWN, buff=MED_LARGE_BUFF) self.play(*[MoveToTarget(o) for o in originals], FadeIn(t1)) self.wait() eqg = VGroup(*originals) # Transform to code code_scale = 0.7 power2_code = CodeBlock( 'Java', r""" public static int power2(int x, int n) { if (n == 0) { return 1; } int t = power2(x, n / 2); if (n % 2 == 0) { return t * t; } return t * t * x; } """, code_scale=code_scale, ) power2_code.to_edge(RIGHT) ef3.generate_target() of2.generate_target() ef3.target.next_to(power2_code.get_code().get_lines(7), LEFT, buff=LARGE_BUFF) of2.target.next_to(power2_code.get_code().get_lines(9), LEFT, buff=LARGE_BUFF) ef3.target.next_to(of2.target, UP, aligned_edge=RIGHT, coor_mask=X_AXIS) et = TextMobject('\\textit{even:}') ot = TextMobject('\\textit{odd:}') ot.next_to(of2.target[0], LEFT) et.next_to(ef3.target[0], LEFT) self.play(FadeOut(t1)) self.play( FadeOut(if_even), FadeOut(if_odd), FadeInFrom(power2_code, RIGHT), MoveToTarget(ef3), MoveToTarget(of2), ReplacementTransform(if_even, et), ReplacementTransform(if_odd, ot), ) self.wait() recursive_call_hr = SurroundingRectangle( power2_code.get_code().get_lines(5)[5:-1]) xn2_hr = SurroundingRectangle(ef3[-1]) ec_hr = SurroundingRectangle( power2_code.get_code().get_lines(7)[-4:-1]) ef_hr = SurroundingRectangle(ef3[1:]) oc_hr = SurroundingRectangle( power2_code.get_code().get_lines(9)[-6:-1]) of_hr = SurroundingRectangle(of2[1:]) for f, c in [(xn2_hr, recursive_call_hr), (ef_hr, ec_hr), (of_hr, oc_hr)]: self.play(ShowCreation(f), ShowCreation(c)) self.wait() self.play(Uncreate(f), Uncreate(c)) self.wait()
def construct(self): t1 = TextMobject("Let's go back to our original equation").shift(UP) self.play(FadeInFromDown(t1)) self.wait() # Even case ef1 = TexMobject('x^n=', 'x \\times ... \\times x', '\\times', 'x \\times ... \\times x') eb1n = BraceLabel(ef1[1:], '$n$ \\textit{times}', brace_direction=UP, label_constructor=TextMobject) eb1l = BraceLabel(ef1[1:2], '$\\frac{n}{2}$ \\textit{times}', brace_direction=UP, label_constructor=TextMobject) eb1r = BraceLabel(ef1[3:4], '$\\frac{n}{2}$ \\textit{times}', brace_direction=UP, label_constructor=TextMobject) ef3 = TexMobject('x^n=', 'x^{\\frac{n}{2}}', '\\times', 'x', '^{\\frac{n}{2}}') ef3.next_to(ef1, ORIGIN, index_of_submobject_to_align=0) eb3l = BraceLabel(ef3[1:2], '$\\frac{n}{2}$ \\textit{times}', brace_direction=UP, label_constructor=TextMobject) if_even = TextMobject('\\textit{if $n$ is even:}').next_to( ef1, LEFT).set_color(YELLOW) self.play(ShowCreation(ef1), ShowCreation(eb1n), t1.shift, UP) self.wait(duration=2) self.play(ReplacementTransform(eb1n, eb1l), ShowCreation(eb1r), FadeIn(if_even), FadeOut(t1)) self.wait(duration=2) self.play(ReplacementTransform(ef1[1], ef3[1]), ReplacementTransform(eb1l, eb3l)) self.wait() tmp_ef3_right = VGroup(ef3[2].copy(), ef3[3].copy(), ef3[4].copy()) tmp_ef3_right.next_to(ef1[2], ORIGIN, submobject_to_align=tmp_ef3_right[0]) eb3r = BraceLabel(tmp_ef3_right[1:], '$\\frac{n}{2}$ \\textit{times}', brace_direction=UP, label_constructor=TextMobject) self.play(ReplacementTransform(ef1[3:], tmp_ef3_right[1:]), ReplacementTransform(eb1r, eb3r)) self.wait() self.play( ReplacementTransform(ef1[2], ef3[2]), ReplacementTransform(tmp_ef3_right[1:], ef3[3:]), FadeOutAndShift(eb3r, LEFT), FadeOut(eb3l), ) self.remove(ef1[0]) self.add(ef3) self.wait() even_group = VGroup(if_even, ef3) self.play(even_group.to_edge, UL) self.wait() # Odd case ot1 = TextMobject( "Now, let's figure out what to do if $n$ is odd").shift( UP * 2).set_color(BLUE) self.play(FadeInFromDown(ot1)) of1 = TexMobject('x^n=', 'x \\times ... \\times x', '\\times', 'x \\times ... \\times x', '\\times x') ob1n = BraceLabel(of1[1:], '$n$ \\textit{times}', brace_direction=UP, label_constructor=TextMobject) ob1nm1 = BraceLabel(of1[1:-1], '$n-1$ \\textit{times}', brace_direction=UP, label_constructor=TextMobject) ob1l = BraceLabel(of1[1:2], '$\\frac{n-1}{2}$ \\textit{times}', brace_direction=UP, label_constructor=TextMobject) ob1r = BraceLabel(of1[3:4], '$\\frac{n-1}{2}$ \\textit{times}', brace_direction=UP, label_constructor=TextMobject) of3 = TexMobject('x^n=', 'x', '^{\\frac{n-1}{2}}', '\\times', 'x', '^{\\frac{n-1}{2}}', '\\times x') of3.next_to(of1, ORIGIN, index_of_submobject_to_align=0) ob3l = BraceLabel(of3[1:3], '$\\frac{n-1}{2}$ \\textit{times}', brace_direction=UP, label_constructor=TextMobject) if_odd = TextMobject('\\textit{if $n$ is odd:}').next_to( of1, LEFT).set_color(YELLOW) self.play(ShowCreation(of1), ShowCreation(ob1n)) self.wait(duration=2) self.play(ReplacementTransform(ob1n, ob1nm1), FadeIn(if_odd), FadeOut(ot1)) self.wait(duration=2) self.play(ReplacementTransform(ob1nm1, ob1l), ShowCreation(ob1r)) self.wait(duration=2) self.play(ReplacementTransform(of1[1], of3[1:3]), ReplacementTransform(ob1l, ob3l)) self.wait() tmp_of3_right = VGroup(of3[3].copy(), of3[4].copy(), of3[5].copy()) tmp_of3_right.next_to(of1[2], ORIGIN, submobject_to_align=tmp_of3_right[0]) ob3r = BraceLabel(tmp_of3_right[1:], '$\\frac{n-1}{2}$ \\textit{times}', brace_direction=UP, label_constructor=TextMobject) self.play(ReplacementTransform(of1[3], tmp_of3_right[1:]), ReplacementTransform(ob1r, ob3r)) self.wait() self.play( ReplacementTransform(of1[2], of3[3]), ReplacementTransform(tmp_of3_right[1:], of3[4:6]), ReplacementTransform(of1[4], of3[-1]), FadeOutAndShift(ob3r, LEFT), FadeOut(ob3l), ) self.remove(of1[0]) self.add(of3) self.wait() # Both originals = [if_even, ef3, if_odd, of3] for o in originals: o.generate_target() ef3.target.next_to(of3, UP, aligned_edge=LEFT) if_even.target.next_to(ef3.target[0], LEFT) VGroup(*[o.target for o in originals]).center().to_edge(TOP) self.play(*[MoveToTarget(o) for o in originals]) self.wait() eqg = VGroup(*originals) # Simplify with int division t1 = TextMobject( 'Fun Fact: if $n$ is odd, and we use \\texttt{int} as the datatype, then', tex_to_color_map={ 'int': RED }).next_to(eqg, DOWN, buff=LARGE_BUFF) div_code = CodeTextString('Java', '(n-1)/2 == n/2').next_to(t1, DOWN) self.play(FadeInFromDown(t1), FadeInFromDown(div_code)) self.wait(duration=3) t2 = TextMobject('Integer division \\textit{truncates}: ' '\\texttt{5/2 == 2}' ', \\textit{not} ' '\\texttt{2.5}') t2.next_to(div_code, DOWN, buff=LARGE_BUFF) self.play(FadeInFromDown(t2)) self.wait(duration=3) # Indicate movement before each exp transformation from code of4 = TexMobject('x^n=', 'x', '^{\\frac{n}{2}}', '\\times', 'x', '^{\\frac{n}{2}}', '\\times x') of4.next_to(of3, ORIGIN, index_of_submobject_to_align=0) def highlight_and_switch_exp(target_exp, old_exp): hrc = SurroundingRectangle(div_code[0][:7]) hrf = SurroundingRectangle(old_exp) self.play(ShowCreation(hrc), ShowCreation(hrf)) self.wait() self.play(Uncreate(hrc), Uncreate(hrf)) self.play(Indicate(div_code[0][-3:])) self.play( ReplacementTransform(div_code[0][-3:].copy(), target_exp), FadeOut(old_exp)) highlight_and_switch_exp(of4[2], of3[2]) tmp_of4_right = VGroup(of4[4].copy(), of4[5].copy()) tmp_of4_right.next_to(of3[4], ORIGIN, submobject_to_align=tmp_of4_right[0]) highlight_and_switch_exp(tmp_of4_right[1], of3[5]) self.wait() self.play( ReplacementTransform(of3[3:5], of4[3:5]), ReplacementTransform(tmp_of4_right[1], of4[5]), ReplacementTransform(of3[-1], of4[-1]), ) self.remove(*of3) self.add(of4) self.wait() self.play(*[FadeOut(o) for o in [t1, t2, div_code]]) originals = [if_even, ef3, if_odd, of4] for o in originals: o.generate_target() for p in [ef3.target[1:], of4.target[1:6]]: p.set_color(ORANGE) eqtg = VGroup(*[o.target for o in originals]).center().to_edge(TOP) t1 = TextMobject( 'Using \\texttt{int} for $n$ makes the equations very similar,\\\\' 'and the coding very simple!', tex_to_color_map={'int': RED}) t1.next_to(eqtg, DOWN, buff=MED_LARGE_BUFF) self.play(*[MoveToTarget(o) for o in originals], FadeIn(t1)) self.wait(duration=3) eqg = VGroup(*originals) # Transform to code code_scale = 0.7 power2_code = CodeBlock( 'Java', r""" public static int power2(int x, int n) { if (n == 0) { return 1; } int t = power2(x, n / 2); if (n % 2 == 0) { return t * t; } return t * t * x; } """, code_scale=code_scale, ) power2_code.to_edge(RIGHT) ef3.generate_target() of4.generate_target() ef3.target.next_to(power2_code.get_code().get_lines(7), LEFT, buff=LARGE_BUFF) of4.target.next_to(power2_code.get_code().get_lines(9), LEFT, buff=LARGE_BUFF) ef3.target.next_to(of4.target, UP, index_of_submobject_to_align=0, coor_mask=X_AXIS) et = TextMobject('\\textit{even:}') ot = TextMobject('\\textit{odd:}') ot.next_to(of4.target[0], LEFT) et.next_to(ef3.target[0], LEFT) self.play(FadeOut(t1)) self.play( FadeOut(if_even), FadeOut(if_odd), FadeInFrom(power2_code, RIGHT), MoveToTarget(ef3), MoveToTarget(of4), ReplacementTransform(if_even, et), ReplacementTransform(if_odd, ot), ) self.wait() highlights = [ [ SurroundingRectangle( power2_code.get_code().get_lines(1)[-6:-2]), SurroundingRectangle( power2_code.get_code().get_lines(5)[-5:-2]), SurroundingRectangle(ef3[-1]), ], [ SurroundingRectangle( power2_code.get_code().get_lines(5)[5:-1]), SurroundingRectangle(ef3[-2:]), ], [ SurroundingRectangle( power2_code.get_code().get_lines(7)[-4:-1]), SurroundingRectangle(ef3[1:]), ], [ SurroundingRectangle( power2_code.get_code().get_lines(9)[-6:-1]), SurroundingRectangle(of4[1:]), ], ] for g in highlights: self.play(*[ShowCreation(h) for h in g]) self.wait(duration=1.5) self.play(*[Uncreate(h) for h in g]) self.wait() self.play(*[FadeOut(o) for o in self.mobjects if o != power2_code]) power2_code.generate_target() power2_code.target.center() power2_code.target.to_edge(UP) self.play(MoveToTarget(power2_code))
def construct(self): code_scale = 0.7 power2_code = CodeBlock( 'Java', r""" public static int power2(int x, int n) { if (n == 0) { return 1; } int t = power2(x, n / 2); if (n % 2 == 0) { return t * t; } return t * t * x; } """, code_scale=code_scale, ) power2_code.to_edge(UP) self.add(power2_code) t1 = TextMobject( "This version should call itself fewer than $n$ times") t1.next_to(power2_code, DOWN, buff=LARGE_BUFF) self.play(FadeInFromDown(t1)) self.wait(duration=2.5) t2 = TextMobject( 'How many times do you think \\texttt{power2(2,30)} will call itself?', tex_to_color_map={'30': YELLOW}) t2.next_to(t1, ORIGIN) self.play(FadeOutAndShift(t1, UP), FadeInFromDown(t2)) self.wait(duration=2) t3 = TextMobject("We know it'll be less than 30! Maybe 15?") t3.next_to(t2, DOWN, buff=MED_LARGE_BUFF) self.play(FadeInFromDown(t3)) self.wait(duration=2) t4 = TextMobject("How about a lot less? Let's see...") t4.next_to(t3, ORIGIN) self.play(ReplacementTransform(t3, t4)) self.wait(duration=2) self.play(*[FadeOut(o) for o in self.mobjects if o != power2_code]) # Start stepping through this and see it go. main_code = CodeBlock( 'Java', r""" public static void main(String[] args) { int y = power2(2, 30); } """, line_offset=10, code_scale=code_scale - 0.1, ) frame_width = 3.5 main_frame = StackFrame(main_code, 'main()', 12, ['y'], width=frame_width, slot_char_width=8) main_code.highlight_lines(12) VGroup(main_code, main_frame).arrange(RIGHT, buff=LARGE_BUFF).to_edge(DOWN) self.play( FadeInFromDown(main_frame), FadeInFromDown(main_code), ) self.wait() def call_power2(x, n, call_stack, cc_num): new_cc_num = TextMobject(str(len(call_stack) - 1)).move_to(cc_num).set_color(YELLOW) update_cc_anims = [ReplacementTransform(cc_num, new_cc_num)] call_ret_delay = 0.5 stack_frame = StackFrame(power2_code, 'power(%d, %d)' % (x, n), 1, ['x', 'n', 't'], width=frame_width) call_stack.animate_call(stack_frame, self, extra_anims=update_cc_anims) self.play( *stack_frame.get_update_line_anims(2), stack_frame.update_slot, 'x', x, stack_frame.update_slot, 'n', n, ) if n == 0: self.play(*stack_frame.get_update_line_anims(3)) self.wait(duration=call_ret_delay) call_stack.animate_return(self) return 1 else: self.play(*stack_frame.get_update_line_anims(5)) self.wait(duration=call_ret_delay) t = call_power2(x, n // 2, call_stack, new_cc_num) self.play( *stack_frame.get_update_line_anims(6), stack_frame.update_slot, 't', t, ) if n % 2 == 0: self.play(*stack_frame.get_update_line_anims(7)) self.wait(duration=call_ret_delay) call_stack.animate_return(self) return t * t self.play(*stack_frame.get_update_line_anims(9)) self.wait(duration=call_ret_delay) call_stack.animate_return(self) return t * t * x cc_label = TextMobject('Recursive Calls').scale(0.75) cc_label.to_edge(UL).shift(DOWN) call_counter = TextMobject('0').set_color(YELLOW).next_to( cc_label, DOWN) self.play(FadeIn(call_counter), FadeIn(cc_label)) result = call_power2(2, 30, CallStack(main_frame), call_counter) self.play( *main_frame.get_update_line_anims(13), main_frame.update_slot, 'y', result, ) self.wait() t1 = TextMobject( 'We got our answer in just 5 recursive calls!').set_color(YELLOW) t1.next_to(power2_code, DOWN, buff=MED_LARGE_BUFF) self.play(ShowCreation(t1)) self.wait(duration=2) t1.generate_target() t1.target.center().to_edge(TOP) t2 = TextMobject('But why?').next_to(t1.target, DOWN, buff=LARGE_BUFF) self.play( MoveToTarget(t1), FadeInFromDown(t2), *[FadeOut(o) for o in self.mobjects if o != t1], )