Tarefa 3 - Critérios para Aprovação

Critérios para aprovação

Em uma disciplina, há avaliações de tarefas práticas e tarefas teóricas. Cada estudante recebe uma média aritmética P de 3 tarefas práticas e uma média aritmética T de 2 tarefas teóricas. A média final é dada por F = (T + P)/2.

Devido ao baixo desempenho da turma nas tarefas teóricas, o professor decidiu aplicar a seguinte regra de normalização: uma nova nota T' será dada pela nota T original vezes 10 e dividida pela maior nota T entre todos estudantes da turma. Assim, se João tirou 3 e a maior nota foi 6, então a nova nota de João será 10*3/6, ou 5.

Devido às inúmeras reclamações da turma, o professor resolveu ainda dar 10% de bônus na nota P de cada estudante. Assim, a nova nota P' será 1,1*P.

Seu objetivo é calcular a nota final de cada estudante e a médias das novas notas T' e P'.

Entrada

A entrada tem:

Exemplo:

2
5 6 7
7 7 10
2 4
5 7

Saída

A saída deve conter

Para a entrada acima, obtemos

5.8
9.4
Max P: 8.8
Max T: 10
Media P: 7.7
Media T: 7.5

Pois sabemos que as médias práticas dos dois estudantes forma um vetor P = [6, 8], mas como aumentamos 10%, obtemos P' = [6.6, 8.8]. As médias teóricas formam um vetor T = [3, 6], mas como normalizamos, temos que multiplicá-las por 10/6, obtendo T' = [5, 10].

Assim, as médias máximas de P' e T' são 8.8 e 10, respectivamente. Também, as médias de P' e T' entre os dois estudantes são 7.7 e 7.5.

Calculando as notas finais de cada estudante, obtemos F = [5.8, 9.4].

Implementando

Essa tarefa parece bem difícil, mas na verdade ela só realiza operações bem simples. O que é complicado é que ela faz diversas coisas diferentes e se não nos organizarmos, podemos nos perder rapidamente. Para evitar isso, vamos listar as várias coisas diferentes que precisamos fazer:

É muita coisa!!!

Se tentarmos fazer tudo de uma vez só, vamos obter um código muito grande e difícil de ler. Pior, se houver um erro, vamos gastar um tempo enorme tentando descobrir onde ele está. Essa é a situação ideal para usarmos funções. Usamos funções porque:

  1. Cada função tem uma responsabilidade bem pequena e podemos pensar em um problema menor de cada vez. Por isso, é muito mais fácil escrever e testar uma função separadamente.

  2. Podemos chamar a mesma função múltiplas vezes. Isso evita duplicidade de código, deixa o código menor, mais simples e mais flexível.

Por onde começamos?

É sempre bom começar a escrever um programa pela leitura dos dados e pelas funções mais simples. Vamos fazer duas funções, uma para ler as notas práticas e uma para ler as notas teóricas.

def ler_notas_praticas(n):
    P = []
    for _ in range(n):
        notas = input().split()
        nota_pratica = 0
        for nota in notas:
            nota_pratica += float(nota)
        nota_pratica = nota_pratica / 3
        P.append(nota_pratica)

    return P

def ler_notas_teoricas(n):
    T = []
    for _ in range(n):
        notas = input().split()
        nota_teorica = 0
        for nota in notas:
            nota_teorica += float(nota)
        nota_teorica = nota_teorica / 2
        T.append(nota_teorica)

    return T

n = int(input())
P = ler_notas_praticas(n)
T = ler_notas_teoricas(n)

Um leitor atento vai perceber que ambas funções precisam ler os números de uma linha e tirar a média entre eles, mas a primeira lê 3 números, enquanto a segunda lê apenas dois. Esse é um caso de duplicidade de código. Podemos simplificar isso da seguinte maneira

def ler_media(m):
    notas = input().split()
    media = 0
    for nota in notas:
        media += float(nota)
    media = media / m
    return media

def ler_notas_praticas(n):
    P = []
    for _ in range(n):
        P.append(ler_media(3))
    return P

def ler_notas_teoricas(n):
    T = []
    for _ in range(n):
        T.append(ler_media(2))
    return T

n = int(input())
P = ler_notas_praticas(n)
T = ler_notas_teoricas(n)

Para escrevermos funções correspondentes em C, precisamos descobrir como definir uma função em C. Primeiro, vamos construir uma função ler_media. Observe que ela deve receber um parâmetro inteiro m e deve devolver um valor númerico fracionário do tipo float. Em C, todas as variáveis devem ter um tipo declarado e isso também vale para os parâmetros e valor de retorno de uma função.

#include <stdio.h>

float ler_media(int m) {
    int i;
    float media, nota;
    media = 0;
    for (i = 0; i < m; i++) {
        scanf("%f", &nota);
        media += nota;
    }
    media = media / m;
    return media;
}

int main() {
    float media;
    media = ler_media(3);
    printf("%f\n", media);
}

Repare o tipo float antes do nome da função. Ele está indicando que essa função deve retornar um valor do tipo float. Se escrevêssemos algo diferente, como return "Olá"; dentro do corpo da função, o compilador iria se recusar a continuar porque o tipo "Olá" é diferente do tipo de retorno esperado.

Observe também que escrevemos uma função main bem simples que apenas imprime uma média. Fizemos isso para poder testar a função ler_media: é sempre bom testar cada uma das funções que construímos separadamente, à medida em que escrevemos.

Devolvendo um vetor por função

Agora temos que escrever a função ler_notas_praticas, mas essa função devolve uma lista de números fracionários do tipo float. Acontece que em C uma função não pode retornar um vetor. Ao invés disso, temos que passar um vetor por parâmetro e alterá-lo dentro da função. Como a função não irá mais retornar o vetor, anotamos o seu tipo como void, para indicar que ela não devolve nada.

#include <stdio.h>

#define MAX_ALUNOS 100

void ler_notas_praticas(float P[MAX_ALUNOS], int n) {
    int i; /* índice da posição do vetor */
    for (i = 0; i < n; i++) {
        P[i] = ler_media(3);
    }
}

int main() {
    int n;
    float P[MAX_ALUNOS];
    scanf("%d", &n);
    ler_notas_praticas(P, n);
}

A função ler_notas_teoricas é completamente análoga.

Recebendo um vetor por função

Revendo nossa lista de passos, notamos que devemos obter o valor máximo de um vetor repetidas vezes. Em Python podemos usar a função interna max, ou implementar nossa próxima função obter_maximo.

def obter_maximo(vetor):
    maximo = vetor[0]
    for valor in vetor:
        if maximo < valor:
            maximo = valor
    return maximo

Em C, uma função recebe um vetor da mesma forma que devolve um vetor: passando por parâmetro.

float obter_maximo(float vetor[MAX_ALUNOS], int n) {
    int i;
    float maximo = vetor[0];
    for (i = 0; i < n; i++) {
        if (maximo < vetor[i])
            maximo = vetor[i];
    }
    return maximo;
}

int main() {
    int n;
    float P[MAX_ALUNOS];
    float maximo;
    scanf("%d", &n);
    ler_notas_praticas(P, n);
    maximo = obter_maximo(P, n);
    printf("Max P: %f\n", maximo);
}

Devemos observar que dessa vez precisamos informar o número de elementos usados no vetor. Em C, o número de elementos usados em um vetor não é guardado no próprio vetor como acontece com as listas em Python, então precisamos sempre manter o número n de posições ocupadas do vetor.

Alterando um vetor por função

Continuando a nossa lista, percebemos que também precisamos de uma função para multiplicar os valores atuais de um vetor por um certo fator. Nesse caso, o vetor é tanto entrada quanto saída da função.

def mutiplicar_fator(vetor, fator):
    for i in range(len(v)):
        v[i] = v[i] * fator

n = int(input())
P = ler_notas_praticas(n)
mutiplicar_fator(P, 1.1)

Em C, fazemos algo como

void mutiplicar_fator(float vetor[MAX_ALUNOS], int n, float fator) {
    int i;
    for (i = 0; i < n; i++) {
        vetor[i] = vetor[i] * fator;
    }
}

int main() {
    int n;
    float P[MAX_ALUNOS];
    float maximo;
    scanf("%d", &n);
    ler_notas_praticas(P, n);
    mutiplicar_fator(P, n, 1.1);
    maximo = obter_maximo(P, n);
    printf("Max P: %f\n", maximo);
}

Dessa vez, o programa irá imprimir o valor máximo das notas práticas já com o bônus de 10%.

Resolvendo o exercício

Ainda precisamos construir outras funções, como obter_media, que devolve a média dos valores de um vetor, e imprimir_notas, que recebe os dois vetores P e T e imprime as médias finais dos estudantes. Com essas funções, só restará organizar todas as chamadas de função em um lugar só. Em Python, teríamos algo como

# ... funções...

n = int(input())

P = ler_notas_praticas(n)
T = ler_notas_teoricas(n)

mutiplicar_fator(P, 1.1)

maximo_teorica = obter_maximo(T)
mutiplicar_fator(T, 10.0/maximo_teorica)

imprimir_notas(P, T)

maximo_pratica = obter_maximo(P)
maximo_teorica = obter_maximo(T)
media_pratica = obter_media(P)
media_teorica = obter_media(T)

print("Max P:", maximo_pratica)
print("Max T:", maximo_teorica)
print("Media P:", media_pratica)
print("Media T:", media_teorica)

Reescreva agora esse programa em C, colocando todas essas chamadas de função na função main e escrevendo as funções que faltam. Você pode começar com o código acima e terminá-lo em Critérios para Aprovação

Exemplos relacionados

É opcional indicar o tamanho máximo do vetor nas declarações de parâmetro de função. Isso é útil porque podemos passar vetores de diferentes capacidades para a mesma função, desde que os vetores tenha o mesmo tipo.

#include <stdio.h>

/* não precisamos da capacidade no parâmetro */
int minimo(int vetor[], int n) {
    int i;
    int min = vetor[0];
    for (i = 0; i < n; i++) {
        if (min > vetor[i]) {
            min = vetor[i];
        }
    }
    return min;
}

int main() {
    int grande[100];
    int pequeno[5];

    /* ...inicializa todas posições dos vetores... */

    if (minimo(grande, 100) > minimo(pequeno, 5)) {
        printf("O menor elemento de todos está em pequeno\n");
    } else {
        printf("O menor elemento de todos está em grande\n");
    }
}