Skip to content

Instantly share code, notes, and snippets.

@samfearn
Last active April 24, 2019 23:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samfearn/dc911f365ddb2384f19e6db12413aa99 to your computer and use it in GitHub Desktop.
Save samfearn/dc911f365ddb2384f19e6db12413aa99 to your computer and use it in GitHub Desktop.
Enable the Stack rand functions to be used in a local copy of Maxima
/* Author Chris Sangwin
Loughborough University
Copyright (C) 2014 Chris Sangwin
University of Edinburgh
Copyright (C) 2017 Chris Sangwin
This program is free software: you can redistribute it or modify
it under the terms of the GNU General Public License version two.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* ********************************** */
/* Global variable options */
/* ********************************** */
simplify(ex) := ev(fullratsimp(ex), simp); /* Allows simplify to be something. */
degree(ex,v) := ev(hipow(expand(ex), v), simp); /* See notes on hipow. */
/* ****************************************************** */
/* Random numbers */
/* ****************************************************** */
/* http://random.mat.sbg.ac.at/generators/ */
/* ****************************************************** */
/* Developer warning: random functions determining */
/* whether a question is a singleton. */
/* When adding new "random" functions, also update */
/* question->has_random_variants() */
/* ****************************************************** */
/* Change the random seed */
stack_randseed(s) := block(RANDOM_STATE:make_random_state(s), errcatch(ev(set_random_state(RANDOM_STATE), simp)))$
/* The top level function */
rand(ex) := block(
ex:ev(ex, simp),
if (integerp(ex)) then return(random(ex)),
if (floatnump(ex)) then return(random(ex)),
if (matrixp(ex)) then return(matrixmap(random, ex)),
if (listp(ex)) then return(randlist(ex))
)$
/* Allow zero as an argument to random. */
rand_zero(ex):= block(
if not(integerp(ex)) then error("rand_zero expects its argument to be an integer."),
if is(ex<0) then error("rand_zero expects its argument to be non-negative."),
if is(ex=0) then return(0),
return(rand(ex))
)$
randlist(ex) := block(
if (length(ex) > 0) then return(ex[ev(1+random(length(ex)),simp)]) else return([])
)$
/* Returns a random number from the set {lower, lower+step, lower+2*step, ... , final}. */
/* Jarno Ruokokoski, 29/10/2009 */
rand_with_step(lower, upper, step_parameter) := block([temprand],
temprand: rand(floor((upper-lower)/step_parameter)+1),
return(ev(step_parameter*temprand+lower, simp))
)$
/* Returns a random integer from the set [lower,upper] such that it cannot be any value in list. This list can include values which are also random variables, for example, generated by rand_with_step. */
/* Jarno Ruokokoski, 29/10/2009 */
rand_with_prohib(lower, upper, list) := block([currents, retVal, kloop],
currents: ev((makelist(i, i, lower, upper)), simp),
for kloop:1 thru length(list) do block(
currents: simplify(delete(list[ev(kloop, simp)], currents))
),
retVal: rand(currents),
return(retVal)
)$
/* Make a random selection of n different items from the list ex. */
/* CJS, 7/6/2016 */
rand_selection(ex, n) := block(
if not(listp(ex)) then (
print("rand_selection error: first argument must be a list."),
return([])
),
if not(integerp(n)) then (
print("rand_selection error: second argument must be an integer."),
return([])
),
if is(n>length(ex)) then (
print("rand_selection error: insuffient elements in the list."),
return([])
),
return(rand_selection_fun(ex, n))
)$
rand_selection_fun(exin, n) := block([k],
if is(n=0) then return([]),
k: ev(rand(length(exin))+1, simp),
cons(exin[k], rand_selection_fun(list_remove(exin, k), ev(n-1, simp)))
)$
/* Remove the n'th element from the list ex. */
list_remove(ex, n) := block([k, l],
if is(n>length(ex)) or is (n<1) then return(ex),
/* Using simplification make a list of indices, then without simplification use them. */
l: ev(append(makelist(k, k, 1, n-1), makelist(k, k, n+1, length(ex))), simp),
makelist(ex[k], k, l)
)$
/* Create a number in a random range. */
rand_range([ex]) := block(
if (length(ex)<2 or length(ex)>3) then error("rand_range must have 2 or 3 arguments."),
if not(integerp(ex[1])) then error("rand_range expects its first argument to be an integer."),
if not(integerp(ex[2])) then error("rand_range expects its second argument to be an integer."),
if is(length(ex)=2) then return(ev(ex[1]+rand_zero(ex[2]-ex[1]), simp)),
if not(integerp(ex[3])) then error("rand_range expects its third argument to be an integer."),
return(ev(ex[1]+ex[3]*rand_zero(floor((ex[2]-ex[1])/ex[3])), simp))
)$
/* Helper function for constructing MCQ arrays. */
multiselqn(corbase, numcor, wrongbase, numwrong):=block([ta1, ta2, ta, version],
if not(listp(corbase)) then error("multiselqn: first argument must be a list."),
if not(listp(wrongbase)) then error("multiselqn: third argument must be a list."),
if not(integerp(numcor)) then error("multiselqn: second argument must be an integer."),
if not(integerp(numwrong)) then error("multiselqn: fourth argument must be an integer."),
if length(corbase)<numcor then error("multiselqn: you have asked for more correct responses than are supplied in the list!"),
if length(wrongbase)<numwrong then error("multiselqn: you have asked for more correct responses than are supplied in the list!"),
ta1: maplist(lambda([ex], [ex, true]), rand_selection(corbase, numcor)),
ta2: maplist(lambda([ex], [ex, false]), rand_selection(wrongbase, numwrong)),
ta: random_permutation(append(ta1, ta2)),
version: map(first, ta),
return([ta, version])
)$
/* Helper function for constructing MCQ arrays with auto-generated alphabetic labels. Students choose the labels. */
multiselqnalpha([exs]):=block([corbase, numcor, wrongbase, numwrong, dispflag, ta1, ta2, ta3, talab, ta, version],
if length(exs)<4 then error("multiselqnalpha must have at least four arguments."),
corbase:first(exs),
numcor:second(exs),
wrongbase:third(exs),
numwrong:fourth(exs),
dispflag:"id",
if length(exs)>4 then dispflag:fifth(exs),
if not(listp(corbase)) then error("multiselqnalpha: first argument must be a list."),
if not(listp(wrongbase)) then error("multiselqnalpha: third argument must be a list."),
if not(integerp(numcor)) then error("multiselqnalpha: second argument must be an integer."),
if not(integerp(numwrong)) then error("multiselqnalpha: fourth argument must be an integer."),
if length(corbase)<numcor then error("multiselqnalpha: you have asked for more correct responses than are supplied in the list!"),
if length(wrongbase)<numwrong then error("multiselqnalpha: you have asked for more correct responses than are supplied in the list!"),
ta1: maplist(lambda([ex], [ex, true]), rand_selection(corbase, numcor)),
ta2: maplist(lambda([ex], [ex, false]), rand_selection(wrongbase, numwrong)),
ta3: random_permutation(append(ta1, ta2)),
/* Add in a slightly different display here. */
talab: ev(makelist(sconcat("(",ascii(96+i),")"), i, 1, length(ta3)), simp),
ta:zip_with(lambda([ex1, ex2], [ex1, ex2[2], sconcat("<b>", ex1, "</b> ", stack_disp(ex2[1], dispflag))]), talab, ta3),
version: map(first, ta3),
return([ta, version])
)$
/* Helper function for constructing MCQ arrays where the values should not be shown to students. */
multiselqndisplay(corbase, numcor, wrongbase, numwrong):=block([ta1, ta2, ta, version],
if not(listp(corbase)) then error("multiselqndisplay: first argument must be a list."),
if not(listp(wrongbase)) then error("multiselqndisplay: third argument must be a list."),
if not(integerp(numcor)) then error("multiselqndisplay: second argument must be an integer."),
if not(integerp(numwrong)) then error("multiselqndisplay: fourth argument must be an integer."),
if length(corbase)<numcor then error("multiselqndisplay: you have asked for more correct responses than are supplied in the list!"),
if length(wrongbase)<numwrong then error("multiselqndisplay: you have asked for more correct responses than are supplied in the list!"),
/* */
corbase: zip_with("[", ev(makelist(k,k,1,length(corbase)),simp), corbase),
wrongbase: zip_with("[", ev(makelist(k,k,1+length(corbase),1+length(corbase)+length(wrongbase)),simp), wrongbase),
ta1: maplist(lambda([ex], [first(ex), true, second(ex)]), rand_selection(corbase, numcor)),
ta2: maplist(lambda([ex], [first(ex), false, second(ex)]), rand_selection(wrongbase, numwrong)),
ta: random_permutation(append(ta1, ta2)),
version: map(first, ta),
/* */
return([ta, version])
)$
/* Helper functions for MCQ arrays. */
mcq_correct(ta):=block(
if not(listp(ta)) then error("mcq_correct: first argument must be a list, but was passed: ", string(ta)),
if not(all_listp(listp, ta)) then error("mcq_correct: all list elements must be lists, but was passed: ", string(ta)),
if not(all_listp(lambda([ex], is(length(ex)>=2)), ta)) then error("mcq_correct: all list elements must be lists of length at least 2, but was passed: ", string(ta)),
maplist(first, sublist(ta, lambda([ex], second(ex))))
)$
mcq_incorrect(ta):=block(
if not(listp(ta)) then error("mcq_incorrect: first argument must be a list, but was passed: ", string(ta)),
if not(all_listp(listp, ta)) then error("mcq_incorrect: all list elements must be lists, but was passed: ", string(ta)),
if not(all_listp(lambda([ex], is(length(ex)>=2)), ta)) then error("mcq_incorrect: all list elements must be lists of length at least 2, but was passed: ", string(ta)),
maplist(first, sublist(ta, lambda([ex], not(second(ex)))))
)$
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment