Привет!
В этой «статье», а вернее сказать очерке, покажу очень простой способ развлечься зная самые основы latex и python.
![](https://habrastorage.org/r/w780q1/webt/f1/-j/h2/f1-jh2nzd35gg2r3mv3rpfsvi9u.jpeg)
Ну, можно генерировать простые выражения для детей чтобы считали. Или просто так. Да хоть на обои поставить, если вы такой же фанатик, как и я.
Идея действительно очень простая, написать такую программу может абсолютно каждый. Мы хотим сгенерировать выражение, равное некоторому числу n (которое вводит пользователь). Любое число можно заменить на арифметическое выражение, например, 3 = 1 + 2. А 2 это 4 / 2. Вот так мы сгенерировали 3 = 1 + 4/2. Аналогично, мы введем несколько разных операций и завернем это в LaTeX, язык формул.
Нам нужно распарсить выражение так, чтобы вытащить оттуда числа. Назовем наш класс как генератор проблем (нам всем его так не хватает!)
Смысл функции extract_nums в том, чтобы получить n пар чисел (a, b), где a — позиция первого символа, b — позиция последнего + 1.
Например, если мы запустим следующий код:
Увидим:
То есть это массив tuple. (0, 2) означает, что есть число между 0 (включительно) и 2 (не включительно).
Теперь нам хотелось бы сделать разные операторы, начнем с умножения и суммы. Объявим три функции
Суть функции unmin не только в том, чтобы просто преобразовать все аргументы в строки, но и в том, чтобы заключить в скобки какой-то из операндов, если он меньше нуля. К примеру, мы получили числа a=3, b=-4. Если мы напишем
То a=«3», b="(-4)"
Ну а остальные функции понятные: __c_sum возвращает строку вида «13 + 4», а __c_mul «13 * 4».
Остается соединить эти две штуки и заменять каждое число в выражении на выражение.
Добавим в ProblemGenerator следующий код:
complexify принимает какое-то число, а возвращает строку — усложненное выражение. Например, если напишем:
Получим:
Как работает __rxp__? Мы выбираем позицию случайно числа из выражения (к примеру, если есть выражение «13+35/45», то допустим мы выбрали (3, 5)) и заменяем это число на выражение, равное этому числу. То есть хотелось бы:
«13+35/45» — рандомное число (3, 5)
«13+» + "(12 + 23)" + "/45"
«13+(12+23)/45»
Так и работает __rxp__
Ну а randexpr работает совсем просто. Например, если у нас четыре шага, то раскрывать выражение будет так:
Попробуем запустить:
Результат:
Как ни странно, осталось самое простое. Объявим целый ряд разных операторов LaTeX:
Добавим все функции в gen:
И наконец добавим вывод результата:
Вот и всё.
В этой «статье», а вернее сказать очерке, покажу очень простой способ развлечься зная самые основы latex и python.
![](https://habrastorage.org/webt/f1/-j/h2/f1-jh2nzd35gg2r3mv3rpfsvi9u.jpeg)
Зачем?
Ну, можно генерировать простые выражения для детей чтобы считали. Или просто так. Да хоть на обои поставить, если вы такой же фанатик, как и я.
Как это по идее должно работать?
Идея действительно очень простая, написать такую программу может абсолютно каждый. Мы хотим сгенерировать выражение, равное некоторому числу n (которое вводит пользователь). Любое число можно заменить на арифметическое выражение, например, 3 = 1 + 2. А 2 это 4 / 2. Вот так мы сгенерировали 3 = 1 + 4/2. Аналогично, мы введем несколько разных операций и завернем это в LaTeX, язык формул.
Вам понадобится...
Одна неделя опыта в python и matplotlib. Я серьезно.
Основной механизм
Нам нужно распарсить выражение так, чтобы вытащить оттуда числа. Назовем наш класс как генератор проблем (нам всем его так не хватает!)
import random
from math import log
import math
import sys
sys.setrecursionlimit(1000) # Эта магия делает нерабочий код рабочим
class ProblemGenerator:
def extract_nums(self, exp):
symbols = list(exp)
NUM = "1234567890."
for i in range(len(symbols)):
symbols[i] = "N" if symbols[i] in NUM else "T"
begins = []
ends = []
for i in range(len(symbols) - 1):
fn = symbols[i] + symbols[i + 1]
if fn == "TN":
begins.append(i)
elif fn == "NT":
ends.append(i)
if exp[-1] in NUM:
ends.append(len(exp) - 1)
if exp[0] in NUM:
begins = [-1] + begins
return [(x + 1, y + 1) for x, y in zip(begins, ends)]
Смысл функции extract_nums в том, чтобы получить n пар чисел (a, b), где a — позиция первого символа, b — позиция последнего + 1.
Например, если мы запустим следующий код:
gen = ProblemGenerator()
print(gen.extract_nums("13+256/355+25"))
Увидим:
[(0, 2), (3, 6), (7, 10), (11, 13)]
То есть это массив tuple. (0, 2) означает, что есть число между 0 (включительно) и 2 (не включительно).
Теперь нам хотелось бы сделать разные операторы, начнем с умножения и суммы. Объявим три функции
def unmin(*args, acc=2):
r = []
for arg in args:
f = round(arg, acc)
if f > 0:
f = str(f)
else:
f = "(" + str(f) + ")"
r.append(f)
return r
def __c_sum(num):
a = round(random.random() * 100, 3)
b = num - a
a, b = unmin(a, b)
return a + " + " + b
def __c_mul(num):
a = num / (random.random() * 100 + 10)
if a == 0.0:
b = random.random()
else:
b = num / a
a, b = unmin(a, b)
return a + " * " + b
Суть функции unmin не только в том, чтобы просто преобразовать все аргументы в строки, но и в том, чтобы заключить в скобки какой-то из операндов, если он меньше нуля. К примеру, мы получили числа a=3, b=-4. Если мы напишем
a = 3
b = -4
a, b = unmin(a, b)
То a=«3», b="(-4)"
Ну а остальные функции понятные: __c_sum возвращает строку вида «13 + 4», а __c_mul «13 * 4».
Остается соединить эти две штуки и заменять каждое число в выражении на выражение.
Добавим в ProblemGenerator следующий код:
class ProblemGenerator:
...
def __init__(self):
self.funcs = []
def add_expander(self, func):
self.funcs.append(func)
def complexify(self, num):
return random.choice(self.funcs)(num)
def __rxp__(self, exp):
x, y = random.choice(self.extract_nums(exp))
exp = exp[:x] + "(" + self.complexify(float(exp[x:y])) + ")" + exp[y:]
return exp
def randexpr(self, ans, steps):
e = str(ans)
for i in range(steps):
e = self.__rxp__(e)
return e
complexify принимает какое-то число, а возвращает строку — усложненное выражение. Например, если напишем:
gen = ProblemGenerator()
gen.add_expander(__c_sum)
print(gen.complexify(13))
Получим:
31.2 + (-18.2)
Как работает __rxp__? Мы выбираем позицию случайно числа из выражения (к примеру, если есть выражение «13+35/45», то допустим мы выбрали (3, 5)) и заменяем это число на выражение, равное этому числу. То есть хотелось бы:
«13+35/45» — рандомное число (3, 5)
«13+» + "(12 + 23)" + "/45"
«13+(12+23)/45»
Так и работает __rxp__
Ну а randexpr работает совсем просто. Например, если у нас четыре шага, то раскрывать выражение будет так:
13
(5.62 + 7.38)
((20.63 + (-15.01)) + 7.38)
((20.63 + (-(67.5 + (-52.49)))) + 7.38)
((20.63 + (-((15.16 + 52.34) + (-52.49)))) + 7.38)
Попробуем запустить:
gen = ProblemGenerator()
gen.add_expander(__c_sum)
gen.add_expander(__c_mul)
exp = gen.randexpr(1, 5)
print(exp)
Результат:
((6.63 + (56.62 + 16.8)) + (-((60.53 + 3.61) + 14.91)))
LaTeX
Как ни странно, осталось самое простое. Объявим целый ряд разных операторов LaTeX:
def __l_sum(num):
a = 100 ** (random.random() * 2)
b = num - a
a, b = unmin(a, b)
return a + " + " + b
def __l_div(num):
a = num * (random.random() * 100 + 10)
if a == 0.0:
b = random.random()
else:
b = a / num
a, b = unmin(a, b)
return "\\frac{" + a + "}{" + b + "}"
def __l_pow(num):
if num == 0:
return str(random.randint(2, 7)) + "^{-\\infty}"
a = random.randint(0, 10) + 3
b = math.log(abs(num), a)
a, b = unmin(a, b)
return ("-" if num < 0 else "") + a + "^{" + b + "}"
def __l_sqrt(num):
a = num ** 0.5
a = unmin(a)[0]
return "\\sqrt{" + a + "}"
def __l_int(num):
patterns = [
("x^{2}", (3 * num) ** (1/3), "dx"),
("y^{3}", (4 * num) ** (1/4), "dy"),
("\sqrt{t}", (1.5 * num) ** (2/3), "dt")
]
p, b, f = random.choice(patterns)
b = str(round(b, 3))
return "\\int_{0}^{" + b + "} " + p + " " + f
def __l_sig(num):
a = random.randint(1, 10)
b = random.randint(1, 10) + a
s = sum([i for i in range(a, b + 1)])
c = num / s
a, b, c = unmin(a, b, c)
return "\\sum_{i=" + a + "}^{" + b + "} i*" + c
Добавим все функции в gen:
gen = ProblemGenerator()
gen.add_expander(__l_sum) # Сумма двух чисел
gen.add_expander(__l_div) # Дробь
gen.add_expander(__l_pow) # Степень
gen.add_expander(__l_sqrt) # Квадратный корень
gen.add_expander(__l_int) # Определенный интеграл
gen.add_expander(__l_sig) # Оператор сигма
И наконец добавим вывод результата:
import matplotlib.pyplot as plt
plt.axis("off")
latex_expression = gen.randexpr(1, 30) # 30 раз заменяем. Выражение будет равно 1
plt.text(0.5, 0.5, "$" + latex_expression + "$", horizontalalignment='center', verticalalignment='center', fontsize=20)
plt.show()
Вот и всё.
Весь код
import random
from math import log
import math
import sys
sys.setrecursionlimit(1000)
class ProblemGenerator:
def extract_nums(self, exp):
symbols = list(exp)
NUM = "1234567890."
for i in range(len(symbols)):
symbols[i] = "N" if symbols[i] in NUM else "T"
begins = []
ends = []
for i in range(len(symbols) - 1):
fn = symbols[i] + symbols[i + 1]
if fn == "TN":
begins.append(i)
elif fn == "NT":
ends.append(i)
if exp[-1] in NUM:
ends.append(len(exp) - 1)
if exp[0] in NUM:
begins = [-1] + begins
return [(x + 1, y + 1) for x, y in zip(begins, ends)]
def __init__(self):
self.funcs = []
def add_expander(self, func):
self.funcs.append(func)
def complexify(self, num):
return random.choice(self.funcs)(num)
def __rxp__(self, exp):
x, y = random.choice(self.extract_nums(exp))
exp = exp[:x] + "(" + self.complexify(float(exp[x:y])) + ")" + exp[y:]
return exp
def randexpr(self, ans, steps):
e = str(ans)
for i in range(steps):
e = self.__rxp__(e)
return e
def unmin(*args, acc=2):
r = []
for arg in args:
f = round(arg, acc)
if f > 0:
f = str(f)
else:
f = "(" + str(f) + ")"
r.append(f)
return r
def __c_sum(num):
a = round(random.random() * 100, 3)
b = num - a
a, b = unmin(a, b)
return a + " + " + b
def __c_mul(num):
a = num / (random.random() * 100 + 10)
if a == 0.0:
b = random.random()
else:
b = num / a
a, b = unmin(a, b, acc=5)
return a + " * " + b
def __c_sub(num):
a = num + 100 ** (random.random() * 2)
b = (a - num)
a, b = unmin(a, b)
return a + " - " + b
def __c_log(num):
fr = random.randint(300, 500)
a = math.e ** (num / fr)
a, fr = unmin(a, fr, acc=5)
return "log(" + a + ") * " + fr
def __l_sum(num):
a = 100 ** (random.random() * 2)
b = num - a
a, b = unmin(a, b)
return a + " + " + b
def __l_div(num):
a = num * (random.random() * 100 + 10)
if a == 0.0:
b = random.random()
else:
b = a / num
a, b = unmin(a, b)
return "\\frac{" + a + "}{" + b + "}"
def __l_pow(num):
if num == 0:
return str(random.randint(2, 7)) + "^{-\\infty}"
a = random.randint(0, 10) + 3
b = math.log(abs(num), a)
a, b = unmin(a, b)
return ("-" if num < 0 else "") + a + "^{" + b + "}"
def __l_sqrt(num):
a = num ** 0.5
a = unmin(a)[0]
return "\\sqrt{" + a + "}"
def __l_int(num):
patterns = [
("x^{2}", (3 * num) ** (1/3), "dx"),
("y^{3}", (4 * num) ** (1/4), "dy"),
("\sqrt{t}", (1.5 * num) ** (2/3), "dt")
]
p, b, f = random.choice(patterns)
b = str(round(b, 3))
return "\\int_{0}^{" + b + "} " + p + " " + f
def __l_sig(num):
a = random.randint(1, 10)
b = random.randint(1, 10) + a
s = sum([i for i in range(a, b + 1)])
c = num / s
a, b, c = unmin(a, b, c)
return "\\sum_{i=" + a + "}^{" + b + "} i*" + c
gen = ProblemGenerator()
gen.add_expander(__l_sum)
gen.add_expander(__l_div)
gen.add_expander(__l_pow)
gen.add_expander(__l_sqrt)
gen.add_expander(__l_int)
gen.add_expander(__l_sig)
import matplotlib.pyplot as plt
plt.axis("off")
latex_expression = gen.randexpr(1, 30) # 30 раз заменяем. Выражение будет равно 1
plt.text(0.5, 0.5, "$" + latex_expression + "$", horizontalalignment='center', verticalalignment='center', fontsize=15)
plt.show()
Результат (3 скриншота)![](https://habrastorage.org/r/w780q1/webt/hj/rd/-m/hjrd-matww-zx0rpwrsk_geioc8.jpeg)
![](https://habrastorage.org/r/w780q1/webt/zx/8l/1d/zx8l1dmiv82imgkdihrajacadgi.jpeg)
![](https://habrastorage.org/r/w780q1/webt/dk/wh/ix/dkwhix8voabixv2yjmmgsz59g0s.jpeg)
![](https://habrastorage.org/webt/hj/rd/-m/hjrd-matww-zx0rpwrsk_geioc8.jpeg)
![](https://habrastorage.org/webt/zx/8l/1d/zx8l1dmiv82imgkdihrajacadgi.jpeg)
![](https://habrastorage.org/webt/dk/wh/ix/dkwhix8voabixv2yjmmgsz59g0s.jpeg)
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Продолжать писать «фановые очерки» вроде этого?
74.7% Да62
13.25% Нет11
12.05% НЛО прилетело и опубликовало эту надпись здесь10
Проголосовали 83 пользователя. Воздержались 14 пользователей.