/* Python 3 -- Loops and recursion while, for range(), for-each, recursion No em dashes anywhere -- use plain hyphens or rewrite the sentence. Line index comments mark each scenario's 0-based line numbers (blank lines are not counted). */ STEPPER_REGISTRY.register("python-3/loops", { label: 'Loops and recursion', panelConfig: { stack: true, heap: false, custom: false, legend: [ { color:'#1d5799', border:'#9dc0e8', label:'Currently running frame' }, { color:'#68655f', border:'#d4d1cc', label:'Paused, waiting to resume' }, { color:'#a0540a', border:'#e8c070', label:'Variable just assigned' }, { color:'#095e40', border:'#90d4b8', label:'Holding a value' }, { color:'#3d3490', border:'#b0a8e0', label:'Reference (list or object)' }, ] }, scenarios: [ /* ------------------------------------------------------------------ */ /* 1. while loop */ /* */ /* 0 count = 0 */ /* 1 while count < 3: */ /* 2 count += 1 */ /* 3 print("Done") */ /* ------------------------------------------------------------------ */ { label: 'while loop', lines: [ { code: [['tok-op','count = '],['tok-num','0']] }, { code: [['tok-kw','while '],['tok-op','count < '],['tok-num','3'],['tok-op',':']] }, { code: [['',' '],['tok-op','count += '],['tok-num','1']] }, { code: [['tok-meth','print'],['tok-op','('],['tok-str','"Done"'],['tok-op',')']] }, ], steps: [ { line:0, stack:[{name:'',vars:[{n:'count',v:'0',fresh:true,cls:false}],active:true}], expl:{label:'Variable assigned', text:'count = 0 creates a variable initialized to 0.'} }, { line:1, tokenRange:[1,2], stack:[{name:'',vars:[{n:'count',v:'0',fresh:false,cls:false}],active:true}], expl:{label:'Condition: true', text:'Python evaluates count < 3. Since 0 < 3, the condition is true and the loop body runs.'} }, { line:2, stack:[{name:'',vars:[{n:'count',v:'1',fresh:true,cls:false}],active:true}], expl:{label:'Loop body', text:'count += 1 increments count from 0 to 1.'} }, { line:1, tokenRange:[1,2], stack:[{name:'',vars:[{n:'count',v:'1',fresh:false,cls:false}],active:true}], expl:{label:'Condition: true', text:'Back at the top. 1 < 3 is still true -- another iteration begins.'} }, { line:2, stack:[{name:'',vars:[{n:'count',v:'2',fresh:true,cls:false}],active:true}], expl:{label:'Loop body', text:'count += 1 increments count to 2.'} }, { line:1, tokenRange:[1,2], stack:[{name:'',vars:[{n:'count',v:'2',fresh:false,cls:false}],active:true}], expl:{label:'Condition: true', text:'2 < 3 is still true -- one more iteration.'} }, { line:2, stack:[{name:'',vars:[{n:'count',v:'3',fresh:true,cls:false}],active:true}], expl:{label:'Loop body', text:'count += 1 increments count to 3.'} }, { line:1, tokenRange:[1,2], stack:[{name:'',vars:[{n:'count',v:'3',fresh:false,cls:false}],active:true}], expl:{label:'Condition: false', text:'3 < 3 is false. The loop exits -- execution continues after the while block.'} }, { line:3, stack:[{name:'',vars:[{n:'count',v:'3',fresh:false,cls:false}],active:true}], expl:{label:'After the loop', text:'count retains its value of 3. print("Done") runs.'} }, { line:3, stack:[{name:'',vars:[{n:'count',v:'3',fresh:false,cls:false}],active:true}], expl:{label:'Script ends', text:'There is nothing more to run.'} }, ] }, /* ------------------------------------------------------------------ */ /* 2. for loop with range() */ /* */ /* 0 total = 0 */ /* 1 for i in range(3): */ /* 2 total += i */ /* 3 print(total) */ /* ------------------------------------------------------------------ */ { label: 'for loop with range()', lines: [ { code: [['tok-op','total = '],['tok-num','0']] }, { code: [['tok-kw','for '],['tok-op','i '],['tok-kw','in '],['tok-meth','range'],['tok-op','('],['tok-num','3'],['tok-op','):']] }, { code: [['',' '],['tok-op','total += i']] }, { code: [['tok-meth','print'],['tok-op','(total)']] }, ], steps: [ { line:0, stack:[{name:'',vars:[{n:'total',v:'0',fresh:true,cls:false}],active:true}], expl:{label:'Variable assigned', text:'total = 0 initializes the accumulator.'} }, { line:1, stack:[{name:'',vars:[{n:'total',v:'0',fresh:false,cls:false},{n:'i',v:'0',fresh:true,cls:false}],active:true}], expl:{label:'First value from range', text:'Python asks range(3) for its first value. i = 0. The loop body will now run.'} }, { line:2, stack:[{name:'',vars:[{n:'total',v:'0',fresh:false,cls:false},{n:'i',v:'0',fresh:false,cls:false}],active:true}], expl:{label:'Loop body', text:'total += i adds 0 to total. total stays 0 this iteration.'} }, { line:1, stack:[{name:'',vars:[{n:'total',v:'0',fresh:false,cls:false},{n:'i',v:'1',fresh:true,cls:false}],active:true}], expl:{label:'Next value from range', text:'Python asks range(3) for its next value. i = 1.'} }, { line:2, stack:[{name:'',vars:[{n:'total',v:'1',fresh:true,cls:false},{n:'i',v:'1',fresh:false,cls:false}],active:true}], expl:{label:'Loop body', text:'total += i adds 1. total is now 1.'} }, { line:1, stack:[{name:'',vars:[{n:'total',v:'1',fresh:false,cls:false},{n:'i',v:'2',fresh:true,cls:false}],active:true}], expl:{label:'Next value from range', text:'Python asks range(3) for its next value. i = 2.'} }, { line:2, stack:[{name:'',vars:[{n:'total',v:'3',fresh:true,cls:false},{n:'i',v:'2',fresh:false,cls:false}],active:true}], expl:{label:'Loop body', text:'total += i adds 2. total is now 3.'} }, { line:1, stack:[{name:'',vars:[{n:'total',v:'3',fresh:false,cls:false}],active:true}], expl:{label:'range exhausted', text:'range(3) has no more values. The loop ends and i goes out of scope.'} }, { line:3, stack:[{name:'',vars:[{n:'total',v:'3',fresh:false,cls:false}],active:true}], expl:{label:'After the loop', text:'print(total) prints 3 -- the sum of 0 + 1 + 2.'} }, { line:3, stack:[{name:'',vars:[{n:'total',v:'3',fresh:false,cls:false}],active:true}], expl:{label:'Script ends', text:'There is nothing more to run.'} }, ] }, /* ------------------------------------------------------------------ */ /* 3. for-each (for n in list) */ /* */ /* 0 nums = [10, 20, 30] */ /* 1 total = 0 */ /* 2 for n in nums: */ /* 3 total += n */ /* 4 print(total) */ /* ------------------------------------------------------------------ */ { label: 'for-each loop', lines: [ { code: [['tok-op','nums = ['],['tok-num','10'],['tok-op',', '],['tok-num','20'],['tok-op',', '],['tok-num','30'],['tok-op',']']] }, { code: [['tok-op','total = '],['tok-num','0']] }, { code: [['tok-kw','for '],['tok-op','n '],['tok-kw','in '],['tok-op','nums:']] }, { code: [['',' '],['tok-op','total += n']] }, { code: [['tok-meth','print'],['tok-op','(total)']] }, ], steps: [ { line:0, stack:[{name:'',vars:[{n:'nums',v:'[10, 20, 30]',fresh:true,cls:true}],active:true}], expl:{label:'List created', text:'nums = [10, 20, 30] creates a list. nums holds a reference to it -- lists are reference types in Python.'} }, { line:1, stack:[{name:'',vars:[{n:'nums',v:'[10, 20, 30]',fresh:false,cls:true},{n:'total',v:'0',fresh:true,cls:false}],active:true}], expl:{label:'Accumulator initialized', text:'total = 0. It will hold the running sum.'} }, { line:2, stack:[{name:'',vars:[{n:'nums',v:'[10, 20, 30]',fresh:false,cls:true},{n:'total',v:'0',fresh:false,cls:false},{n:'n',v:'10',fresh:true,cls:false}],active:true}], expl:{label:'First element', text:'Python takes the first element from nums. n = 10.'} }, { line:3, stack:[{name:'',vars:[{n:'nums',v:'[10, 20, 30]',fresh:false,cls:true},{n:'total',v:'10',fresh:true,cls:false},{n:'n',v:'10',fresh:false,cls:false}],active:true}], expl:{label:'Loop body', text:'total += n adds 10. total is now 10.'} }, { line:2, stack:[{name:'',vars:[{n:'nums',v:'[10, 20, 30]',fresh:false,cls:true},{n:'total',v:'10',fresh:false,cls:false},{n:'n',v:'20',fresh:true,cls:false}],active:true}], expl:{label:'Next element', text:'Python takes the next element from nums. n = 20.'} }, { line:3, stack:[{name:'',vars:[{n:'nums',v:'[10, 20, 30]',fresh:false,cls:true},{n:'total',v:'30',fresh:true,cls:false},{n:'n',v:'20',fresh:false,cls:false}],active:true}], expl:{label:'Loop body', text:'total += n adds 20. total is now 30.'} }, { line:2, stack:[{name:'',vars:[{n:'nums',v:'[10, 20, 30]',fresh:false,cls:true},{n:'total',v:'30',fresh:false,cls:false},{n:'n',v:'30',fresh:true,cls:false}],active:true}], expl:{label:'Last element', text:'Python takes the last element from nums. n = 30.'} }, { line:3, stack:[{name:'',vars:[{n:'nums',v:'[10, 20, 30]',fresh:false,cls:true},{n:'total',v:'60',fresh:true,cls:false},{n:'n',v:'30',fresh:false,cls:false}],active:true}], expl:{label:'Loop body', text:'total += n adds 30. total is now 60.'} }, { line:2, stack:[{name:'',vars:[{n:'nums',v:'[10, 20, 30]',fresh:false,cls:true},{n:'total',v:'60',fresh:false,cls:false}],active:true}], expl:{label:'No more elements', text:'All elements have been visited. The loop exits and n goes out of scope.'} }, { line:4, stack:[{name:'',vars:[{n:'nums',v:'[10, 20, 30]',fresh:false,cls:true},{n:'total',v:'60',fresh:false,cls:false}],active:true}], expl:{label:'After the loop', text:'print(total) prints 60 -- the sum of all elements.'} }, { line:4, stack:[{name:'',vars:[{n:'nums',v:'[10, 20, 30]',fresh:false,cls:true},{n:'total',v:'60',fresh:false,cls:false}],active:true}], expl:{label:'Script ends', text:'There is nothing more to run.'} }, ] }, /* ------------------------------------------------------------------ */ /* 4. Recursion */ /* */ /* Line index (blank lines excluded from count): */ /* 0 def countdown(n): */ /* 1 if n == 0: */ /* 2 print("Go!") */ /* 3 return */ /* 4 print(n) */ /* 5 countdown(n - 1) */ /* (blank -- not counted) */ /* 6 countdown(3) */ /* ------------------------------------------------------------------ */ { label: 'Recursion', lines: [ { code: [['tok-kw','def '],['tok-meth','countdown'],['tok-op','(n):']] }, { code: [['',' '],['tok-kw','if '],['tok-op','n == '],['tok-num','0'],['tok-op',':']] }, { code: [['',' '],['tok-meth','print'],['tok-op','('],['tok-str','"Go!"'],['tok-op',')']] }, { code: [['',' '],['tok-kw','return']] }, { code: [['',' '],['tok-meth','print'],['tok-op','(n)']] }, { code: [['',' '],['tok-meth','countdown'],['tok-op','(n - '],['tok-num','1'],['tok-op',')']] }, { blank: true }, { code: [['tok-meth','countdown'],['tok-op','('],['tok-num','3'],['tok-op',')']] }, ], steps: [ { line:0, stack:[{name:'',vars:[],active:true}], expl:{label:'Script starts', text:'Python reads the def statement and stores countdown as a function.'} }, { line:6, stack:[{name:'',vars:[],active:true}], expl:{label:'Calling countdown(3)', text:'countdown(3) is called. Python will push a new frame for it.'} }, { line:1, stack:[{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:true,cls:false}],active:true},{name:'',vars:[],active:false}], expl:{label:'Frame created', text:'A new frame for countdown() is pushed. Parameter n is set to 3. <module> is now waiting.'} }, { line:1, stack:[{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:false,cls:false}],active:true},{name:'',vars:[],active:false}], expl:{label:'Base case check', text:'n == 0? No -- 3 is not 0. The if block is skipped.'} }, { line:4, stack:[{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:false,cls:false}],active:true},{name:'',vars:[],active:false}], expl:{label:'Print n', text:'print(n) prints 3.'} }, { line:5, stack:[{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:false,cls:false}],active:true},{name:'',vars:[],active:false}], expl:{label:'Recursive call', text:'countdown(n - 1) calls itself with 2. A new frame is about to be pushed.'} }, { line:1, stack:[{name:'countdown(n=2)',vars:[{n:'n',v:'2',fresh:true,cls:false}],active:true},{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:false,cls:false}],active:false},{name:'',vars:[],active:false}], expl:{label:'Stack grows', text:'A second countdown() frame is pushed. Each call has its own n. The first call waits below.'} }, { line:1, stack:[{name:'countdown(n=2)',vars:[{n:'n',v:'2',fresh:false,cls:false}],active:true},{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:false,cls:false}],active:false},{name:'',vars:[],active:false}], expl:{label:'Base case check', text:'n == 0? No -- 2 is not 0.'} }, { line:4, stack:[{name:'countdown(n=2)',vars:[{n:'n',v:'2',fresh:false,cls:false}],active:true},{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:false,cls:false}],active:false},{name:'',vars:[],active:false}], expl:{label:'Print n', text:'Prints 2.'} }, { line:5, stack:[{name:'countdown(n=2)',vars:[{n:'n',v:'2',fresh:false,cls:false}],active:true},{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:false,cls:false}],active:false},{name:'',vars:[],active:false}], expl:{label:'Recursive call', text:'countdown(1) is called. Another frame will be pushed.'} }, { line:1, stack:[{name:'countdown(n=1)',vars:[{n:'n',v:'1',fresh:true,cls:false}],active:true},{name:'countdown(n=2)',vars:[{n:'n',v:'2',fresh:false,cls:false}],active:false},{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:false,cls:false}],active:false},{name:'',vars:[],active:false}], expl:{label:'Stack grows', text:'Three countdown() frames now sit above <module>. Each is waiting for the one above it to finish.'} }, { line:1, stack:[{name:'countdown(n=1)',vars:[{n:'n',v:'1',fresh:false,cls:false}],active:true},{name:'countdown(n=2)',vars:[{n:'n',v:'2',fresh:false,cls:false}],active:false},{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:false,cls:false}],active:false},{name:'',vars:[],active:false}], expl:{label:'Base case check', text:'n == 0? No -- 1 is not 0.'} }, { line:4, stack:[{name:'countdown(n=1)',vars:[{n:'n',v:'1',fresh:false,cls:false}],active:true},{name:'countdown(n=2)',vars:[{n:'n',v:'2',fresh:false,cls:false}],active:false},{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:false,cls:false}],active:false},{name:'',vars:[],active:false}], expl:{label:'Print n', text:'Prints 1.'} }, { line:5, stack:[{name:'countdown(n=1)',vars:[{n:'n',v:'1',fresh:false,cls:false}],active:true},{name:'countdown(n=2)',vars:[{n:'n',v:'2',fresh:false,cls:false}],active:false},{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:false,cls:false}],active:false},{name:'',vars:[],active:false}], expl:{label:'Recursive call', text:'countdown(0) is called -- this will trigger the base case.'} }, { line:1, stack:[{name:'countdown(n=0)',vars:[{n:'n',v:'0',fresh:true,cls:false}],active:true},{name:'countdown(n=1)',vars:[{n:'n',v:'1',fresh:false,cls:false}],active:false},{name:'countdown(n=2)',vars:[{n:'n',v:'2',fresh:false,cls:false}],active:false},{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:false,cls:false}],active:false},{name:'',vars:[],active:false}], expl:{label:'Deepest call', text:'The stack is now at its deepest: five frames. n = 0 in this frame.'} }, { line:1, stack:[{name:'countdown(n=0)',vars:[{n:'n',v:'0',fresh:false,cls:false}],active:true},{name:'countdown(n=1)',vars:[{n:'n',v:'1',fresh:false,cls:false}],active:false},{name:'countdown(n=2)',vars:[{n:'n',v:'2',fresh:false,cls:false}],active:false},{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:false,cls:false}],active:false},{name:'',vars:[],active:false}], expl:{label:'Base case: true', text:'n == 0 is true. The base case is reached -- the if block executes.'} }, { line:2, stack:[{name:'countdown(n=0)',vars:[{n:'n',v:'0',fresh:false,cls:false}],active:true},{name:'countdown(n=1)',vars:[{n:'n',v:'1',fresh:false,cls:false}],active:false},{name:'countdown(n=2)',vars:[{n:'n',v:'2',fresh:false,cls:false}],active:false},{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:false,cls:false}],active:false},{name:'',vars:[],active:false}], expl:{label:'Print "Go!"', text:'print("Go!") runs. The countdown is complete.'} }, { line:3, stack:[{name:'countdown(n=0)',vars:[{n:'n',v:'0',fresh:false,cls:false}],active:true},{name:'countdown(n=1)',vars:[{n:'n',v:'1',fresh:false,cls:false}],active:false},{name:'countdown(n=2)',vars:[{n:'n',v:'2',fresh:false,cls:false}],active:false},{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:false,cls:false}],active:false},{name:'',vars:[],active:false}], expl:{label:'return', text:'return exits countdown(n=0). Its frame is popped -- the stack begins to unwind.'} }, { line:5, stack:[{name:'countdown(n=1)',vars:[{n:'n',v:'1',fresh:false,cls:false}],active:true},{name:'countdown(n=2)',vars:[{n:'n',v:'2',fresh:false,cls:false}],active:false},{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:false,cls:false}],active:false},{name:'',vars:[],active:false}], expl:{label:'Unwinding', text:'Back in countdown(n=1). It has no more work to do -- it reaches the end of its body and returns.'} }, { line:5, stack:[{name:'countdown(n=2)',vars:[{n:'n',v:'2',fresh:false,cls:false}],active:true},{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:false,cls:false}],active:false},{name:'',vars:[],active:false}], expl:{label:'Unwinding', text:'countdown(n=2) also completes and is popped.'} }, { line:5, stack:[{name:'countdown(n=3)',vars:[{n:'n',v:'3',fresh:false,cls:false}],active:true},{name:'',vars:[],active:false}], expl:{label:'Unwinding', text:'countdown(n=3) completes and is popped. Only <module> remains.'} }, { line:6, stack:[{name:'',vars:[],active:true}], expl:{label:'Back in module', text:'<module> resumes after the original countdown(3) call. Script ends.'} }, ] }, ] });