# Khan/khan-exercises

### Subversion checkout URL

You can clone with HTTPS or Subversion.

Add exercises: Finding the determinant of a matrix, finding the inver…

…se of a matrix

Test Plan: Tested locally

Reviewers: eater

Reviewed By: eater

Differential Revision: http://phabricator.khanacademy.org/D891
commit 8fc85e7bcee3198c18e3b744f4fe3fa9f0e68ecf 1 parent 6935430
stephjang authored
42 css/khan-exercise.css
 @@ -655,6 +655,12 @@ div.timeline-total { filter: alpha(opacity = 80); } +/* Video hints */ + +.video-hint { + margin-bottom: 20px; +} + /* Question-based hints */ .qhint { @@ -683,4 +689,40 @@ div.timeline-total { .qhint-feedback.incorrect { color: #CE4444; +} + +/* Fancy matrix input - goes along with matrix-input.js */ + +#answer_area .matrix-row .sol { + margin: 0; + float: left; +} + +#answer_area .matrix-row .sol input[type=text] { + width: 45px; + height: 30px; + border: none; + margin: 3px; + padding: 1px; +} + +.matrix-input .matrix-bracket { + width: 6px; + position: absolute; + border-top: 2px solid #888; + border-bottom: 2px solid #888; + /* margin-top must have the same magnitude + as the border widths */ + margin-top: -2px; +} + +.matrix-input .matrix-bracket.bracket-left { + border-left: 2px solid #888; + /* margin-left for the left bracket must have + the same magnitude as the border widths */ + margin-left: -2px; +} + +.matrix-input .matrix-bracket.bracket-right { + border-right: 2px solid #888; }
 @@ -1,19 +1,9 @@ - + Matrix addition and subtraction -
@@ -32,6 +22,7 @@ } }, MAT_A, MAT_B) + matrixPad(SOLN_MAT, 3, 3) BLUE GREEN @@ -86,11 +77,16 @@
-

- - elem +

+ + + elem + + + elem + -

+
60 exercises/matrix_determinant.html
 @@ -0,0 +1,60 @@ + + + + + Matrix determinant + + + +
+ +
+ makeMatrix(randRange(-2, 5, DIM, DIM)) + matrixDet(MAT) + "\\textbf " + randFromArray("ABCDEF") +
+ +
+

+ PRETTY_MAT_ID = printSimpleMatrix(MAT) +

+
+ +

+ What is the determinant of PRETTY_MAT_ID? +

+ +
+
+

The solution is:

+ \text{det}(PRETTY_MAT_ID) = SOLN +
+
+ +
+ SOLN +
+ +
+
+
+ 2 +
+
+
+
+
+ +
+
+ 3 +
+
+
+
+
+
+ +
+ +
74 exercises/matrix_inverse_2x2.html
 @@ -0,0 +1,74 @@ + + + + + Inverse of a 2x2 matrix + + + +
+ +
+ 2 + makeMatrix(randRange(-2, 5, DIM, DIM)) + matrixDet(MAT) + matrixInverse(MAT) + matrixPad(SOLN_MAT, 3, 3) + + printMatrix(function(a) { + var sign = (a < 0) ? "-" : ""; + + var frac = toFraction(abs(a)); + + // omit denominator when it's equal to 1 + if (frac[1] === 1) { + return sign + frac[0]; + } + + return sign + "\\frac{" + frac[0] + "}{" + frac[1] + "}"; + }, SOLN_MAT) + + "\\textbf " + randFromArray("ABCDEF") +
+ +
+
+
+

+ PRETTY_MAT_ID = printSimpleMatrix(MAT) +

+
+ +

+

+ What is PRETTY_MAT_ID^{-1}? +

+

+ +
+
+ + + elem + + + elem + + +
+

+

The fractions do not need to be simplified.

+
+ +
+
+
+ PRETTY_MAT_ID^{-1} = PRETTY_SOLN_MAT +
+
+
+
+ +
+ +
76 exercises/matrix_inverse_3x3.html
 @@ -0,0 +1,76 @@ + + + + + Inverse of a 3x3 matrix + + + +
+ +
+ 3 + makeMatrix(randRange(-2, 5, DIM, DIM)) + matrixDet(MAT) + matrixInverse(MAT) + matrixPad(SOLN_MAT, 3, 3) + + printMatrix(function(a) { + var sign = (a < 0) ? "-" : ""; + + var frac = toFraction(abs(a)); + + // omit denominator when it's equal to 1 + if (frac[1] === 1) { + return sign + frac[0]; + } + + return sign + "\\frac{" + frac[0] + "}{" + frac[1] + "}"; + }, SOLN_MAT) + + "\\textbf " + randFromArray("ABCDEF") +
+ +
+
+
+

+ PRETTY_MAT_ID = printSimpleMatrix(MAT) +

+
+ +

+

+ What is PRETTY_MAT_ID^{-1}? +

+

+ +
+
+ + + elem + + + elem + + +
+

+

The fractions do not need to be simplified.

+
+ +
+
+
+
+
+ PRETTY_MAT_ID^{-1} = PRETTY_SOLN_MAT +
+
+
+
+ +
+ +
94 exercises/matrix_transpose.html
 @@ -0,0 +1,94 @@ + + + + + Matrix transpose + + + +
+ +
+ makeMatrix(randRange(0, 9, DIM_1, DIM_2)) + matrixTranspose(MAT) + matrixPad(SOLN_MAT, 3, 3) + "\\textbf " + randFromArray("ABCDEF") + printSimpleMatrix(SOLN_MAT) +
+ +
+

+ PRETTY_MAT_ID = printSimpleMatrix(MAT) +

+
+ +

+ What is PRETTY_MAT_ID^{T}? +

+ +
+
+ PRETTY_MAT_ID^{T} = PRETTY_SOLN_MAT +
+
+ +
+
+ + + elem + + + elem + + +
+
+ +
+
+
+ 1 + 2 +
+
+ +
+
+ 2 + 2 +
+
+ +
+
+ 2 + 3 +
+
+ +
+
+ 3 + 1 +
+
+ +
+
+ 3 + 2 +
+
+ +
+
+ 3 + 3 +
+
+
+ +
+ +
32 exercises/multiplying_a_matrix_by_a_matrix.html
 @@ -1,19 +1,9 @@ - + Multiplying a matrix by a matrix -
@@ -26,6 +16,7 @@ matrixMult(MAT_1, MAT_2) + matrixPad(SOLN_MAT, 3, 3) "\\textbf " + randFromArray("ABCDEF") "\\textbf " + randFromArray("ABCDEF") @@ -42,8 +33,10 @@

- Let PRETTY_MAT_1_ID = printSimpleMatrix(MAT_1) and - PRETTY_MAT_2_ID = printSimpleMatrix(MAT_2). + PRETTY_MAT_1_ID = printSimpleMatrix(MAT_1) +

+

+ PRETTY_MAT_2_ID = printSimpleMatrix(MAT_2)

@@ -139,11 +132,16 @@
-

- - elem +

+ + + elem + + + elem + -

+
116 exercises/multiplying_a_matrix_by_a_matrix_solution_only.html
 @@ -0,0 +1,116 @@ + + + + + Multiplying a matrix by a matrix + + + +
+ +
+ DIM_2 + makeMatrix(randRange(-2, 5, DIM_1, DIM_2)) + makeMatrix(randRange(-2, 5, DIM_3, DIM_4)) + + + matrixMult(MAT_1, MAT_2) + + matrixPad(SOLN_MAT, 3, 3) + + "\\textbf " + randFromArray("ABCDEF") + "\\textbf " + randFromArray("ABCDEF") + + + [GREEN, BLUE, GRAY] + [ORANGE, "#DF0030", "#9D38BD"] + + + makeMultHintMatrix(MAT_1, MAT_2, ROW_COLORS, COL_COLORS) + + +
+ +
+

+ PRETTY_MAT_1_ID = printSimpleMatrix(MAT_1) +

+

+ PRETTY_MAT_2_ID = printSimpleMatrix(MAT_2) +

+
+ +

+ What is PRETTY_MAT_1_ID + PRETTY_MAT_2_ID? +

+ +
+
+

The solution is:

+
+ + printSimpleMatrix(SOLN_MAT) + +
+
+
+ +
+
+ + + elem + + + elem + + +
+
+ +
+ +
+
+ 2 + 2 + 2 +
+
+
+
+ 2 + 3 + 2 +
+
+ + +
+
+ 2 + 2 + 3 +
+
+ + +
+
+ 3 + 1 + 2 +
+
+
+
+ 3 + 2 + 2 +
+
+
+ +
+ +
176 exercises/multiplying_a_matrix_by_a_matrix_steps_only.html
 @@ -0,0 +1,176 @@ + + + + + Multiplying a matrix by a matrix + + + +
+ +
+ DIM_2 + makeMatrix(randRange(-2, 5, DIM_1, DIM_2)) + makeMatrix(randRange(-2, 5, DIM_3, DIM_4)) + + + matrixMult(MAT_1, MAT_2) + + matrixPad(SOLN_MAT, 3, 3) + + "\\textbf " + randFromArray("ABCDEF") + "\\textbf " + randFromArray("ABCDEF") + + + [GREEN, BLUE, GRAY] + [ORANGE, "#DF0030", "#9D38BD"] + + + makeMultHintMatrix(MAT_1, MAT_2, ROW_COLORS, COL_COLORS) + + +
+ +
+

+ PRETTY_MAT_1_ID = printSimpleMatrix(MAT_1) +

+

+ PRETTY_MAT_2_ID = printSimpleMatrix(MAT_2) +

+
+ +

+ What is PRETTY_MAT_1_ID + PRETTY_MAT_2_ID? +

+ +
+
+

+ + PRETTY_MAT_1_ID + PRETTY_MAT_2_ID + = + printColoredDimMatrix(MAT_1, ROW_COLORS, true) + printColoredDimMatrix(MAT_2, COL_COLORS, false) + = + + printSimpleMatrix(maskMatrix(FINAL_HINT_MAT, [])) + + +

+
+
+

+ + = + + printSimpleMatrix( + maskMatrix(FINAL_HINT_MAT, [[1, 1]]) + ) + + +

+
+
+

+ + = + + printSimpleMatrix( + maskMatrix(FINAL_HINT_MAT, [[1, 1], [2, 1]]) + ) + + +

+
+
+

+ + = + + printSimpleMatrix( + maskMatrix(FINAL_HINT_MAT, [[1, 1], [2, 1], [1, 2]]) + ) + + +

+
+
+

+ + = + + printSimpleMatrix(FINAL_HINT_MAT) + + +

+
+
+
+ + = + printSimpleMatrix(SOLN_MAT) + +
+
+
+ +
+
+ + + elem + + + elem + + +
+
+ +
+ +
+
+ 2 + 2 + 2 +
+
+
+
+ 2 + 3 + 2 +
+
+ + +
+
+ 2 + 2 + 3 +
+
+ + +
+
+ 3 + 1 + 2 +
+
+
+
+ 3 + 2 + 2 +
+
+
+ +
+ +
119 exercises/multiplying_a_matrix_by_a_matrix_video_hint.html
 @@ -0,0 +1,119 @@ + + + + + Multiplying a matrix by a matrix + + + +
+ +
+ DIM_2 + makeMatrix(randRange(-2, 5, DIM_1, DIM_2)) + makeMatrix(randRange(-2, 5, DIM_3, DIM_4)) + + + matrixMult(MAT_1, MAT_2) + + matrixPad(SOLN_MAT, 3, 3) + + "\\textbf " + randFromArray("ABCDEF") + "\\textbf " + randFromArray("ABCDEF") + + + [GREEN, BLUE, GRAY] + [ORANGE, "#DF0030", "#9D38BD"] + + + makeMultHintMatrix(MAT_1, MAT_2, ROW_COLORS, COL_COLORS) + + +
+ +
+

+ PRETTY_MAT_1_ID = printSimpleMatrix(MAT_1) +

+

+ PRETTY_MAT_2_ID = printSimpleMatrix(MAT_2) +

+
+ +

+ What is PRETTY_MAT_1_ID + PRETTY_MAT_2_ID? +

+ +
+
+
+
+
+

The solution is:

+
+ + printSimpleMatrix(SOLN_MAT) + +
+
+
+ +
+
+ + + elem + + + elem + + +
+
+ +
+ +
+
+ 2 + 2 + 2 +
+
+
+
+ 2 + 3 + 2 +
+
+ + +
+
+ 2 + 2 + 3 +
+
+ + +
+
+ 3 + 1 + 2 +
+
+
+
+ 3 + 2 + 2 +
+
+
+ +
+ +
40 exercises/multiplying_a_matrix_by_a_vector.html
 @@ -1,19 +1,9 @@ - + Multiplying a matrix by a vector -
@@ -27,6 +17,7 @@ matrixMult(MAT_1, MAT_2) + matrixPad(SOLN_MAT, 3, 3) "\\textbf " + randFromArray("ABCDEF") "\\textbf " + randFromArray("vw") @@ -43,8 +34,10 @@

- Let PRETTY_MAT_1_ID = printSimpleMatrix(MAT_1) and - PRETTY_MAT_2_ID = printSimpleMatrix(MAT_2). + PRETTY_MAT_1_ID = printSimpleMatrix(MAT_1) +

+

+ PRETTY_MAT_2_ID = printSimpleMatrix(MAT_2)

@@ -126,11 +119,16 @@
-

- - elem +

+ + + elem + + + elem + -

+
@@ -156,14 +154,6 @@ 2
- - -
-
- 4 - 2 -
-
26 exercises/scalar_matrix_multiplication.html
 @@ -1,19 +1,9 @@ - + Scalar matrix multiplication -
@@ -28,6 +18,7 @@ return a * SCALAR; }, MAT_A) + matrixPad(SOLN_MAT, 3, 3) BLUE GREEN @@ -74,11 +65,16 @@
-

- - elem +

+ + + elem + + + elem + -

+
8 khan-exercise.js
 @@ -56,6 +56,9 @@ * updateUserExercise -- when an updated userExercise has been received and is being used by khan-exercises, either via the result of an API call or initialization + + * showGuess -- when a guess is populated in the answer area in problem + history mode */ var Khan = (function() { @@ -318,7 +321,8 @@ var Khan = (function() { "simplify": ["math-model", "ast", "expr-helpers", "expr-normal-form", "steps-helpers"], "congruency": ["angles", "interactive"], "graphie-3d": ["graphie", "matrix"], - "graphie-geometry": ["graphie", "matrix"] + "graphie-geometry": ["graphie", "matrix"], + "matrix-input": ["jquery.cursor-position"] }, warnTimeout: function() { @@ -1661,6 +1665,8 @@ var Khan = (function() { } else { answerData.showGuess(); } + // fire the "show guess" event + $(Khan).trigger("showGuess"); // TODO: still highlight even if hint modifies problem (and highlight following hints) if (slideNum > 0 && (thisState.hintNum > statelist[slideNum - 1].hintNum)) { 28 utils/jquery.cursor-position.js  @@ -0,0 +1,28 @@ +(function ($) { + // from http://stackoverflow.com/questions/1891444/how-can-i-get-cursor-position-in-a-textarea?rq=1 + $.fn.getCursorPosition = function() { + var el =$(this).get(0); + var pos = 0; + if ("selectionStart" in el) { + pos = el.selectionStart; + } else if ("selection" in document) { + el.focus(); + var sel = document.selection.createRange(); + var selLength = document.selection.createRange().text.length; + sel.moveStart("character", -el.value.length); + pos = sel.text.length - selLength; + } + return pos; + }; + + $.fn.isCursorFirst = function() { + var pos =$(this).getCursorPosition(); + return pos === 0; + }; + + $.fn.isCursorLast = function() { + var pos =$(this).getCursorPosition(); + var last = $(this).val().length; + return pos === last; + }; +})(jQuery); 320 utils/matrix-input.js  @@ -0,0 +1,320 @@ +/** + * Allows for intuitive matrix input for matrix exercises. + * + * See matrix_transpose.html for an example. + * + * To use in an exercise: + * + * 1. Add "matrix matrix-input" to data-require. + * + * 2. Use matrixPad() to pad the solution matrix with empty string values + * and assign to a var named PADDED_SOLN_MAT: + * + * Ex: matrixPad(SOLN_MAT, 3, 3) + * + * 3. Use the following HTML for the + * solution markup: + * + * + * + * + * + * elem + * + * + * elem + * + * + * + * + * + */ + +$.extend(KhanUtil, { + + matrixInput: { + + containerEl: null, + bracketEls: null, + cells: null, + + LEFT_ARROW: 37, + UP_ARROW: 38, + RIGHT_ARROW: 39, + DOWN_ARROW: 40, + ENTER_KEY: 13, + + ROWS: 3, + COLS: 3, + + maxRow: 0, + maxCol: 0, + + contentMaxRow: 0, + contentMaxCol: 0, + + init: function() { + var self = this; + + this.initContainer(); + + var inputs = $(".matrix-input .sol input[type='text']"); + this.cells = _.map(inputs, function(input, i) { + return { + el: input, + index: i, + row: self.indexToRow(i), + col: self.indexToCol(i), + val: function() { + return$.trim($(this.el).val()); + }, + clearVal: function() { +$(this.el).val(""); + } + }; + }); + + this.addBrackets(); + + this.bindInputEvents(); + this.resetAllMaxVals(); + this.render(); + }, + + initContainer: function() { + this.containerEl = $("#solutionarea").addClass("matrix-input"); + }, + + addBrackets: function(i) { + var left =$("
").addClass("matrix-bracket bracket-left"); + var right = $(" ").addClass("matrix-bracket bracket-right"); + this.containerEl.append(left).append(right); + this.bracketEls = [left, right]; + }, + + indexToRow: function(i) { + return Math.floor(i / this.COLS); + }, + + indexToCol: function(i) { + return i % this.COLS; + }, + + coordToIndex: function(row, col) { + return this.COLS * row + col; + }, + + bindInputEvents: function() { + // We reevaluate the highlighted area after: + // 1) clicking on some element besides the cells, or + // 2) tabbing to a new cell in the solution area + // This is sufficient since these are the only ways + // the user will get to change the value. + var self = this; + + // case #1 +$("body").click(function() { + self.resetMaxToContentMax(); + self.render(); + }); + + _.each(this.cells, function(cell) { + + $(cell.el).on({ + // case #2 + focus: function(e) { + self.setMaxVals(cell); + self.render(); + }, + + blur: function(e) { + self.setMaxVals(cell); + }, + + // case #1 (prevent from performing a redundant + // reevaluation when clicking on a cell, since focus event + // is triggered on both tabs and clicks) + click: function(e) { + e.stopPropagation(); + }, + + keydown: function(e) { + var LAST_ROW = self.ROWS - 1; + var LAST_INDEX = self.cells.length - 1; + + var nextIndex = null; + var nextRow; + + // cursor position only does something when you + // are at the start of the input, moving left, or + // at the end of the input, moving right + + if (e.which === self.LEFT_ARROW) { + // don't do anything if at the first cell + // or if the cursor is not at the start + if (cell.index === 0 || !$(this).isCursorFirst()) { + return; + } + nextIndex = cell.index - 1; + + } else if (e.which === self.RIGHT_ARROW) { + // don't do anything if at the last cell + // or if the cursor is not at the end of the input + // text + if (cell.index === LAST_INDEX || + !$(this).isCursorLast()) { + return; + } + nextIndex = cell.index + 1; + + } else if (e.which === self.UP_ARROW) { + // if already on first row, don't do anything + if (cell.row === 0) { + return; + } + nextRow = cell.row - 1; + nextIndex = self.coordToIndex(nextRow, cell.col); + + } else if (e.which === self.DOWN_ARROW) { + // if on last row, don't do anything + if (cell.row === LAST_ROW) { + return; + } + nextRow = cell.row + 1; + nextIndex = self.coordToIndex(nextRow, cell.col); + + // when submitting via enter key, make sure max vals + // are set properly + } else if (e.which === self.ENTER_KEY) { + self.setMaxVals(cell); + } + + // let default behavior take place if we don't do + // anything + if (nextIndex === null) { + return; + } + + // change focus to next input +$(self.cells[nextIndex].el).focus(); + + // don't let event bubble + e.preventDefault(); + } + }); + }); + }, + + setContentMaxRow: function(val) { + this.contentMaxRow = Math.max(val, this.contentMaxRow); + }, + + setContentMaxCol: function(val) { + this.contentMaxCol = Math.max(val, this.contentMaxCol); + }, + + // maxRow/maxCol is the max of the currently selected element and the + // content max element + setMaxRow: function(val) { + this.maxRow = Math.max(val, this.contentMaxRow); + }, + + setMaxCol: function(val) { + this.maxCol = Math.max(val, this.contentMaxCol); + }, + + resetMaxToContentMax: function() { + this.maxRow = this.contentMaxRow; + this.maxCol = this.contentMaxCol; + }, + + resetAllMaxVals: function() { + this.maxRow = 0; + this.maxCol = 0; + this.contentMaxRow = 0; + this.contentMaxCol = 0; + }, + + setMaxValsFromScratch: function() { + // initialize to 0, since we want to start from scratch + this.resetAllMaxVals(); + + var self = this; + _.each(this.cells, function(cell) { + if (cell.val()) { + self.setContentMaxRow(cell.row); + self.setContentMaxCol(cell.col); + } + }); + + this.resetMaxToContentMax(); + }, + + setMaxVals: function(cell) { + var el = $(cell.el); + var val = cell.val(); + + // cell is nonempty + if (val) { + // only nonempty cell can be used to set content max values + // unless (see case below) + this.setContentMaxRow(cell.row); + this.setContentMaxCol(cell.col); + + // cell is empty + } else { + // reset the contents of the cell when it's just spaces + cell.clearVal(); + + // if it was the cell responsible for a content max val(s), + // we need to find the new content max val(s)... + if (this.contentMaxRow === cell.row || + this.contentMaxCol === cell.col) { + + this.setMaxValsFromScratch(); + } + } + + // both nonempty and empty cells can set absolute max values + this.setMaxRow(cell.row); + this.setMaxCol(cell.col); + }, + + // position matrix brackets based on bounds + positionBrackets: function() { + + var cell =$(this.cells[0].el); + var bracketWidth = this.bracketEls[0].width(); + + var rows = this.maxRow + 1; + var cols = this.maxCol + 1; + + var height = cell.outerHeight(true) * rows; + var marginLeft = cell.outerWidth(true) * cols - bracketWidth; + + _.each(this.bracketEls, function($el) { +$el.css({ + "height": height + }); + }); + + // right bracket + this.bracketEls[1].css({ + "margin-left": marginLeft + }); + }, + + render: function() { + this.positionBrackets(); + } + } +}); + +$(Khan).on("newProblem", function() { + KhanUtil.matrixInput.init(); +}); + +$(Khan).on("showGuess", function() { + KhanUtil.matrixInput.setMaxValsFromScratch(); + KhanUtil.matrixInput.render(); +});
231 utils/matrix.js
 @@ -13,6 +13,10 @@ $.extend(KhanUtil, { } }, + matrixCopy: function(mat) { + return jQuery.extend(true, [], mat); + }, + /** * Apply the given function to each element of the given matrix and return * the resulting matrix. @@ -160,15 +164,18 @@$.extend(KhanUtil, { // add matrix properties to a 2d matrix // currently only rows and columns - makeMatrix: function(m) { - m.r = m.length; - m.c = m[0].length; + makeMatrix: function(mat) { + mat.r = mat.length; + mat.c = mat[0].length; - return m; + return mat; }, // multiply two matrices matrixMult: function(a, b) { + a = KhanUtil.makeMatrix(a); + b = KhanUtil.makeMatrix(b); + var c = []; // create the new matrix _.times(a.r, function() { @@ -190,6 +197,219 @@ $.extend(KhanUtil, { return KhanUtil.makeMatrix(c); }, + /** + * Find the transpose of a matrix. + * + * @param m {result of makeMatrix} the matrix + */ + matrixTranspose: function(mat) { + mat = KhanUtil.makeMatrix(mat); + + var r = mat.c; + var c = mat.r; + + if (!r || !c) { + return undefined; + } + + var matT = []; + + _.times(r, function(i) { + var row = []; + _.times(c, function(j) { + row.push(mat[j][i]); + }); + matT.push(row); + }); + + return KhanUtil.makeMatrix(matT); + }, + + /** + * Find the determinant of a matrix. + * + * Note: Only works for 2x2 and 3x3 matrices. + * + * @param m {result of makeMatrix} the matrix + */ + matrixDet: function(mat) { + mat = KhanUtil.makeMatrix(mat); + + // determinant is only defined for a square matrix + if (mat.r !== mat.c) { + return undefined; + } + + var a, b, c, d, e, f, g, h, k, det; + + // 2x2 case + // [[a, b], [c, d]] + if (mat.r === 2) { + + a = mat[0][0]; + b = mat[0][1]; + c = mat[1][0]; + d = mat[1][1]; + + det = a*d - b*c; + + // 3x3 case + // [[a, b, c], [d, e, f], [g, h, k]] + } else if (mat.r === 3) { + + a = mat[0][0]; + b = mat[0][1]; + c = mat[0][2]; + d = mat[1][0]; + e = mat[1][1]; + f = mat[1][2]; + g = mat[2][0]; + h = mat[2][1]; + k = mat[2][2]; + + det = a*(e*k - f*h) - b*(k*d - f*g) + c*(d*h - e*g); + } + + return det; + }, + + /** + * Find the adjugate of a matrix. + * + * Note: Only works for 2x2 and 3x3 matrices. + * + * @param m {result of makeMatrix} the matrix + */ + matrixAdj: function(mat) { + mat = KhanUtil.makeMatrix(mat); + + var a, b, c, d, e, f, g, h, k; + var adj; + + // 2x2 case + // [[a, b], [c, d]] + if (mat.r === 2) { + + a = mat[0][0]; + b = mat[0][1]; + c = mat[1][0]; + d = mat[1][1]; + + adj = [[d, -b], [-c, a]]; + + // 3x3 case + // [[a, b, c], [d, e, f], [g, h, k]] + } else if (mat.r === 3) { + + a = mat[0][0]; + b = mat[0][1]; + c = mat[0][2]; + d = mat[1][0]; + e = mat[1][1]; + f = mat[1][2]; + g = mat[2][0]; + h = mat[2][1]; + k = mat[2][2]; + + var A = (e*k - f*h); + var B = -(d*k - f*g); + var C = (d*h - e*g); + var D = -(b*k - c*h); + var E = (a*k - c*g); + var F = -(a*h - b*g); + var G = (b*f - c*e); + var H = -(a*f - c*d); + var K = (a*e - b*d); + + adj = [[A, D, G], [B, E, H], [C, F, K]]; + } + + if (adj) { + adj = KhanUtil.makeMatrix(adj); + } + + return adj; + }, + + /** + * Find the inverse of a matrix. + * + * Note: Only works for 2x2 and 3x3 matrices. + * + * @param m {result of makeMatrix} the matrix + * @param precision {int} number of decimal places to round to (optional) + */ + matrixInverse: function(mat, precision) { + var det = KhanUtil.matrixDet(mat); + + // if determinant is undefined or 0, inverse does not exist + if (!det) { + return undefined; + } + + var adj = KhanUtil.matrixAdj(mat); + + if (!adj) { + return undefined; + } + + var inv = KhanUtil.deepZipWith(2, function(val) { + val = val / det; + if (precision) { + val = KhanUtil.roundTo(precision, val); + } + return val; + }, adj); + + inv = KhanUtil.makeMatrix(inv); + + return inv; + }, + + /** + * Pad (or crop) the given matrix with the given padding value (padval) + * until it is of dimensions rows x cols + * @param {result of makeMatrix} m + * @param {int} rows + * @param {int} cols + * @param {anything} padVal [defaults to "" if not specified] + * @return {result of makeMatrix} + */ + matrixPad: function(mat, rows, cols, padVal) { + mat = KhanUtil.makeMatrix(mat); + matP = KhanUtil.matrixCopy(mat); + + finalCols = Math.max(cols, mat.c); + + if (padVal === undefined) { + padVal = ""; + } + + // first add padding to the columns + var dcols = cols - matP.c; + if (dcols > 0) { + _.times(matP.r, function(i) { + _.times(dcols, function() { + matP[i].push(padVal); + }); + }); + } + + // make new rows and fill with padding + var drows = rows - matP.r; + if (drows > 0) { + _.times(drows, function() { + var row = []; + _.times(finalCols, function() { + row.push(padVal); + }); + matP.push(row); + }); + } + + return KhanUtil.makeMatrix(matP); + }, + // convert an array to a column matrix arrayToColumn: function(arr) { var col = []; @@ -223,4 +443,5 @@$.extend(KhanUtil, { vectorDot: function(a, b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; } -}); + +});
34 utils/tmpl.js
 @@ -101,7 +101,41 @@ $.tmpl = { "data-unwrap": function(elem) { return$(elem).contents(); + }, + + "data-video-hint": function(elem) { + var youtubeIds = $(elem).data("youtube-id"); + if (!youtubeIds) { + return; + } + + youtubeIds = youtubeIds.split(/,\s*/); + + var author =$(elem).data("video-hint-author") || "Sal"; + var msg = "Watch " + author + + " work through a very similar problem:"; + var preface = $(" ").text(msg); + + var wrapper =$("

", { "class": "video-hint" }); + wrapper.append(preface); + + _.each(youtubeIds, function(youtubeId) { + var href = "http://www.khanacademy.org/embed_video?v=" + + youtubeId; + var iframe = \$("