Как стать автором
Обновить

LaTex: Упражнения продолжаются или как «замучать» своих детей

Наткнувшись недавно на топик LaTex: Упражнение я решил, что мне такое тоже надо. Но поскольку с Groovy разбираться мне не очень хотелось, а добавить действия в дополнение к сложению надо, я решил переписать скрипт с нуля.
Вот что у меня получилось.

Начну я с краткого пояснения. Программка состоит из двух частей: шаблона на LaTeX-е и Perl-скрипта, наполняющего шаблон смыслом (т.е. числами). В текущем виде он работает в полу-ручном режиме — из Латех-файла PDF делаю руками (что в общем-то не сложно доделать); а разделил её на сам скрипт и шаблон для удобства добавления/изменения если что. Теперь скрипт…:

#!/usr/bin/perl
use feature qw(switch say);
use strict;
use Getopt::Std;
use Math::Random;
use Text::Iconv;
use locale;

# Ключи запуска
# -c количество примеров
# -r количество разрядов
# -a addition - сложение
# -s subtraction - вычитание
# -m multiplication - умножение
# -d division - деление
#

my $pash_tex = '/home/user/math/'; # Путь к временной папке LaTeX

our %options=();
if (not getopts("e:r:asmd",\%options)) {
print STDERR "Внимание! Использованы неизвестные опции запуска - они проигнорированы!\n(Возможно, Вы ввели опции русскими символами)\n";
};

if (! (($options{a}) || ($options{s}) || ($options{m}) || ($options{d}))) {
print STDERR "Необходимо указать хотя бы одно действие: +(-a),-(-s),*(-m),/(-d)\n";
exit;
}
my $ranks; my $examples;
if (!$options{r}) { $ranks = 3 } # Кол-во разрядов
else { $ranks = $options{r} };
if (!$options{e}) { $examples = 40 } # Кол-во примеров
else { $examples = int(($options{e}+3)/4)*4 };
my $max_num = 10**$ranks;

my @ques_ans; # Массив вопросов и ответов

my $choice_action;
my $action;
for my $i (0..$examples-1) {
$choice_action = 0;
do {
$action = int(rand 4);
if ((($options{a}) && ($action == 0)) || (($options{s}) && ($action == 1)) ||
(($options{m}) && ($action == 2)) || (($options{d}) && ($action == 3)))
{ $choice_action = 1; };
} until ($choice_action == 1);
# print $action,"\n";
if ($action == 0) { # Сложение
$ques_ans[$i][3] = '+';
my $fi = int(rand $max_num);
my $se = int(rand $max_num);
if ($fi > $se) { $ques_ans[$i][2] = $fi; $ques_ans[$i][0] = $se;
} else { $ques_ans[$i][2] = $se; $ques_ans[$i][0] = $fi; }
$ques_ans[$i][1] = $ques_ans[$i][2] - $ques_ans[$i][0];
} elsif ($action == 1) { # Вычитание
$ques_ans[$i][3] = '-';
my $fi = int(rand $max_num);
my $se = int(rand $max_num);
if ($fi > $se) { $ques_ans[$i][0] = $fi; $ques_ans[$i][1] = $se;
} else { $ques_ans[$i][0] = $se; $ques_ans[$i][1] = $fi; }
$ques_ans[$i][2] = $ques_ans[$i][0] - $ques_ans[$i][1];
} elsif (($action == 2) || ($action == 3)) { # Умножение
$ques_ans[$i][3] = 'x';
$choice_action = 0;
do {
my $fi = int(rand $max_num/3-2)+2;
my $se = int(rand $max_num/3-2)+2;
my $re;
$fi>$se ? ($re = $fi/$se) : ($re = $se/$fi);
if (($re < 50) && ( !( (($fi<10) || ($se<10)) && ($fi*$se>100)) || rand(5) )) {
if ($fi*$se<$max_num*1.2 ) {
$ques_ans[$i][0] = $fi;
$ques_ans[$i][1] = $se;
$ques_ans[$i][2] = $ques_ans[$i][0] * $ques_ans[$i][1];
$choice_action = 1;
};
};
} until ($choice_action == 1);
if ($action == 3) {
$ques_ans[$i][3] = '÷';
($ques_ans[$i][2],$ques_ans[$i][0]) = ($ques_ans[$i][0],$ques_ans[$i][2])
};
};
}

my $latex_out_file_name = $pash_tex."result.tex";

open LAYOUT, "<", $pash_tex."make_math.tex";
open LATEX, ">", $latex_out_file_name;

my @loop_str_prod=(); # массив строк в цикле примеров
my $mark_of_loop = 0; # маркер цикла примеров

while () { # Цикл считывания файла ШАБЛОНА .tex
chomp;
if ($mark_of_loop) { # в цикле считывания строчек примера - запоминать строки и искать конец одного примера
if (m/^%#%\*/) { # если строка - команда
my $ins_cmd = (split /:/)[1]; # найти имя команды
if ($ins_cmd eq "end_one_cell") { # если последняя строка вывода ячейки - выводим примеры
for my $all_rows (1..$examples/4){
for my $all_cells (1..4){
my $current_example = (($all_rows-1)*4+$all_cells-1); # номер текущего примера
my $interval_resalt = 30; # интервал по умолчанию для сложения и вычитания
if ($ques_ans[$current_example][3] eq 'x') {
$interval_resalt = $interval_resalt + 20 * ((length($ques_ans[$current_example][1])-1) );
} elsif ($ques_ans[$current_example][3] eq '÷') {
$interval_resalt = $interval_resalt + 35 * ((length($ques_ans[$current_example][2])-1) );
}
if ($ques_ans[$current_example][3] ne '÷') {
for my $i (0..$#loop_str_prod) { # для каждой tex-строки..
if ($loop_str_prod[$i] =~ m/^%#%\*/) { # это команда?
my $ins_cmd = (split /:/ , $loop_str_prod[$i])[1];
if ($ins_cmd eq "number_question") { # Вставляем операцию
print LATEX ($current_example+1),")";
} elsif ($ins_cmd eq "operation") { # Вставляем операцию number_question
print LATEX $ques_ans[$current_example][3];
} elsif ($ins_cmd eq "first_arg") {
print LATEX $ques_ans[$current_example][0];
} elsif ($ins_cmd eq "second_arg") {
print LATEX $ques_ans[$current_example][1];
} elsif ($ins_cmd eq "interval_resalt") {
print LATEX $interval_resalt;
}
# конец обработки команды interval_resalt
} else { # если не команда просто вывести строку в файл
say LATEX $loop_str_prod[$i];
}
} # Конец вывода одной ячейки примера (при +,-,*)
} else {
say LATEX "\\makebox[5mm][l]{", ($current_example+1),") }\n\\makebox[40mm][c]{\n\$\$\$\n\\begin{array}{r}\n\\begin{tabular}{r|l}\n\\texttt{\\Large",$ques_ans[$current_example][0],"} &\n\\texttt{\\Large", $ques_ans[$current_example][1],"}\\\\\n\\cline{2-2}\n\& \\rule{0pt}{",$interval_resalt,"pt} \\rule{",(length($ques_ans[$current_example][2])*10),"pt}{0pt}\\\\ \n \\end{tabular}\n\\end{array}\n\$\$\$\n}";
}
if ($all_cells == 4){
say LATEX '\\\\ \\hline';
} else {
say LATEX '&';
}
} # Конец вывода строки
} # Конец вывода всех примеров
$mark_of_loop = 0;
} else {
push @loop_str_prod, $_;
} # Конец вывода в tex-файл примеров
} else { # если строка - не команда, то просто считать
push @loop_str_prod, $_;
}
} elsif (m/^%#%\*/) { # не в цикле и это команда, то обрабатываем команду
my $ins_cmd = (split /:/)[1];
if ($ins_cmd eq "begin_one_cell") {
$mark_of_loop = 1;
} elsif ($ins_cmd eq "answer_page") {
for my $i (1..$examples) {
print LATEX $i,")~",$ques_ans[$i-1][2],"; ";
}
}
} else {
say LATEX $_;
}
} # конец цикла считывания файла ШАБЛОНА .tex и формирования выходного tex-файла

close LAYOUT;
close LATEX;

exit;

... и шаблон (сохранить в make_math.tex для соответствия скрипту):

\documentclass[a4paper,12pt,oneside]{article}
\usepackage{multirow}
\usepackage{makecell}
\usepackage[utf8]{inputenc}
\usepackage[english,russian]{babel}
\usepackage{longtable}
% Page layout (geometry)
\setlength\voffset{-1.3in} %{-15.4mm}
\setlength\hoffset{-1.3in}
\setlength\oddsidemargin{2cm}
\setlength\textheight{267mm}
\setlength\textwidth{180mm}
\setlength\marginparwidth{8mm}
\setlength\marginparsep{2mm}
\setlength\footskip{1.0cm}

\begin{document}
%\pagestyle{empty}
\begin{longtable}{p{40mm}|p{40mm}|p{40mm}|p{40mm}}

%#%*:begin_one_cell: ==== начало одной ячейки ====
\makebox[5mm][l]{
%#%*:number_question: ==== номер примера ====
}
\makebox[40mm][r]{
$$$
\begin{array}{r}
\texttt{\Large
%#%*:operation: ==== действие ====
}
\begin{array}{r}
\texttt{\Large
%#%*:first_arg: ==== первый аргумент ====
}\\
\texttt{\Large
%#%*:second_arg: ==== второй аргумент ====
}\\
\end{array} \\
\hline
\begin{array}{r}
\rule{0pt}{
%#%*:interval_resalt: ==== интервал для результата ====
pt}
\end{array}
\end{array}
$$$
}
%#%*:end_one_cell: ==== конец одной ячейки ====
% после каждой ячейки в программе нужно добавлять "&" и после строки (4-ре ячейки) - "\\"
\end{longtable}

\newpage

%#%*:answer_page: ==== страница ответов ====

\end{document}


Как водится привел суть программки, опустив, возможно, необходимые проверки. Теперь по логике работы в двух словах.
Результат выводится строками по 4-ре примера в каждой на формате A4. Задается в командной строке кол-во разрядов и кол-во примеров (которое округляется до кратного 4-ке числа), выбираются случайным образом операнды и дальше идет процесс вывода в латех-файл. Но в первой части я так и не сделал нормальное формирование операндов при умножении/делении - не нравится оно мне как-то (оно то работает правильно, но вот насколько равномерно-случайный выбор идет - это вопрос).
Теперь о выводе в латеховский файл. Думаю понятно что вывожу меняющиеся параметры (операнды и действие) в цикле, добавляя строки между "начало ячейки" и "конец ячейки". А вот втиснуть в эту схему "деление" не удалось, уж очень у него отличающийся внешний вид - поэтому пришлось втиснуть латеховский код при делении в сам скрипт.
Результат получается такой:
image

Собственно все, пользуйтесь на радость детям, высказывайте замечания и предложения.

P.S. как оформить код с отступами для правильночитаемости я так и не понял, пробелы и табуляции игнорировались - прошу простить.
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.