Introdução aos vetores e listas

[ Vetores | Vetores em C | Vetores em Python ]

Nesta seção examinaremos resumidamente como utilizar vetores tanto em C quanto em Python, mas iniciamos com a questão essencial: por que usar vetor (ou lista, outro possível nome, apesar desse segundo poder implicar em operadores especiais).

Por que precisamos de vetores?

Pense em um problema simples, muito comum, que é extrair informações sobre o comportamento médio de um "conjunto" de dados, mas apenas a média pode ser pouco informativo (e.g., metade tem valor 10 e a outra metade 0). Assim, estamos interessados em um software que compute a média e o desvio padrão de um "conunto" de dados.
Lembrando a definição de desvio padrão (DP):

`DP(v0, v1,... vn-1) = ((v0-med)2+(v1-med)2+(vn-1-med)2)1/2`
Então não é viável usar variáveis "simples", pois não sabemos a priori quantos dados o sistema receberá, além disso não parece razoável investir tempo para desenvolver um sistema que compute a média e desvio padrão para apenas 5, 15 ou qualquer número fixo de valores; mesmo que "hoje" precisemos conhecer da informação apenas para este número fixo, podemos pensar em reaproveitar o software "amanhã".
Mas suponhamos ainda que o responsável pelo desenvolvimento desconheça o conceito de vetores (algo improvável), neste caso, ele poderia tentar ler n e depois, em um laço, os n valores para computar a média:

`s:=0;`
`repetir para i entre 0 e n { ler(x); s:=s+x; }`
`<verm>imprimir</verm>(s/n); <cyan>//# med = s/n</cyan>`

Algoritmo 1. Tentativa de obter a méida (sem guardar todos os dados).
Desse modo conseguiria a média (digamos na variável `med`), mas agora para obter o desvio padrão seria necessário obter uma soma do quadrado da distância entre cada valor à média!
Mas no algoritmo 1 os dados foram lidos, um a um, usados para computar a soma e ao final, teríamos apenas o último valor lido (em `x`).
A saĩda é o conceito de vetor, que é implementado por todas as linguagens de programação de propósito geral, a ideia é reservar uma variável que tenha acesso à várias posições de memória, como exploraremos a seguir. Com o vetor podemos guardar todos os dados lidos no algoritmo 1, possibilitando o calculo do desvio padrão. Vetores: sequência contígua de variáveis

Usualmente um vetor é um agregado de dados de um mesmo tipo básico ocupando posições consecutivas de memória. Isso significa que é possível pegar alternadamente o valor armazenado em qualquer das posições do vetor, Usualmente isso é implementado nas linguagens por uma indexamentos, ou seja, sendo vet o nome da variável agregada, pode-se pegar o primeiro elemento fazendo vet[0], o segundo com vet[1] e assim por diante.

Fig. 1. Representação da RAM com um vetor de 4 posições..

Na figura acima representamos a memória do computador com um vetor (agregado) de variáveis (usando 8 bits apenas) e de tamanho 4, ou seja, um vetor para armazenar 4 valores do tipo dado.

Como citado na explicação sobre funções, uma grande vantagem de ter um algoritmo implementado como uma função separada é que o código do programa fica mais organizado e pode-se invocar esse algoritmo (a partir da função) em qualquer outro trecho do programa. Por isso, alguns funções muito úteis são implementadas pelos desenvolvedores para ficarem disponíveis dentro dos ambientes de programação.

Retomando o exemplo do cálculo da média aritmética e do desvio padrão de n valores, poderíamos implementar a função `calcula_media` e a função `calcula_dp`, como abaixo.

    <verm>inteiro</verm> calcula_media (vetor, n) { <cyan>//# declara funcao que devolve 'inteiro'</cyan>
      s := 0; <cyan>//# atencao, dependendo da linguagem, e' melhor declarar ser to tipo "real"</cyan>
      repetir para i entre 0 e n { s := s+vetor[i]; }
      devolve s/n; <cyan>//# a media - atencao, dependendo da linguagem precisa de alguma forma forcar tratar-se de "reais"</cyan>
      }
    <verm>real</verm> calcula_dp (vetor, med, n) { <cyan>//# declara funcao que devolve 'real'</cyan>
      s := 0;
      repetir para i entre 0 e n {
        aux := med-vetor[i]; <cyan>//# pequeno truque para melhorar desempenho do algoritmo...</cyan>
        s := s + aux*aux; <cyan>//# nao precisa computar 2 vezes med-vetor[i]</cyan>
        }
      devolve raiz_quadrada(s); <cyan>//# raiz_quadrada(s) devolve s1/2</cyan>
      }

Algoritmo 2. Calcula média e desvio padrão em funções separadas.

Por exemplo, tanto em C quanto em Python existem implementações para funções matemáticas úteis, como seno, cosseno ou raíz quadrada (respectivamente, `sin`, `cos` e `sqrt`). Mas é preciso indicar ao ambiente que ele deve carregar essas funções, isso é feito com os comandos `#include <math.h>` em C e `import "math"`.

Vetores em C

Como em C deve-se sempre declarar o tipo da variável, no caso de vetor deve-se declarar seu tipo e seu tamanho. No exemplo abaixo ilustramos declaração e uso de vetores dos tipos básicos int, char e float.

Vetores em C
Vetor de inteiros | Vetor de caracteres | Vetor de flutuantes
--- | --- | ---

    #include <stdio.h>
    #define<;tt> M 20 <cyan>// usado para constante</cyan>
    <verm>int</verm> main (void) {
      <verm>int</verm> i; <cyan>// auxiliar, para indexar os vetores</cyan>
      <verm>int</verm> nI; <cyan>// tamanho util dos vetores</cyan>
      <verm>int</verm>  vetI[M]; <cyan>// Vetor para inteiros (ate' M elementos)</cyan>
      scanf("%d", &nI);
      <azul>for</azul> (i=0; i< nI; i++) <cyan>// "Ler" nI inteiros</cyan>
        scanf("%d", &vetI[i]);
      <azul>for</azul> (i=0; i< nI; i++) <cyan>// "Imprimir" os elementos</cyan>
        printf("%d\n", vetI[i]);
      return 0;
      }

    #include <stdio.h>
    #define M 20 <cyan>// usado para constante</cyan>
    <verm>int</verm> main (void) {
      <verm>int</verm> i; <cyan>// auxiliar, para indexar os vetores</cyan>
      <verm>int</verm> nC; <cyan>// tamanho util dos vetores</cyan>
      char vetC[M]; <cyan>// Vetor para caracteres</cyan>
      scanf("%d", &nC);
      <azul>for</azul> (i=0; i< nC; i++) <cyan>// ler nC caracteres</cyan>
        scanf(" %c ", &vetC[i]); <cyan>// USAR branco antes de %c</cyan>
      <azul>for</azul> (i=0; i< nC; i++)
        printf("%c\n", vetC[i]);
      return 0;
      }

    #include <stdio.h>
    #define M 20 <cyan>// usado para constante</cyan>
    <verm>int</verm> main (void) {
      <verm>int</verm> i; <cyan>// Auxiliar, para indexar os vetores</cyan>
      <verm>int</verm> iF; <cyan>// Tamanho util dos vetores</cyan>
      float vetF[M]; <cyan>// Vetor para flutuantes</cyan>
      scanf("%d", &nF);
      <azul>for</azul> (i=0; i< nF; i++) <cyan>// "Ler" nF "floats"</cyan>
        scanf("%f", &vetF[i]);
      <azul>for</azul> (i=0; i< nF; i++)
        printf("%f4.1\n", vetF[i]); <cyan>// Usar 4 posicoes</cyan>
      return 0;                     <cyan>// e 1 para pto dec.</cyan>
      }

Como já examinado, em C geralmente deve-se carregar a biblioteca `stdio` mas existem outras, como a `stdlib.h` que dispõe, entre outras, da função `qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))` que implementa o método ordenador eficiente denominado Quick Sort (ou Ordenador Rápido). Veja um exemplo de código usando o `qsort`:

    #include <stdio.h> <cyan>// para funcao 'printf'</cyan>
    #include <stdlib.h> <cyan>// para a funcao 'qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))'</cyan>
    <verm>int</verm> vetor[] = { 3, 9, 1, 7, 0, 4, 6, 5, 2, 8 }; <cyan>// por simplicidade usar globais e manter fixo</cyan>
    <verm>int</verm> N = 10;

    void imprime (<verm>int</verm> vet[], <verm>int</verm> n) { <cyan>// funcao para imprimir em mesma linha</cyan>
      <verm>int</verm> i;
      <azul>for</azul> (i=0; i<n; i++)
         printf(" %d", vet[i]);
      printf("\n");
      }

    <cyan>// Esta e' a funcao comparadora: pode-se comparar qualquer coisa, basta codificar seu comparador</cyan>
    <verm>int</verm> comparador (const void *a, const void *b) { <cyan>// precisa de 2 parametros a serem depois comparados</cyan>
      return ( *(int*)a - *(int*)b );
      }

    <verm>int</verm> main (void) {
      printf("Testar 'qsort' da biblioteca 'stdlib'\n");
      printf("Antes: ");
      imprime(vetor, N);
      printf("Inicio: invocar 'qsort' da biblioteca 'stdlib'\n");
      <cyan>// Parametros para 'qsort': vetor de dados, numero de elementos no vetor, tamanho em bytes de cada elemento, endereco da funcao comparadora</cyan>
      qsort(vetor, N, sizeof(int), &comparador);
      printf("Ordenado: ");
      imprime(vetor, N);
      return 0;
      }

Atenção. No C padrão NÃO é possível declarar o vetor usando a variável que será usada para informar o número efetivo de posições a serem usadas, ou seja, não tente fazer algo como `int n; float vetF[n];`. A razão disso é que C é uma linguagem compilada e ao compilar deve-se reservar o espaço máximo a ser usado pelo vetor. Já a variável `n` só será conhecida durante a execução do programa.

Apesar de algumas implementações de compiladores C permitirem, NÃO usem sob pena de seu programa não funcionar em outros compiladores.

Vetores/listas em Python

Geralmente, na literatura de programação, o termo vetor refere-se a um agregado de dados, de mesmo tipo e em posições consecutivas de memória. Como em Python, pode-se misturar tipos, então usa-se o termo lista (ou tuple). Por exemplo, pode-se declarar `lista = (1, 2, "tres", "VI");` ou `tupla = [1, 2, "tres", "VI"];` e alterar ou imprimir um elemento, como em: `lista[3] = 3; tupla[3] = 3; <verm>print</verm>("lista=%s, tupla=%s, lista[3]=%d" % (lista,tupla, lista[3]);`.

Como citado na explicação sobre funções, uma grande vantagem de ter um algoritmo genericamente implementado como uma função separada é que o código do programa fica mais organizado e pode-se invocar a função em qualquer outro trecho do programa. Por isso, alguns funções muito úteis acabam sendo implementadas pelos desenvolvedores nos ambientes de programação.

Por exemplo, em Python existe a função `sorted(.)` que ordena listas (e tuplas), mas também existe um método sobre lista (não sobre tupla) que também ordena lista:

      lista = ["tres", "quatro", "um", "dois"];
      <verm>print</verm>("Ordenado via 'sorted': %s" % sorted(lista)); <cyan># ['dois', 'quatro', 'tres', 'um']</cyan>
      lista.sort(); <cyan># nao tente imprimir direto "lista.sort()" senao aparece "None"</cyan>
      <verm>print</verm>("Ordenado via 'lista.sort()': %s" % lista); <cyan># ['dois', 'quatro', 'tres', 'um']</cyan>

Note que o resultado em `lista` será `['dois', 'quatro', 'tres', 'um']`, pois a ordem é lexicográfica (do dicionário).

Outra observação importante, o método sort() não está definido para tuplas, ou seja, os comandos `lista = ("tres", "quatro", "um", "dois"); lista.sort(); <verm>print</verm>(lista);` resultaria erro (`AttributeError: 'tuple' object has no attribute 'sort'`).

No exemplo abaixo ilustramos a declaração e uso de listas dos tipos básicos int, char e float. Nesses exemplos utilizamos uma técnica de pré-alocar todo o espaço para a lista, utilizando um valor máximo para o número de posições a serem utilizadas (o `M = 20;`). Depois solicitamos que o usuário digite o número de elementos que ele deseja inserir na lista (não fazemos um teste, mas nessa técnica esse valor tem que ser menor que M), então utilizamos um laço para solicitar a "entrada" de cada elemento, inserindo-o na posição adequada da lista.

Listas em Python com alocação inicial de todo o espaço à priori
Vetor de caracteres | Vetor de inteiros | Vetor de flutuantes
--- | --- | ---

    M = 20 <cyan># usado para dimensionar os vetores</cyan>
    <verm>def</verm> main () :
      vetC = list(range(0,M)); <cyan># Vetor para inteiros (M elementos)</cyan>
      nC = <verm>int</verm>(input());       <cyan># Ler quem definira' tamanho</cyan>
      <azul>for</azul> i in range(0, nC) : <cyan># Para ler nC caracteres</cyan>
        vetI[i] = <verm>raw_input</verm>(); <cyan># no Python 3 usar 'input()'</cyan>
      <azul>for</azul> i in range(0, nC) : <cyan># imprimir os elementos</cyan>
        <verm>print</verm>(vetI[i]);

    M = 20 <cyan># usado para dimensionar vetores</cyan>
    <verm>def</verm> main () :
      vetI = list(range(0,M)); <cyan># Vetor de inteiros (M elementos)</cyan>
      nI = <verm>int</verm>(input());       <cyan># Ler quem definira' tamanho</cyan>
      <azul>for</azul> i in range(0, nI) : <cyan># Para ler nI inteiros</cyan>
        vetI[i] = <verm>int</verm>(input())
      <azul>for</azul> i in range(0, nI) : <cyan># Imprimir os elementos</cyan>
        <verm>print</verm>(vetI[i]);

    M = 20 <cyan># Para dimensionar os vetores</cyan>
    <verm>def</verm> main () :
      vetF = list(range(0,M)); <cyan># Vetor para inteiros (M elementos)</cyan>
      nF = <verm>int</verm>(input());       <cyan># Ler quem definira' tamanho</cyan>
      <azul>for</azul> i in range(0, nF) : <cyan># Para ler nF inteiros</cyan>
        vetI[i] = float(input());
      <azul>for</azul> i in range(0, nI) : <cyan># Imprimir os elementos</cyan>
        <verm>print</verm>(vetI[i]);

Vale notar que, como em Python o espaço para o vetor é reservado durante a execução, não é necessário pré-dimensionar o espaço para o "vetor" como nos 3 códigos acima, e pode-se usar como linhas 3 e 4: `nI = <verm>int</verm>(input()); vetI = list(range(0,nI));`.

Por fim, vale destacar um detalhe a respeito de como "digitar" os dados usando o comando input. No Python 2, Nos códigos acima, se o usuário digitar `SyntaxError: invalid syntax`.

Podemos adotar outra técnica para registro dos dados na lista, desta vez não usando alocação a priori de espaço e sim estendendo a lista a cada novo elemento digitado. Mas ainda usando a técnica que obriga o usuário a digitar um único valor por vez (que dizer, digitar um inteiro e teclar `ENTER`). Esse exemplo está à esquerda da tabela abaixo.

Na coluna da direita da tabela mostramos um exemplo distinto, no qual não é necessário digitar a priori o número de inteiros que comporão a lista. O usuário deverá digitar todos os dados em uma mesma linha, com um único `ENTER` ao final, e o código decompõe a "string" digitada, utilizando como separadores espaço em branco para deduzir quais são os inteiros.

Listas em Python com alocação dinâmica de espaço e tipos de digitação
Digitando um elemento por vez | Digitando todos os elementos de uma vez
--- | ---

    <verm>def</verm> alocacaodinamica1por_linha () :
      vetI = []; <cyan># Aloca um "vetor" vazio</cyan>
      <cyan># nI = <verm>int</verm>(input());</cyan>
      nI = M;
      <azul>for</azul> i in range(0, nI) : <cyan># ler nI inteiro</cyan>
        vetI.append(<verm>int</verm>(input())); <cyan># le novo elemento e estende a lista</cyan>
      <verm>print</verm>("Lista: ", end=""); <cyan># imprimir sem mudar de linha</cyan>
      <azul>for</azul> i in range(0, nI) : <cyan># imprimir os elementos</cyan>
        <verm>print</verm>("%d " % vetI[i], end=""); <cyan># imprimir sem mudar de linha</cyan>
      <verm>print</verm>(); <cyan># quebrar a linha ao final

</cyan>

    <verm>def</verm> alocacaodinamica_varios_por_linha () :
      <cyan># como leremos por linha, pode-se deduzir depois o tamanho da lista</cyan>
      linha = <verm>raw_input</verm>(); <cyan># ler como caracteres para poder pegar uma linha inteira - no Python 3 usar 'input()'</cyan>
      vetI = map(<verm>int</verm>, linha.split()); <cyan># decomponha a linha em itens e faz um mapeamento, passando cada item para inteiro</cyan>
      nI = len(vetI); <cyan># pegar tamanho da lista digitada</cyan>
      <verm>print</verm>("Lista: ", end=""); <cyan># imprimir sem mudar de linha</cyan>
      <azul>for</azul> i in range(0, nI) : <cyan># imprimir os elementos</cyan>
        <verm>print</verm>("%d " % vetI[i], end=""); <cyan># imprimir sem mudar de linha</cyan>
      <verm>print</verm>(); <cyan># quebrar a linha ao final</cyan>

Leônidas de Oliveira Brandão
http://line.ime.usp.br

Alterações:
2020/08/20: acertos no formato
2020/08/11: acerto formato, pequenos acertos no texto
2020/05/05: acrescentada subseção Por que precisamos de vetores?
2019/06/11: detalhes de forma;
2019/06/04: revisão geral.