PythonBrasil

segunda-feira, junho 16, 2008

Cria Prova - Um python script para gerar provas

Este é um programa para a criação automática de provas diferentes, com questões aleatórias, a partir de um banco de questões.

A idéia deste pequeno programa veio de uma conversa com um colega (Prof. Anibal, que já utiliza uma versão DOS para as suas provas).

O programa gera, a partir do banco de questões, tantas provas diferentes e com a quantidade de questões indicadas pelo usuário.

As questões devem estar em um arquivo texto com o seguinte layout:
Q: pergunta
A: resposta 1
A: resposta 2 [opcional]

Uma pergunta pode ter várias respostas (aspectos). Neste caso, a pergunta, para ter sentido, deve ser do tipo: "Dentre as características de XXXX, está" O programa selecionará aleatoriamente, para cada prova, uma das respostas da pergunta; poder-se-á ter então, provas que tenham uma pergunta com o mesmo enunciado mas com resposta diferente. Isto é bom! Dificulta a "cola".

As perguntas podem ser separadas por linhas em branco no arquivo de questões. Mas isto é opcional.

Atualmente, o programa gera um arquivo .tex com a prova (prova_NN.tex) e um arquivo gabarito com as respostas das provas (prova_gb.tex). Futuramente, outros formatos de saída deverão ser adicionados, bem como uma interface gráfica para facilitar o uso.

Cada prova é identificada por um código de barras que contém o gabarito. Veja no código fonte informações importantes sobre os identificadores.

No arquivo gabarito, as respostas são identificadas pelo mesmo código de barras. Veja no código fonte mais informações sobre os identificadores no gabarito.

Após gerados, os arquivos das provas podem ser editados para a inserção de outras questões ou ajuste fino na formatação.

O código está abaixo, e pode ser baixado daqui



#!/usr/bin/env python
# -*- coding: ISO-8859-1 -*-

import random, sys, os

# define a quantidade maxima de questoes/respostas: 702 por enquanto.

letras_1 = ['A','B','C','D','E','F','G','H','I','J', 'K','L', 'M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']

letras_2 = [x+y for x in letras_1 for y in letras_1]

letras = letras_1 + letras_2

identificadores = ['.-' , ',+' , ':-' , '=+', ';.', '...', ';;;', '+++', '~~~', '***']

def lequestoes(arquivos):
""" funcao que le os arquivos e cria uma lista com todas as perguntas, que sao dicionarios"""
print 'crialista', arquivos
listao = []
respostas = []
d = dict()

# estados possiveis: fora de alguma questao
# dentro de uma questao - Q - pergunta
# dentro de uma questao - A - respostas
# quando esta fora de uma questao e encontra um Q, deve entrar em modo questao, adicionar a pergunta ao
# dicionario
# quando esta em modo questao e encontra um A, deve entrar em modo respostas, adicionar a resposta ao
# dicionario
# quando esta em modo respostas e encontra um A, adiciona a resposta ao dicionario
# quando esta em modo respostas e encontra um Q, deve entrar em modo questao, adicionar o dicionario
# ao listao de perguntas, zerar o dicionario e as respostas
# quando esta em qualquer modo e encontra uma linha em branco, passa a proxima linha
# quando esta em modo questao e encontra um Q, significa erro no arquivo: retorna ERRO.

for a in arquivos:
arq = open(a)
lista = []
todaslinhas = arq.readlines()
respostas = []
estado = 'OUT'
for linha in todaslinhas:
if linha.startswith('Q:') and estado == 'OUT': # nova questao
d = dict()
estado = 'Q' # entra em modo questao
linha = linha.lstrip(' ')
d["q"] = linha[3:]
d["t"] = linha[0]
elif linha.startswith('Q') and estado == 'Q': # outra linha com Q -> erro
print "Arquivo %s de questoes com erro linha %d " % (a, todaslinhas.index(linha))
sys.exit(-1)
if linha.startswith('A') and (estado == 'Q' or estado == 'A'):
estado = 'A' # entra em modo resposta
linha = linha.lstrip(' ')
if len(linha) > 4:
respostas.append(linha[3:])
d["a"] = respostas
else:
print "linha len < 4"
if linha.startswith('Q') and estado == 'A': # nova questao
listao.append(d)
respostas = []
d = dict()
estado = 'Q'
linha = linha.lstrip(' ')
d["q"] = linha[3:]
d["t"] = linha[0]
if linha.startswith('\n'): # linha em branco separando questao
if estado == 'A' and len(d):
listao.append(d)
respostas = []
d = dict()
estado = 'OUT'
pass
# fim do loop
if estado == 'A' and len(d): # terminaram as linhas e o dicionario contem dados
listao.append(d)
estado = 'OUT'
d = {}
respostas = []
arq.close()

return listao

def criasequencia(listao):
""" cria uma sequencia (lista) de tuplas (q,a) a partir do listao geral de questoes """
sequencia = [] # todas as possibilidades de perguntas (tuplas do tipo (Q,A))
for q in listao:
perg = q['q']
resp = random.choice(q['a'])
sequencia.append((perg,resp))
if q['t'] != 'QNI':
pass
return sequencia

def criaprovas(sequencia, numprovas,questaoporprova):
""" cada prova eh gerada aleatoriamente a partir da lista de tuplas com todas as questoes.
recebe como argumentos: uma lista de tuplas (q,a), o num de provas a serem geradas e
quantas questoes cada prova deve ter.
retorna as provas geradas e os gabaritos; as provas sao listas de tuplas (q,a) e os gabaritos sao listas de tuplas (num questao, letra resposta)
"""
provas = []
gabaritos = []
for np in range(numprovas):
prova = random.sample(sequencia,questaoporprova)
provas.append(prova)
perguntas = []
respostas = []
embaralha = range(len(prova))
random.shuffle(embaralha)
gabarito = []
gaba_str = ''
for i in range(len(prova)):
gabarito.append((i+1,letras[embaralha[i]]))
gabaritos.append(gabarito)
return provas, gabaritos


def crialatex(sequencia, numprovas,questaoporprova):
""" cria um arquivo .tex para cada prova e tb o arquivo gabarito para as provas
recebe como argumentos: uma lista de tuplas (q,a), o num de provas a serem geradas e
quantas questoes cada prova tera.
cada prova eh gerada aleatoriamente a partir da lista de tuplas com todas as questoes.
retorna as provas geradas.
"""
preambulo1 = """
\documentclass[10pt,brazil,a4paper]{article}
\usepackage[latin1]{inputenc}
\usepackage[portuguese]{babel}
%\usepackage{graphicx}
%\usepackage{multicol}
%\usepackage{shadow}
%\usepackage{pifont}
%\usepackage{listings}
%\usepackage{fancyvrb}

%\usepackage{boxedminipage}
%\usepackage{theorem}
\usepackage{verbatim}
\usepackage{tabularx}
%\usepackage{moreverb}
\usepackage{times}
%\usepackage{relsize}
\usepackage{pst-barcode}

\setlength{\\textwidth}{180mm}
\setlength{\\oddsidemargin}{-0.5in}
\setlength{\\evensidemargin}{0in}
\setlength{\\columnsep}{8mm}
\setlength{\\topmargin}{-28mm}
\setlength{\\textheight}{265mm}
\setlength{\\itemsep}{0in}
\\begin{document}
\\pagestyle{empty}
%\lstset{language=python}

"""
preambulo2= """

\\textbf{Curso:}\\rule{11cm}{0.1pt}\hspace{0.5cm}\\textbf{Turma:}\\rule{3cm}{0.1pt}

\\textbf{Nome:}\\rule{11cm}{0.1pt} \hspace{0.5cm}\\textbf{nota:} \\rule{3cm}{0.1pt}

{\scriptsize

\\textbf{Instruções:}
\\begin{verbatim}
1. Proibida a consulta de livros ou anotações 2. Permitido o uso de calculadoras eletrônicas
3. Somente serão consideradas as respostas da Parte 1 na região "Gabarito" desta página
\end{verbatim}
}


\\begin{tabularx}{\linewidth}{X|X}
\\textbf{Parte 1 -- Questões} """

preambulo3 = """

& \\textbf{Respostas} \\\\ \hline \hline
%& \\\\

"""

finaltabela = """
\end{tabularx}

%\\vspace{0.3cm}

\\textbf{Parte 1 -- Gabarito:}
"""
fimdocumento = """
\end{document}
"""

gabatabela = """

\\begin{tabular}[t]{|b{10mm}|b{10mm}|b{10mm}|b{10mm}|b{10mm}|b{10mm}|b{10mm}|b{10mm}|b{10mm}|b{10mm}|} \\\\ \hline
"""

titulo = "\\textbf{\\centering \\large %s}" % tituloprova
provas = []
arqgaba = open('prova_'+'gb.tex','w')
arqgaba.write(preambulo1.decode('utf-8').encode("latin1"))
titulogaba = "%s \\textbf{\\large GABARITO}" % titulo
arqgaba.write(titulogaba.decode('utf-8').encode('latin1'))

for np in range(numprovas):
prova = random.sample(sequencia,questaoporprova)
arqprova = open('prova_'+str(np)+'.tex','w')
arqprova.write(preambulo1.decode('utf-8').encode("latin1"))
arqprova.write(titulo.decode('utf-8').encode("latin1"))
arqprova.write(preambulo2.decode('utf-8').encode("latin1"))
arqprova.write("%s" % (identificadores[np]))
arqprova.write(preambulo3.decode('utf-8').encode("latin1"))
#arqprova.write(initabela)


provas.append(prova)
perguntas = []
respostas = []
embaralha = range(len(prova))
random.shuffle(embaralha)
gabarito = []
gaba_str = ''
for i in range(len(prova)):
arqprova.write("%d) %s & \n".encode('latin1') % (i+1, prova[embaralha[i]][0]))
gabarito.append((i+1,letras[embaralha[i]]))
gaba_str += letras[embaralha[i]]
arqprova.write("%s) %s \\\\ \n".encode("latin1") % (letras[i],prova[i][1]))

arqprova.write(finaltabela)
#arqprova.write("%s" % (identificadores[np]))
arqprova.write("\\hspace{1cm}\\begin{pspicture}(1,1in)\n")
# a linha abaixo gera o codigo de barras do identificador (duplicado) da prova,
# inclui o texto no codigo de barras
#arqprova.write("\\psbarcode[scalex=0.9,scaley=0.3]{%s%s}{includetext}{code39}\n" % (np,np))
# gera codigo de barras usando a string do gabarito, nao inclui o texto no codigo de barras.
arqprova.write("\\psbarcode[scalex=0.9,scaley=0.3]{%s}{}{code39}\n" % (gaba_str))
arqprova.write("\\end{pspicture}\n")
arqprova.write(gabatabela.encode('latin1'))
for l in range(len(prova)):
arqprova.write("{\\raggedright \\raisebox{0.8ex}[2.5ex][0.75ex]{%d}} ".encode("latin1") % (l+1))
if l > 0 and (l+1) % 10 == 0:
arqprova.write("\\\\ \hline \n")
else:
arqprova.write("&")
arqprova.write("\\end{tabular}")

arqprova.write(fimdocumento)

arqgaba.write("\n\n\n PROVA")
arqgaba.write("%s" % (identificadores[np]))
arqgaba.write("\\hspace{1cm}\\begin{pspicture}(1,1in)\n")
# a linha abaixo gera o codigo de barras do identificador (duplicado) da prova,
# inclui o texto no codigo de barras
#arqprova.write("\\psbarcode[scalex=0.9,scaley=0.3]{%s%s}{includetext}{code39}\n" % (np,np))
# gera codigo de barras usando a string do gabarito, nao inclui o texto no codigo de barras.
arqgaba.write("\\psbarcode[scalex=0.9,scaley=0.3]{%s}{}{code39}\n" % (gaba_str))
arqgaba.write("\\end{pspicture}\n")
arqgaba.write(gabatabela.encode('latin1'))
for l in range(len(prova)):
arqgaba.write("{\\raggedright \\raisebox{0.8ex}[2.5ex][0.75ex]{%d}} %s ".encode("latin1") % (gabarito[l]))
if l > 0 and (l+1) % 10 == 0:
arqgaba.write("\\\\ \hline \n")
else:
arqgaba.write("&")
arqgaba.write("\\end{tabular}")

arqprova.close()

arqgaba.write(fimdocumento)
arqgaba.close()
return provas


tituloprova = "Avaliação Bimestral"

def main():
if len(sys.argv) > 1:
arquivos = sys.argv[3:]
numprovas = int(sys.argv[1])
questaoporprova = int(sys.argv[2])
print arquivos

else:
numprovas = int(raw_input("entre o numero de provas: "))
questaoporprova = int(raw_input("entre o numero de questoes por prova: "))
entrada = raw_input("entre os nomes dos arquivos, separados por espaco: ")
entrada.rstrip('\n')
arquivos = entrada.split(' ')
global tituloprova
tituloprova= raw_input("Digite o titulo da prova: ")
lista = lequestoes(arquivos)
sequencia = criasequencia(lista)
provas = crialatex(sequencia, numprovas, questaoporprova)

if __name__ == '__main__':
main()

6 comentários:

Sergio disse...

Enviei a dica para alguns professores meus da Faculdade eles vão querer aprender python.

Cárlisson Galdino (Bardo) disse...

Uma boa seria um exemplo prático. Tipo uma prova com 3 questões e mostrar uma imagem de como fica o impresso dela (já transformado a partir do tex). Só uma sugestão para aumentar o interesse na ferramenta... ;-)

Mario M. disse...

@bardo

Obrigado pela sugestão.

Veja a última atualização do script
criaprova

Tem um exemplo do resultado final.

Unknown disse...

Cara, muito bom mesmo..

Parabéns!! Queria ser seu aluno hehehe

Mario M. disse...

@chackal_sjc

Obrigado!

Arnaldo de Souza disse...

Eu também achei fantástico, o dificil é desenvolver um script que ache as respostas corretas para a prova dele.

Arnaldo