Give a man a fish and you feed him for a day. Teach him how to fish and you feed him for his life time.
In this very spirit, I will not share code, but show you how you can make your own.
Here are the ingredients:
a = (1,2,3,4,5,6,7,8,9,10) # a tuple
b = [1,2,3] # a list
c = ['Text or numbers, or bools. Any type, really.', 42, True, a, b]
d = [a,b,c] # nested list
display(d) # display produces nicer output in notebooks than print
[(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), [1, 2, 3], ['Text or numbers, or bools. Any type, really.', 42, True, (1, 2, 3, 4, 5, 6, 7, 8, 9, 10), [1, 2, 3]]]
e = {1,2,3,3,3,3,3} # a set
display(e)
{1, 2, 3}
# remember a = (1,2,3,4,5,6,7,8,9,10)
[x^2 for x in a]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
question_fragments = (
('4+2', '6'),
('2+5', '7'),
('8-3', '5'),
('8-5', '3'),
)
[f'What is {question}?' for question, answer in question_fragments]
['What is 4+2?', 'What is 2+5?', 'What is 8-3?', 'What is 8-5?']
answers = {answer for question, answer in question_fragments} # extract answers
def mca(answers, answer):
'format multiple choice answers as text, highlight the correct answer'
answers = list(set(answers)) # copy with unique elements
shuffle(answers) # this is in-place
answers = answers[:4] # the the first four
answers += ['None of the others']
def correct(a):
if a == answer:
return '*'
else:
return ' '
return "\n".join(f' {correct(a)} {chr(65+i)}) {a}' for i,a in enumerate(answers))
print(mca(answers,'7'))
A) 5 B) 3 * C) 7 D) 6 E) None of the others
question_db = [f'''What is {question}?\n{mca(answers,answer)}''' for question, answer in question_fragments]
question_db
['What is 4+2?\n A) 7\n B) 5\n C) 3\n * D) 6\n E) None of the others', 'What is 2+5?\n * A) 7\n B) 3\n C) 6\n D) 5\n E) None of the others', 'What is 8-3?\n * A) 5\n B) 3\n C) 7\n D) 6\n E) None of the others', 'What is 8-5?\n A) 6\n B) 7\n C) 5\n * D) 3\n E) None of the others']
[q.split('\n') for q in question_db]
[['What is 4+2?', ' A) 7', ' B) 5', ' C) 3', ' * D) 6', ' E) None of the others'], ['What is 2+5?', ' * A) 7', ' B) 3', ' C) 6', ' D) 5', ' E) None of the others'], ['What is 8-3?', ' * A) 5', ' B) 3', ' C) 7', ' D) 6', ' E) None of the others'], ['What is 8-5?', ' A) 6', ' B) 7', ' C) 5', ' * D) 3', ' E) None of the others']]
import pandas as pd
df = pd.DataFrame([q.split('\n') for q in question_db])
df
0 | 1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|---|
0 | What is 4+2? | A) 7 | B) 5 | C) 3 | * D) 6 | E) None of the others |
1 | What is 2+5? | * A) 7 | B) 3 | C) 6 | D) 5 | E) None of the others |
2 | What is 8-3? | * A) 5 | B) 3 | C) 7 | D) 6 | E) None of the others |
3 | What is 8-5? | A) 6 | B) 7 | C) 5 | * D) 3 | E) None of the others |
df.to_csv('my_question_database.tsv', sep='\t') # TAB separated output file
! cat my_question_database.tsv # look at the contents of the output file
0 1 2 3 4 5 0 What is 4+2? A) 7 B) 5 C) 3 * D) 6 E) None of the others 1 What is 2+5? * A) 7 B) 3 C) 6 D) 5 E) None of the others 2 What is 8-3? * A) 5 B) 3 C) 7 D) 6 E) None of the others 3 What is 8-5? A) 6 B) 7 C) 5 * D) 3 E) None of the others
Of course SageMath can do more exciting things like the following:
x = var('x')
p(x) = sum(randint(-10,10)*x^k for k in range(10))
p
x |--> 8*x^9 + 10*x^8 + 3*x^7 - 10*x^6 - 3*x^5 + 8*x^4 - 7*x^3 + 9*x^2 + 9*x - 5
show(p)
show(diff(p,x))
SageMath will typeset formulas with show(<sage expression>)
. But sometimes we want to typese more complicated $\LaTeX$ constructs for previewing purposes.
class MATHJAX():
'take a HTML formated string with embedded latex and typeset it'
def __init__(self,s):
self.s = s
def _repr_html_(self):
'this is used by display() / show() in your jupyter notebook'
return '''
<div>
<script type="text/javascript" async="" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML"></script>
</div>
''' + self.s
def __str__(self):
'this is used by print() and str()'
return self._repr_html_()
# example
display(MATHJAX(r'Hello, <i>world</i>. \[ \int_a^b f(x) dx. \]'))
print(MATHJAX(r'Hello, <i>world</i>. \[ \int_a^b f(x) dx. \]'))
<div> <script type="text/javascript" async="" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML"></script> </div> Hello, <i>world</i>. \[ \int_a^b f(x) dx. \]
We can also access the $\LaTeX$ representation of a SageMath expression as a string via latex(<sage expression>)
.
latex(p(x)) # remember out polynomial?
8 \, x^{9} + 10 \, x^{8} + 3 \, x^{7} - 10 \, x^{6} - 3 \, x^{5} + 8 \, x^{4} - 7 \, x^{3} + 9 \, x^{2} + 9 \, x - 5
remember generator expressions?
[x^2 for x in a]
[f'''What is {question}?\n{mca(answers,answer)}''' for question, answer in question_fragments]
[q.split('\n') for q in question_db]
Here's another one
[1/(k+1) for k in range(10)]
# [1, 1/2, 1/3, 1/4, 1/5, 1/6, 1/7, 1/8, 1/9, 1/10]
def quiz_1_elementary_products():
for _ in range(10):
a, b = randint(1,9), randint(2,10)
yield rf'What is ${a}\cdot {b}$?', f'{a*b}', f'{a+b}', f'{a-b}', f'{a/b}'
quiz_1_elementary_products()
<generator object quiz_1_elementary_products at 0x25d20aad0>
question_1_bank = [q for q in quiz_1_elementary_products()]
question_1_bank
[('What is $4\\cdot 10$?', '40', '14', '-6', '0.4'), ('What is $3\\cdot 7$?', '21', '10', '-4', '0.42857142857142855'), ('What is $2\\cdot 5$?', '10', '7', '-3', '0.4'), ('What is $2\\cdot 4$?', '8', '6', '-2', '0.5'), ('What is $6\\cdot 4$?', '24', '10', '2', '1.5'), ('What is $9\\cdot 10$?', '90', '19', '-1', '0.9'), ('What is $4\\cdot 6$?', '24', '10', '-2', '0.6666666666666666'), ('What is $2\\cdot 10$?', '20', '12', '-8', '0.2'), ('What is $1\\cdot 7$?', '7', '8', '-6', '0.14285714285714285'), ('What is $5\\cdot 3$?', '15', '8', '2', '1.6666666666666667')]
We need a formatter for such lists of questions and four answers.
formatted_questions = [f'{q}\n\n{mca(answers,answers[0])}'
for q, *answers in question_1_bank]
formatted_questions
['What is $4\\cdot 10$?\n\n * A) 40\n B) 0.4\n C) 14\n D) -6\n E) None of the others', 'What is $3\\cdot 7$?\n\n A) 0.42857142857142855\n B) -4\n * C) 21\n D) 10\n E) None of the others', 'What is $2\\cdot 5$?\n\n A) 7\n B) -3\n * C) 10\n D) 0.4\n E) None of the others', 'What is $2\\cdot 4$?\n\n A) 0.5\n * B) 8\n C) -2\n D) 6\n E) None of the others', 'What is $6\\cdot 4$?\n\n A) 10\n B) 1.5\n * C) 24\n D) 2\n E) None of the others', 'What is $9\\cdot 10$?\n\n A) 0.9\n * B) 90\n C) 19\n D) -1\n E) None of the others', 'What is $4\\cdot 6$?\n\n * A) 24\n B) -2\n C) 10\n D) 0.6666666666666666\n E) None of the others', 'What is $2\\cdot 10$?\n\n A) -8\n B) 12\n * C) 20\n D) 0.2\n E) None of the others', 'What is $1\\cdot 7$?\n\n A) -6\n B) 0.14285714285714285\n C) 8\n * D) 7\n E) None of the others', 'What is $5\\cdot 3$?\n\n * A) 15\n B) 8\n C) 2\n D) 1.6666666666666667\n E) None of the others']
for fq in formatted_questions[:2]:
print(fq,end='\n\n\n')
What is $4\cdot 10$? * A) 40 B) 0.4 C) 14 D) -6 E) None of the others What is $3\cdot 7$? A) 0.42857142857142855 B) -4 * C) 21 D) 10 E) None of the others
Now put this into a function:
def my_question_printer(question_list):
'take a list of the form (q,a1,a2,a3,a4) and pretty-print it'
formatted_questions = [f'{q}\n\n{mca(answers,answers[0])}'
for q, *answers in question_list]
for fq in formatted_questions[:2]:
print(fq,end='\n\n\n')
my_question_printer(question_1_bank)
What is $4\cdot 10$? A) 0.4 B) 14 * C) 40 D) -6 E) None of the others What is $3\cdot 7$? A) -4 B) 10 * C) 21 D) 0.42857142857142855 E) None of the others
Consider again this code, and imagin we are working on it, debugging it.
def quiz_1_elementary_products():
for _ in range(10):
a, b = randint(1,9), randint(2,10)
yield rf'What is ${a}\cdot {b}$?', f'{a*b}', f'{a+b}', f'{a-b}', f'{a/b}'
@with_preview
def quiz_1_elementary_products():
for _ in range(10):
a, b = randint(1,9), randint(2,10)
yield rf'What is ${a}\cdot {b}$?', f'{a*b}', f'{a+b}', f'{a-b}', f'{a/b}'
What is $4\cdot 10$? A) -6 B) 0.4 * C) 40 D) 14 E) None of the others What is $5\cdot 9$? A) -4 * B) 45 C) 0.5555555555555556 D) 14 E) None of the others
@with_preview
is called a decorator. How does it work you ask?
def with_preview(f):
'"decorate" the generator f by pretty-printing a few of its elements'
example_of_our_generator = f()
import itertools
my_question_printer(itertools.islice(example_of_our_generator,2))
return f
That's (almost) all folks. Let's recap.
Create your own decorators that
Some references you may find useful:
this presentation:
MATHxxxx, a static assessment generator
a video recording of this presentation