Authoring drag and drop matching problems in STACK
The drag-and-drop functionality of the Parson's block in STACK can also be used to write matching and other grid-based problems, which can be answered by drag and drop.
This page provides a quick-start guide to authoring matching problems with the parsons
block.
As of STACK v4.6.0, the parsons
block has three main configurations (each of which support further customisation) which can be achieved by setting appropriate block header parameters columns
and rows
as appropriate:
1. Proof (Example usage: [[parsons]] ... [[/parsons]]
) : This was introduced in STACK v4.5.0, and a full guide can be found here.
2. Grouping (Example usage: [[parsons columns="3"]] ... [[/parsons]]
) :
This will set up a number of columns, each of which behave similarly to the single left-hand column of the Proof configuration, where the student may drag and drop items starting at the top of each column.
This is useful when we are only interesting in grouping items together, and specific row positions do not matter, or when each column may have variable length.
3. Grid (Example usage: [[parsons columns="3" rows="2"]] ... [[/parsons]]
) :
This will set up a grid of shape (columns, rows)
, where the student may drag and drop items to any position in the grid.
Note that many Grid style questions can also be written using the Grouping setup. The main difference between them is that Grid allows the student to drag any item to any position in the grid, regardless of whether the item above it has been filled; Grouping on the other hand only allows students to drag items to the end of the list within a column.
Troubleshooting
If your matching problem is not displaying properly, in particular if the all the items are displayed in a single yellow block, then double-check that you have spelled the keys of the JSON inside the Parsons block correctly as described below. They should be a subset of
{"steps", "options", "headers", "available_header", "index"}
and a superset of
{"steps"}
For technical reasons this is one error that we are unable to validate currently.
Switching orientation
Parsons blocks will display columns vertically by default.
The student has the option to flip the orientation so that the columns become horizontal rows, and back again, through a button on the question page.
If you wish the columns to be horizontal rows as the question display default, then simply add transpose="true"
to the block header (e.g., [[parsons columns="3" transpose="true"]] ... [[/parsons]]
, will load a question with 3 horizontal rows).
Providing a model answer
The format of the model answer is fixed and independent of the orientation discussed in the previous section. It should be defined in Question variables as a two-dimensional array that is always column-grouped. For example if a model answer looks like
a | b | c
---|---|---
d | | e
---|---|---
f | |
Then this should be defined as
ans: [["A", "D", "F"], ["B"], ["C", "E"]];
where, e.g., "A"
is the key identifier for string "a"
.
Cloning items
The clone
header parameter has been available in parsons
since v4.5.0 of STACK.
This can be set to "true"
to allow items to be used more than once (e.g., [[parsons columns="3" clone="true"]]...[[/parsons]]
).
Headers and index
Column headers default to 0
, 1
, ..., columns
when using Grouping or Grid configurations of the Parson's block, so we recommend to always set custom headers for these cases.
See the examples below for how to do this.
There is also the option of setting the index, which is possible in both Grouping and Grid configurations, but is more likely to be useful for the latter. This will appear as a specially styled left-most column and fixes labels for the rows. The index does not count as a column, so you should decrease columns by one in the header parameters. The item that appears top-left in both the index and header should be included in the index only. See the examples below for more details.
Item style
In Grouping and Grid configurations, the height and width of individual items will by default auto-resize so that all their heights and widths are set to contain the largest item content. This will also apply to headers and index items as well.
This can be overriden via the item-height
and item-width
header parameters. Parameter values should be a string containing a number, and this will set the pixels of the height/width.
The default is 50px
(item-height="50"
) for height, and the width is automatically deduced from the page layout and number of columns for the vertical orientation, or it is 100px
(item-width="100"
) for the horizontal configuration.
This may be needed if autosizing does not quite work as expected.
The matching library
The matchlib.mac
Maxima library contains a small number of functions that are required for basic functionality of assessing matching problems in STACK.
Essentially they translate the author answers to and back from JSON format expected by the drag-and-drop engine.
Be sure to include this and make use of it as detailed in the examples below.
We include some basic helper functions that can allow the author to specify whether they care or not about the order within and between rows or columns as follows.
- ans: [["A", "B"], ["C", "D"], ["E", "F"]]
- match_column_set
: I don't care about the order within a column ->
match_column_set(ans) = [{"A", "B"}, {"C", "D"}, {"E", "F"}]
match_row_set
: I don't care about the order within a row (unlikely to be required but included for completeness) ->
match_row_set(ans) = [{"A", "C", "E"}, {"B", "D", "F"}]
match_set_column
: I don't care about the order of the columns (unlikely to be required but included for completeness) ->
match_set_column(ans) = {["A", "B"], ["C", "D"], ["E", "F"]}
match_set_row
: I don't care about the order of the rows ->
match_set_rows(ans) = {["A", "C", "E"], ["B", "D", "F"]}
match_transpose
: I would like to turn my answer into a row-grouped array ->
match_transpose(ans) = [["A", "C", "E"], ["B", "D", "F"]]
Example 1 : Grouping example
In our first example, the student is asked to place functions, given as equations, into columns with the categories "Differentiable", "Continuous, not differentiable", "Discontinuous".
Question variables
As a minimum it is recommended to include:
- Load the matching library.
- Define all items in the available list as a two-dimensional array, where each item is an array of the form ["<ID>", "<actual item contents>"]
.
You will use the "<ID>"
string to write solutions and assess student inputs; the second item is what is displayed to the student.
- Randomly permute the available items.
- The headers that will appear on top of the answer columns.
- The correct answer as a two-dimensional array.
This should be column grouped.
For our example, the Question variables field looks as follows.
stack_include("contribl://matchlib.mac");
steps : [
["sq", "\\(f(x) = x^2\\)"],
["sin", "\\(f(x) = \\sin(x)\\)"],
["abs", "\\(f(x) = |x|\\)"],
["sqrt", "\\(f(x) = \\sqrt{|x|}\\)"],
["rec", "\\(f(x) = \\left\{\\begin{array}{ll}1/x &, x\\neq 0 \\\\ 0&, x=0\\end{array}\\right.\\)"],
["sgn", "\\(f(x) = \\text{sgn}(x)\\)"]
];
steps: random_permutation(steps);
headers: [
"Differentiable",
"Continuous, not differentiable",
"Discontinuous"
];
ans: [
["sq", "sin"],
["abs", "sqrt"],
["rec", "sgn"]
];
Question text
Here we should:
- Write the question text itself.
- Open the parsons
block with input
and columns
header parameters.
- Transfer the variables from Question variables into a JSON inside the parsons
block as appropriate.
- Close the parsons
block.
- Set style="display:none"
in the input div to hide the messy state from the student.
<p>Recall that a function may be differentiable, continuous but
not differentiable, or discontinuous. The following expressions define functions \(f:\mathbb{R}\rightarrow\mathbb{R}\).
Drag the functions to their appropriate category. </p>
[[parsons input="ans1" columns="3"]]
{
"steps" : {#stackjson_stringify(steps)#},
"headers" : {#headers#}
}
[[/parsons]]
<p style="display:none">[[input:ans1]] [[validation:ans1]]</p>
Question note
A question note is required due to the random permutation of steps
. We use:
{@map(first, steps)@}
Input: ans1
- The Input type field should be String.
- The Model answer field should construct a JSON object from the teacher's answer
ans
usingmatch_correct(ans, steps)
. - Set the option Student must verify to "no".
- Set the option Show the validation to "no".
- Add
hideanswer
to Extra options.
Steps 3, 4 and 5 are strongly recommended, otherwise the student will see unhelpful code representing the underlying state of their answer.
Potential response tree: prt1
Define the feedback variable
sans: match_interpret(ans1);
This provides the student response as a two-dimensional array of the same format as ans
.
At this point the author may choose to assess by comparing sans
and ans
as they see fit.
In this case, the order within a column really doesn't matter, but the order of the columns does of course.
So we may convert the columns of sans
and ans
to sets in feedback variables using match_column_set
from matchlib.mac
.
sans: match_column_set(sans);
ans: match_column_set(ans);
We can then do a regular algebraic equivalence test between sans
and ans
. You should turn the node to Quiet: Yes
, otherwise the student will see unhelpful code if they the answer wrong.
Example 2 : Grid example
Here, the student is asked to drag functions and their derivatives to relevant columns and rows. This particular example could work as a grouping example in the vein of Example 1 above, however the key difference here is that the student can drag an item to any position in the grid, whereas in grouping items can only be added to the end of a growing column list.
Much of this example is very similar to Example 1 above, with the following key differences:
- The parsons
block should include a specified rows
parameter.
- The match_correct
function should use true
as a third parameter inside Model answer.
- The match_interpret
function should use true
as a third parameter inside the PRT.
- We also define our PRT answer test differently, since we care only about the order within a row being preserved.
However this difference is not required and is due only to the nature of the question (i.e., what we want to assess from this question is
different from the one in Example 1), rather than from any system requirements.
Question variables
As a minimum it is recommended to include:
- Load the matching library.
- Define all items in the available list as a two-dimensional array, where each item is an array of the form ["<ID>", "<actual item contents>"]
.
You will use the "<ID>"
string to write solutions and assess student inputs; the second item is what is displayed to the student.
- Randomly permute the available items.
- The headers that will appear on top of the answer columns.
- The correct answer as a two-dimensional array. This should always be column grouped.
For our example, the Question variables field looks as follows.
stack_include("contribl://matchlib.mac");
steps : [
["f", "\\(y = x^2\\)"],
["g", "\\(y = x^3\\)"],
["dfdx", "\\(y' = 2x\\)"],
["dgdx", "\\(y' = 3x^2\\)"],
["df2d2x", "\\(y'' = 2\\)"],
["dg2d2x", "\\(y'' = 6x\\)"]
];
steps: random_permutation(steps);
headers: [
"Function",
"\\(d/dx\\)",
"\\(d^2/d^2x\\)"
];
ans: [
["f", "g"],
["dfdx", "dgdx"],
["df2d2x", "dg2d2x"]
];
Question text
Here we should:
- Write the question text itself.
- Open the parsons
block with input
, columns
and rows
header parameters.
- Transfer the variables from Question variables into a JSON inside the parsons
block as appropriate.
- Close the parsons
block.
- Set style="display:none"
in the input div to hide the messy state from the student.
<p>Drag the items to match up the functions with their derivatives. </p>
[[parsons input="ans1" columns="3" rows="2"]]
{
"steps" : {#stackjson_stringify(steps)#},
"headers" : {#headers#}
}
[[/parsons]]
<p style="display:none">[[input:ans1]] [[validation:ans1]]</p>
Question note
A question note is required due to the random permutation of steps
. We use:
{@map(first, steps)@}
Input: ans1
- The Input type field should be String.
- The Model answer field should construct a JSON object from the teacher's answer
ta
usingmatch_correct(ans, steps, true)
. - Set the option Student must verify to "no".
- Set the option Show the validation to "no".
- Add
hideanswer
to Extra options.
Steps 3, 4 and 5 are strongly recommended, otherwise the student will see unhelpful code representing the underlying state of their answer.
Potential response tree: prt1
Define the feedback variable
sans: match_interpret(ans1, true);
This provides the student response as a two-dimensional array of the same format as ans
.
At this point the author may choose to assess by comparing sans
and ans
as they see fit. In this case, the order of the rows themselves really doesn't matter, but the order of the rows does of course. So we may convert the list of rows of sans
and ans
to a set in feedback variables using match_set_row
from matchlib.mac
.
sans: match_set_row(sans);
ans: match_set_row(ans);
We can then do a regular algebraic equivalence test between sans
and ans
.
You should turn the node to Quiet: Yes
, otherwise the student will see unhelpful code if they the answer wrong.
Example 3 : Grid example with an index
One can add a left-hand index to the grid in Example 2 by defining an index
array in Question variables and passing this through in the JSON inside the parsons
block.
This will fix the row order and simplify assessment.
Important points:
- An item that appears in both the header and the index is required.
This item should appear in the index and not in the header.
- Reduce the value of the columns
parameter in the parsons
block by one: this corresponds only to the number of answer columns.
- Pass the index as the value of key "index"
inside the JSON within the parsons
block.
Question variables
The question variables for Example 2 with an index is as follows.
stack_include("contribl://matchlib.mac");
steps : [
["dfdx", "\\(y' = 2x\\)"],
["dgdx", "\\(y' = 3x^2\\)"],
["df2d2x", "\\(y'' = 2\\)"],
["dg2d2x", "\\(y'' = 6x\\)"]
];
steps: random_permutation(steps);
headers: [
"\\(d/dx\\)",
"\\(d^2/d^2x\\)"
];
index: [
"Function",
"\\(y = x^2\\)",
"\\(y = x^3\\)"
]
ans: [
["dfdx", "dgdx"],
["df2d2x", "dg2d2x"]
];
Question text
<p>Drag the items to match up the functions with their derivatives. </p>
[[parsons input="ans1" columns="2" rows="2"]]
{
"steps" : {#stackjson_stringify(steps)#},
"headers" : {#headers#},
"index" : {#index#}
}
[[/parsons]]
<p style="display:none">[[input:ans1]] [[validation:ans1]]</p>
Question note
A question note is required due to the random permutation of steps
. We use:
{@map(first, steps)@}
Input
This is exactly the same as Example 2.
- The Input type field should be String.
- The Model answer field should construct a JSON object from the teacher's answer
ta
usingmatch_correct(ans, steps, true)
. - Set the option Student must verify to "no".
- Set the option Show the validation to "no".
- Add
hideanswer
to Extra options.
PRT
As in Example 2, we first extract the two-dimensional array of used items from the students input.
sans: match_interpret(ans1, true);
At this point the author may choose to assess by comparing sans
and ans
as they see fit.
Since we have fixed the order of both dimensions, there is only one correct answer which is given by ans
.
Hence we have a basic PRT which tests only algebraic equivalence between sans
and ans
.
As always, turn the node to Quiet: Yes
, otherwise the student will see unhelpful code if they the answer wrong.
Example 4 : Using images
Through the use of STACK's plot
function, which wraps Maxima's plot2d
, static images can also be included within items.
Apart from modifying the content of the steps of Example 2, the key difference here is that the width and height of the items must also be specified in the block parameter, to make sure that plots fit inside.
This can be done by specifying the item-width
and item-height
parameters within the block header of parsons
.
Because of this, it is recommended to always specify the [size, x, y]
option within plot
, and add some padding to x
and y
to define the values of item-width
and item-height
.
Example 2 with plots rather than equations is given below.
Question variables
stack_include("contribl://matchlib.mac");
steps: [
["f", plot(x^2,[x,-1,1], [size, 200, 200])],
["g", plot(x^3,[x,-1,1], [size, 200, 200])],
["dfdx", plot(2*x,[x,-1,1], [size, 200, 200])],
["dgdx", plot(3*x^2,[x,-1,1], [size, 200, 200])],
["df2d2x", plot(2,[x,-1,1], [size, 200, 200])],
["dg2d2x", plot(6*x,[x,-1,1], [size, 200, 200])]
];
steps: random_permutation(steps);
headers: ["Function", "\\(d/dx\\)", "\\(d^2/d^2x\\)"];
ans: [
["f", "g"],
["dfdx", "dgdx"],
["df2d2x", "dg2d2x"]
];
Question text
<p>Drag the items to match up the functions with their derivatives. </p>
[[parsons input="ans1" columns="3" rows="2" item-height="250" item-width="250"]]
{
"steps" : {#stackjson_stringify(steps)#},
"headers" : {#headers#},
}
[[/parsons]]
<p style="display:none">[[input:ans1]] [[validation:ans1]]</p>
Question note, Inputs and PRT
These are exactly the same as Example 2.