Skip to content

Commit

Permalink
Add Division Support (#34)
Browse files Browse the repository at this point in the history
* add division support

* some clean up
  • Loading branch information
januschung authored Oct 21, 2020
1 parent ccfc0bc commit d690add
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 27 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ There are four choices:
1. Addition
2. Subtraction
3. Multiplication
4. Mixed
4. Division
5. Mixed

## Requirements
[python3](https://www.python.org/downloads/)
Expand All @@ -34,7 +35,7 @@ pip install -r requirements.txt
## How to Use
1. Generate the worksheet in pdf format with the following command:
```
python3 run.py --type [+|-|x|mix] --digits [1|2|3] [-q|--question_count] [int] --output [custom-name.pdf]
python3 run.py --type [+|-|x|/|mix] --digits [1|2|3] [-q|--question_count] [int] --output [custom-name.pdf]
```
2. Print out the generated file `worksheet.pdf`

Expand Down Expand Up @@ -70,7 +71,6 @@ I appreciate all suggestions or PRs which will help kids learn math better. Feel

## TODO
1. Add date/name/score section to the front page
2. Add support for division in long division format

## Special Thanks
My long time friend San for the inspiration of this project and lovely sons Tim and Hin. Thanks [thedanimal](https://github.com/thedanimal) for reviewing this README and adding new features.
Expand All @@ -92,4 +92,6 @@ Thank you for your coverage.

[Real Python Facebook](https://www.facebook.com/LearnRealPython/posts/1688239528018053?__tn__=-R)

[Github Trends Telegram](https://t.me/githubtrending/9007)
[Github Trends Telegram](https://t.me/githubtrending/9007)

[Python Trending Twitter](https://twitter.com/pythontrending/status/1316659466935373826)
Binary file added division.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
100 changes: 77 additions & 23 deletions run.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import argparse
import random
from fpdf import FPDF
from functools import reduce
from typing import List, Tuple

QuestionInfo = Tuple[int, str, int, int]
Expand All @@ -32,6 +33,23 @@ def __init__(self, type_: str, max_number: int, question_count: int):
self.font_1 = 'Times'
self.font_2 = 'Arial'

# From https://stackoverflow.com/questions/6800193/what-is-the-most-efficient-way-of-finding-all-the-factors-of-a
# -number-in-python
def factors(self, n: int):
return set(reduce(list.__add__,
([i, n//i] for i in range(1, int(n**0.5) + 1) if n % i == 0)))

def division_helper(self, num) -> [int, int, int]:
# prevent num = 0 or divisor = 1 or divisor = dividend
factor = 1
while not num or factor == 1 or factor == num:
num = random.randint(0, self.max_number)
# pick a factor of num; answer will always be an integer
if num:
factor = random.sample(self.factors(num), 1)[0]
answer = int(num / factor)
return [num, factor, answer]

def generate_question(self) -> QuestionInfo:
"""Generates each question and calculate the answer depending on the type_ in a list
To keep it simple, number is generated randomly within the range of 0 to 100
Expand All @@ -40,7 +58,7 @@ def generate_question(self) -> QuestionInfo:
num_1 = random.randint(0, self.max_number)
num_2 = random.randint(0, self.max_number)
if self.main_type == 'mix':
current_type = random.choice(['+', '-', 'x'])
current_type = random.choice(['+', '-', 'x', '/'])
else:
current_type = self.main_type

Expand All @@ -52,6 +70,9 @@ def generate_question(self) -> QuestionInfo:
answer = num_1 - num_2
elif current_type == 'x':
answer = num_1 * num_2
elif current_type == '/':
num_1, num_2, answer = self.division_helper(num_1)

else:
raise RuntimeError(f'Question main_type {current_type} not supported')
return num_1, current_type, num_2, answer
Expand Down Expand Up @@ -94,40 +115,63 @@ def split_arr(self, x: int, y: int):
quotient, remainder = divmod(x, y)
if remainder != 0:
return [y] * quotient + [remainder]
else:
return [y] * quotient

return [y] * quotient

def print_top_row(self, question_num: str):
"""Helper function to print first character row of a question row"""
self.pdf.set_font(self.font_1, size=self.middle_font_size)
self.pdf.cell(self.pad_size, self.pad_size, txt=question_num, border='LT', align='C')
self.pdf.cell(self.size, self.pad_size, border='T', align='C')
self.pdf.cell(self.size, self.pad_size, border='T', align='C')
self.pdf.cell(self.pad_size, self.pad_size, border='TR', align='C')
self.pdf.cell(self.size, self.pad_size, border='T')
self.pdf.cell(self.size, self.pad_size, border='T')
self.pdf.cell(self.pad_size, self.pad_size, border='TR')

def print_second_row(self, num: int):
"""Helper function to print second character row of a question row"""
self.pdf.set_font(self.font_2, size=self.large_font_size)
self.pdf.cell(self.pad_size, self.size, border='L', align='C')
self.pdf.cell(self.size, self.size, align='C')
self.pdf.cell(self.pad_size, self.size, border='L')
self.pdf.cell(self.size, self.size)
self.pdf.cell(self.size, self.size, txt=str(num), align='R')
self.pdf.cell(self.pad_size, self.size, border='R', align='C')
self.pdf.cell(self.pad_size, self.size, border='R')

def print_second_row_division(self, num_1: int, num_2: int):
"""Helper function to print second character row of a question row for division"""
self.pdf.set_font(self.font_2, size=self.large_font_size)
self.pdf.cell(self.pad_size, self.size, border='L')
self.pdf.cell(self.size, self.size, txt=str(num_2), align='R')
x_cor = self.pdf.get_x() - 3
y_cor = self.pdf.get_y()
self.pdf.image(name='division.png', x=x_cor, y=y_cor)
self.pdf.cell(self.size, self.size, txt=str(num_1), align='R')
self.pdf.cell(self.pad_size, self.size, border='R')

def print_third_row(self, num: int, current_type: str):
"""Helper function to print third character row of a question row"""
self.pdf.set_font(self.font_2, size=self.large_font_size)
self.pdf.cell(self.pad_size, self.size, border='L', align='C')
self.pdf.cell(self.pad_size, self.size, border='L')
self.pdf.cell(self.size, self.size, txt=current_type, align='L')
self.pdf.cell(self.size, self.size, txt=str(num), align='R')
self.pdf.cell(self.pad_size, self.size, border='R', align='C')
self.pdf.cell(self.pad_size, self.size, border='R')

def print_third_row_division(self):
"""Helper function to print third character row of a question row for division"""
self.pdf.cell(self.pad_size, self.size, border='L')
self.pdf.cell(self.size, self.size, align='L')
self.pdf.cell(self.size, self.size, align='R')
self.pdf.cell(self.pad_size, self.size, border='R')

def print_bottom_row(self):
"""Helper function to print bottom row of question"""
self.pdf.set_font(self.font_2, size=self.large_font_size)
self.pdf.cell(self.pad_size, self.size, border='LB', align='C')
self.pdf.cell(self.size, self.size, border='TB', align='C')
self.pdf.cell(self.size, self.size, border='TB', align='R')
self.pdf.cell(self.pad_size, self.size, border='BR', align='C')
self.pdf.cell(self.pad_size, self.size, border='LB')
self.pdf.cell(self.size, self.size, border='TB')
self.pdf.cell(self.size, self.size, border='TB')
self.pdf.cell(self.pad_size, self.size, border='BR')

def print_bottom_row_division(self):
"""Helper function to print bottom row of question"""
self.pdf.cell(self.pad_size, self.size, border='LB')
self.pdf.cell(self.size, self.size, border='B')
self.pdf.cell(self.size, self.size, border='B')
self.pdf.cell(self.pad_size, self.size, border='BR')

def print_edge_vertical_separator(self):
"""Print space between question for the top or bottom row"""
Expand All @@ -139,7 +183,7 @@ def print_middle_vertical_separator(self):

def print_horizontal_separator(self):
"""Print line breaker between two rows of questions"""
self.pdf.cell(self.size, self.size, align='C')
self.pdf.cell(self.size, self.size)
self.pdf.ln()

def print_question_row(self, data, offset, num_problems):
Expand All @@ -149,15 +193,24 @@ def print_question_row(self, data, offset, num_problems):
self.print_edge_vertical_separator()
self.pdf.ln()
for x in range(0, num_problems):
self.print_second_row(data[x + offset][0])
if data[x + offset][1] == '/':
self.print_second_row_division(data[x + offset][0], data[x + offset][2])
else:
self.print_second_row(data[x + offset][0])
self.print_middle_vertical_separator()
self.pdf.ln()
for x in range(0, num_problems):
self.print_third_row(data[x + offset][2], data[x + offset][1])
if data[x + offset][1] == '/':
self.print_third_row_division()
else:
self.print_third_row(data[x + offset][2], data[x + offset][1])
self.print_middle_vertical_separator()
self.pdf.ln()
for _ in range(0, num_problems):
self.print_bottom_row()
for x in range(0, num_problems):
if data[x + offset][1] == '/':
self.print_bottom_row_division()
else:
self.print_bottom_row()
self.print_edge_vertical_separator()
self.pdf.ln()

Expand Down Expand Up @@ -194,11 +247,12 @@ def main(type_, size, question_count, filename):
parser.add_argument(
'--type',
default='+',
choices=['+', '-', 'x', 'mix'],
choices=['+', '-', 'x', '/', 'mix'],
help='type of calculation: '
'+: Addition; '
'-: Subtraction; '
'x: Multiplication; '
'/: Division; '
'mix: Mixed; '
'(default: +)',
)
Expand Down
31 changes: 31 additions & 0 deletions tests/test_math_worksheet_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ def test_generate_question_multiplication(self):
question = g.generate_question()
self.assertEqual(question[0] * question[2], question[3])

def test_generate_question_division(self):
g = Mg(type_='/', max_number=9, question_count=10)
question = g.generate_question()
self.assertEqual(question[0] / question[2], question[3])

def test_generate_question_unsupport_type_(self):
g = Mg(type_='p', max_number=9, question_count=10)
with self.assertRaisesRegex(RuntimeError, expected_regex=r"Question main_type p not supported"):
Expand All @@ -40,6 +45,32 @@ def test_make_question_page_page_count(self):
total_page = math.ceil(g.question_count / (g.num_x_cell * g.num_y_cell))
self.assertEqual(total_page, g.pdf.page)

def test_factors_two_digits(self):
g = Mg(type_='x', max_number=9, question_count=2)
expect_factors = {1, 2, 4, 13, 26, 52}
self.assertEqual(expect_factors, g.factors(52))

def test_factors_three_digits(self):
g = Mg(type_='x', max_number=9, question_count=2)
expect_factors = {1, 2, 3, 4, 6, 12, 73, 146, 219, 292, 438, 876}
self.assertEqual(expect_factors, g.factors(876))

def test_division_helper_zero_input(self):
g = Mg(type_='x', max_number=9, question_count=2)
division_info = g.division_helper(0)
self.assertNotEqual(0, division_info[0])

def test_division_helper_divisor_not_equal_one_nor_dividend(self):
g = Mg(type_='x', max_number=9, question_count=2)
division_info = g.division_helper(876)
self.assertNotEqual(1, division_info[0])
self.assertNotEqual(division_info[2], division_info[0])

def test_division_helper_divisor_answer_type_is_int(self):
g = Mg(type_='x', max_number=9, question_count=2)
division_info = g.division_helper(876)
self.assertIs(type(division_info[2]), int)


if __name__ == '__main__':
unittest.main()

0 comments on commit d690add

Please sign in to comment.