// Numbas version: exam_results_page_options {"name": "Long division in a spreadsheet", "extensions": ["sheets"], "custom_part_types": [{"source": {"pk": 242, "author": {"name": "Christian Lawson-Perfect", "pk": 7}, "edit_page": "/part_type/242/edit"}, "name": "Spreadsheet", "short_name": "spreadsheet", "description": "

An editable spreadsheet. Ranges of cells can be disabled, and you can specify ranges of cells to be marked. A cell is marked correct if its value is equal to the value in the expected answer spreadsheet.

", "help_url": "", "input_widget": "spread-sheet", "input_options": {"correctAnswer": "settings[\"correct_answer\"]", "hint": {"static": true, "value": ""}, "initial_sheet": {"static": false, "value": "disable_cells(settings[\"initial_sheet\"], settings[\"disable_ranges\"])"}}, "can_be_gap": true, "can_be_step": true, "marking_script": "correctAnswer:\nsettings[\"correct_answer\"]\n\nmark:\nif(sum(mark_ranges)=0,\n incorrect(),\n apply(mark_ranges)\n)\n\ninterpreted_answer:\nstudentAnswer\n\nrange_cells:\nmap(parse_range(ref),ref,values(settings[\"mark_ranges\"]))\n\ntotal_cells:\nlen(flatten(range_cells))\n\nrange_weights:\nswitch(\n settings[\"marking_method\"]=\"per_cell\",\n map(len(r)/total_cells, r, range_cells),\n // otherwise, mark per range\n repeat(1/len(range_cells), len(range_cells))\n)\n\nmark_ranges:\nmap(\n let(\n range_credit,\n sum(map(\n let(\n correctCellString, correctAnswer[c],\n correctCellNumber, parsenumber(correctCellString, notation_styles),\n studentCellString, studentAnswer[c],\n studentCellNumber, parsenumber(studentCellString, notation_styles),\n award(\n 1/len(cells), \n if(isnan(correctCellNumber) and correctCellString<>\"\",\n lower(correctCellString) = lower(studentCellString),\n abs(studentCellNumber - if(isnan(correctCellNumber),0,correctCellNumber)) <= settings[\"tolerance\"]\n )\n )\n ),\n c,\n cells\n )),\n message,\n switch(\n range_credit=0,\n if(len(cells)=1, \"This entry is incorrect.\", \"All entries in this range are incorrect.\"),\n range_credit=1,\n if(len(cells)=1, \"This entry is correct.\", \"All entries in this range are correct.\"),\n //otherwise\n \"Some entries in this range are correct.\"\n ),\n assert(len(cells)=0, add_credit(range_credit*w, \"{name}: \"+message)); \n range_credit\n ),\n [cells,w,name],\n zip(range_cells, range_weights, keys(settings[\"mark_ranges\"]))\n)\n\nnotation_styles:\n[\"plain\",\"si-en\"]", "marking_notes": [{"name": "correctAnswer", "description": "

A spreadsheet representing the expected answer.

", "definition": "settings[\"correct_answer\"]"}, {"name": "mark", "description": "This is the main marking note. It should award credit and provide feedback based on the student's answer.", "definition": "if(sum(mark_ranges)=0,\n incorrect(),\n apply(mark_ranges)\n)"}, {"name": "interpreted_answer", "description": "A value representing the student's answer to this part.", "definition": "studentAnswer"}, {"name": "range_cells", "description": "

For each range to be marked, the addresses of the cells in that range.

", "definition": "map(parse_range(ref),ref,values(settings[\"mark_ranges\"]))"}, {"name": "total_cells", "description": "

The total number of cells to be marked. Cells in overlapping ranges will be counted once for each range they're in.

", "definition": "len(flatten(range_cells))"}, {"name": "range_weights", "description": "

The weight of each range, as a proportion of the available credit.

", "definition": "switch(\n settings[\"marking_method\"]=\"per_cell\",\n map(len(r)/total_cells, r, range_cells),\n // otherwise, mark per range\n repeat(1/len(range_cells), len(range_cells))\n)"}, {"name": "mark_ranges", "description": "

Mark each of the ranges specified by the question author.

", "definition": "map(\n let(\n range_credit,\n sum(map(\n let(\n correctCellString, correctAnswer[c],\n correctCellNumber, parsenumber(correctCellString, notation_styles),\n studentCellString, studentAnswer[c],\n studentCellNumber, parsenumber(studentCellString, notation_styles),\n award(\n 1/len(cells), \n if(isnan(correctCellNumber) and correctCellString<>\"\",\n lower(correctCellString) = lower(studentCellString),\n abs(studentCellNumber - if(isnan(correctCellNumber),0,correctCellNumber)) <= settings[\"tolerance\"]\n )\n )\n ),\n c,\n cells\n )),\n message,\n switch(\n range_credit=0,\n if(len(cells)=1, \"This entry is incorrect.\", \"All entries in this range are incorrect.\"),\n range_credit=1,\n if(len(cells)=1, \"This entry is correct.\", \"All entries in this range are correct.\"),\n //otherwise\n \"Some entries in this range are correct.\"\n ),\n assert(len(cells)=0, add_credit(range_credit*w, \"{name}: \"+message)); \n range_credit\n ),\n [cells,w,name],\n zip(range_cells, range_weights, keys(settings[\"mark_ranges\"]))\n)"}, {"name": "notation_styles", "description": "

Accepted number notation styles for a value in an individual cell.

", "definition": "[\"plain\",\"si-en\"]"}], "settings": [{"name": "initial_sheet", "label": "Initial sheet", "help_url": "", "hint": "A spreadsheet object giving the initial state of the sheet that the student should fill in.", "input_type": "code", "default_value": "", "evaluate": true}, {"name": "correct_answer", "label": "Correct answer", "help_url": "", "hint": "A spreadsheet object representing a correct answer to the part.", "input_type": "code", "default_value": "", "evaluate": true}, {"name": "disable_ranges", "label": "Ranges to disable", "help_url": "", "hint": "A list of cell or range references, denoting the cells that should not be editable.", "input_type": "code", "default_value": "[]", "evaluate": true}, {"name": "mark_ranges", "label": "Ranges to mark", "help_url": "", "hint": "A dictionary of cell or range references, mapping names to ranges of cells, denoting the cells that should be compared for equality with the expected answer.", "input_type": "code", "default_value": "dict()", "evaluate": true}, {"name": "marking_method", "label": "Marking method", "help_url": "", "hint": "", "input_type": "dropdown", "default_value": "per_cell", "choices": [{"value": "per_cell", "label": "Each cell has the same weight"}, {"value": "per_range", "label": "Each range has the same weight"}]}, {"name": "tolerance", "label": "Allowed margin of error", "help_url": "", "hint": "", "input_type": "code", "default_value": "0", "evaluate": true}], "public_availability": "always", "published": true, "extensions": ["sheets"]}], "resources": [], "navigation": {"allowregen": true, "showfrontpage": false, "preventleave": false, "typeendtoleave": false}, "question_groups": [{"pickingStrategy": "all-ordered", "questions": [{"name": "Long division in a spreadsheet", "tags": [], "metadata": {"description": "

The student is asked to calculate a division by the method of long division, which they should enter in a grid.

\n

The process is simulated and the order in which cells are filled in is recorded, so the marking feedback tries to identify the first cell that the student got wrong, or should try to fill in next.

\n

They're asked to give the quotient as a plain number in a second part, to check that they can interpret the finished grid properly.

", "licence": "Creative Commons Attribution 4.0 International"}, "statement": "

I've calculated $\\var{example_a} \\div \\var{example_b}$ by long division:

\n

{example_finished}

\n

So $\\var{example_a} \\div \\var{example_b} = \\var{example_a/example_b}$.

", "advice": "", "rulesets": {}, "extensions": ["sheets"], "builtin_constants": {"e": true, "pi,\u03c0": true, "i": true}, "constants": [], "variables": {"sheet,correct_sheet,cell_order": {"name": "sheet,correct_sheet,cell_order", "group": "Ungrouped variables", "definition": "complete_long_division(a,b)", "description": "", "templateType": "anything", "can_override": false}, "b,c": {"name": "b,c", "group": "Ungrouped variables", "definition": "repeat(random(11..200 except [example_c, example_b]),2)", "description": "", "templateType": "anything", "can_override": false}, "a": {"name": "a", "group": "Ungrouped variables", "definition": "b*c", "description": "", "templateType": "anything", "can_override": false}, "example_sheet, example_finished, example_cell_order": {"name": "example_sheet, example_finished, example_cell_order", "group": "Ungrouped variables", "definition": "complete_long_division(example_a, example_b)", "description": "", "templateType": "anything", "can_override": false}, "example_c, example_b": {"name": "example_c, example_b", "group": "Ungrouped variables", "definition": "[123,41]", "description": "", "templateType": "anything", "can_override": false}, "example_a": {"name": "example_a", "group": "Ungrouped variables", "definition": "example_b * example_c", "description": "", "templateType": "anything", "can_override": false}, "na": {"name": "na", "group": "Ungrouped variables", "definition": "int(len(string(a)))", "description": "", "templateType": "anything", "can_override": false}, "nb": {"name": "nb", "group": "Ungrouped variables", "definition": "int(len(string(b)))", "description": "", "templateType": "anything", "can_override": false}}, "variablesTest": {"condition": "", "maxRuns": 100}, "ungrouped_variables": ["sheet,correct_sheet,cell_order", "b,c", "example_sheet, example_finished, example_cell_order", "example_c, example_b", "example_a", "a", "na", "nb"], "variable_groups": [], "functions": {"long_division_steps": {"parameters": [["a", "number"], ["b", "number"]], "type": "anything", "language": "javascript", "definition": "// a/b\n\n// get the digits of x as a list of integers\nfunction digits_of(x) {\n return (x+'').split('').map(d=>parseInt(d));\n}\n\n// Given a list of digits, produce the number it represents.\nfunction from_digits(digits) {\n\tlet t = 0;\n for(let d of digits) {\n t = 10*t + d;\n }\n return t;\n}\n\nconst a_digits = digits_of(a);\nconst b_digits = digits_of(b);\n\n// Sort two numbers represented as lists of digits, lexicographically: look at most-significant digits first, and put the biggest values first.\nfunction compare_digits(d1,d2) {\n for(let i=0;ib) {\n return 1;\n }\n }\n return 0;\n}\n\n// Compute the multiplication table of b, and sort it lexicographically\nconst multiplication_table = [1,2,3,4,5,6,7,8,9].map(i=>[digits_of(i*b),i]).sort(([d1,i1],[d2,i2]) => compare_digits(d1,d2));\n\n// Perform the long division\nconst steps = [];\nlet d = a_digits; // `d` is the value to be divided by `b`. We'll take away multiples of `b` until it's zero.\nlet x = 0; // `x` is the position in the quotient of the current step. 0 is the most-significant digit.\nwhile(d.length && x db) {\n // a is bigger than m: can use\n break;\n } else if(da < db) {\n // a is smaller than m\n ok = false;\n break;\n } else {\n //check another digit\n }\n }\n if(ok) {\n best = [i, m];\n }\n }\n // If there was no multiple of b that works, then carry the most-significant digit of d to the next digit along\n if(!best) {\n x += 1;\n const z = d.shift();\n d[0] += z*10;\n continue;\n }\n \n // If there was a multiple of `b` that works, then subtract it from `d` and record a step.\n const [i, m] = best;\n const nd = d.slice();\n for(let j=0; jz%10), i, m, nd, step: steps.length});\n d = nd;\n x = nx;\n}\n\nreturn Numbas.jme.wrapValue(steps);"}, "complete_long_division": {"parameters": [["a", "number"], ["b", "number"]], "type": "anything", "language": "jme", "definition": "// Show the steps of computing a\u00f7b by long division\nlet(\n a_digits, split(string(a),\"\"),\n b_digits, split(string(b),\"\"),\n na, int(len(a_digits)),\n nb, int(len(b_digits)),\n dsteps, long_division_steps(a,b),\n sheet, \n spreadsheet([[\"\"]])\n |> fill_range(encode_range(0,0,na+nb,int(2*len(dsteps))+2),[[]])\n |> update_range(encode_range(0,0,na+nb,int(2*len(dsteps))+2), cell_type(\"number\"))\n |> fill_range(encode_range(0,1,nb-1,1),[b_digits])\n |> fill_range(encode_range(nb,1,na+nb-1,1),[a_digits])\n |> update_range(encode_range(nb,1,nb,1),border(\"left\",\"thick\"))\n |> update_range(encode_range(nb,1,na+nb-1,1), border(\"top\",\"thick\"))\n |> disable_cells([encode_range(0,1,na+nb,1)])\n, [correct_sheet, cell_order],\n foldl(\n let(\n sheet, v[0],\n cells, v[1],\n x, int(s[\"x\"])+nb,\n y, int(s[\"step\"]*2)+1,\n d, s[\"d\"],\n m, s[\"m\"],\n dx, int(x+len(m)-1),\n d_range, encode_range(x, y, x+int(len(d)), y),\n m_range, encode_range(x, y+1, x+int(len(m)-1), y+1),\n i_range, encode_range(dx,0,dx,0),\n [\n sheet\n |> fill_range(d_range, if(s[\"step\"]=0,d,d[0..len(m)]))\n |> fill_range(m_range, m)\n |> fill_range(i_range,[s[\"i\"]])\n |> update_range(m_range, border(\"bottom\",\"thin\"))\n , cells+flatten(map(parse_range(r), r, [d_range,i_range,m_range]))\n ]\n )\n , v\n , s\n , [sheet, []]\n , dsteps\n )\n, [sheet, correct_sheet, cell_order]\n)"}}, "preamble": {"js": "", "css": "spread-sheet td {\n border-width: 0;\n}\n\nspread-sheet td > textarea.input-value {\n background: #eee;\n}"}, "parts": [{"type": "spreadsheet", "useCustomName": true, "customName": "Now you do it", "marks": 1, "scripts": {}, "customMarkingAlgorithm": "first_wrong_cell: \nlet(\n correct,map(studentAnswer[cell]=correct_sheet[cell] or (studentAnswer[cell]=\"0\" and correct_sheet[cell]=\"\"),cell,cell_order),\n is,indices(correct,false),\n if(len(is)>0, cell_order[is[0]], nothing)\n)\n\nfirst_wrong_filled_cell: \nlet(\n incorrect,map(studentAnswer[cell]<>\"\" and studentAnswer[cell] <> correct_sheet[cell],cell,cell_order),\n is,indices(incorrect,true),\n if(len(is)>0, cell_order[is[0]], nothing)\n)\n\nquotient:\n parsenumber(join(studentAnswer[encode_range(0,0,na+nb,0)][0],\"\"), \"plain\")\n\nstudent_first_wrong_cell: studentAnswer[first_wrong_cell]\n\ncorrect_first_wrong_cell: correct_sheet[first_wrong_cell]\n\nmark:\n if(quotient=a/b,\n correct()\n ,\n if(first_wrong_filled_cell <> nothing,\n incorrect(\"I think you went wrong at {first_wrong_filled_cell}\");\n feedback(\n studentAnswer |> update_range(first_wrong_filled_cell,bg_color(\"red\"))\n )\n , \n if(studentAnswer[first_wrong_cell]=\"\",\n feedback(\"Next try to fill in {first_wrong_cell}\")\n , incorrect(\"I think you went wrong at {first_wrong_cell}\")\n );\n feedback(\n studentAnswer |> update_range(first_wrong_cell,bg_color(\"yellow\"))\n )\n )\n )", "extendBaseMarkingAlgorithm": true, "unitTests": [], "showCorrectAnswer": true, "showFeedbackIcon": true, "variableReplacements": [], "variableReplacementStrategy": "originalfirst", "nextParts": [], "suggestGoingBack": false, "adaptiveMarkingPenalty": 0, "exploreObjective": null, "prompt": "

Now you calculate $\\var{a} \\div \\var{b}$ by long division.

\n

You can press the Submit button at any time to check you're doing it right, or to get a hint about what to do next.

", "settings": {"initial_sheet": "sheet", "correct_answer": "correct_sheet", "disable_ranges": "[]", "mark_ranges": "dict()", "marking_method": "per_cell", "tolerance": "0"}}, {"type": "numberentry", "useCustomName": true, "customName": "So what's the answer?", "marks": 1, "scripts": {}, "customMarkingAlgorithm": "", "extendBaseMarkingAlgorithm": true, "unitTests": [], "showCorrectAnswer": true, "showFeedbackIcon": true, "variableReplacements": [], "variableReplacementStrategy": "originalfirst", "nextParts": [], "suggestGoingBack": false, "adaptiveMarkingPenalty": 0, "exploreObjective": null, "prompt": "

So what is $\\var{a} \\div \\var{b}$?

", "minValue": "a/b", "maxValue": "a/b", "correctAnswerFraction": false, "allowFractions": false, "mustBeReduced": false, "mustBeReducedPC": 0, "displayAnswer": "", "showFractionHint": true, "notationStyles": ["plain", "en", "si-en"], "correctAnswerStyle": "plain"}], "partsMode": "all", "maxMarks": 0, "objectives": [], "penalties": [], "objectiveVisibility": "always", "penaltyVisibility": "always", "contributors": [{"name": "Christian Lawson-Perfect", "profile_url": "https://numbas.mathcentre.ac.uk/accounts/profile/7/"}]}]}], "contributors": [{"name": "Christian Lawson-Perfect", "profile_url": "https://numbas.mathcentre.ac.uk/accounts/profile/7/"}]}