PC: n >= 0, m = mQual a condição invariante? Usando a técnica usual de substituir uma das constantes no quantificador por uma nova variável de programa, obtem-se:
CO: (Q k : 0 <= k < n : m[k] = m[n - 1 - k])
CI: (Q k : 0 <= k < i : m[k] = m[n - 1 - k]) e 0 <= i <= nDonde a guarda surge naturalmente como G: i != n, e uma possível inicialização é:
i = 0;pois conduz trivialmente à veracidade da CI. Um possível progresso é simplesmente
i++;pois conduz necessariamente à terminação do ciclo. Mas que acção utilizar? No passo do ciclo tem-se CI e G, ou seja
(Q k : 0 <= k < i : m[k] = m[n - 1 - k]) e 0 <= i <= n e i <> no que implica
(Q k : 0 <= k < i : m[k] = m[n - 1 - k]) e 0 <= i + 1 <= nPara que a CI seja recuperada, apesar do progresso, é necessário que a acção conduza a
(Q k : 0 <= k < i + 1 : m[k] = m[n - 1 - k]) e 0 <= i + 1 <= nou seja
(Q k : 0 <= k < i : m[k] = m[n - 1 - k]) e m[i] = m[n - 1 - i] e 0 <= i + 1 <= nMas a atribuição
m[i] = m[n - 1 - i];não é possível, pois m é uma variável matemática usada para representar os valores originais na matriz m! A atribuição
m[i] = m[n - 1 - i];já é possível, mas, como não se garante que m[n - 1 - i] = m[n - 1 - i], não conduz necessariamente, em conjunto com o progresso, à CI.
A conclusão que se pode tirar do falhanço anterior é que a CI tem de afirmar não só que os primeiros i elementos de m estão já trocados, como tem de afirmar alguma coisa acerca dos restantes elementos da matriz. Em particular, seria desejável (aparentemente) que, para que a atribuição m[n - 1 - i] = m[n - 1 - i] surtisse o efeito desejado, os restantes elementos estivessem pela ordem original. Assim, uma nova versão da CI é
CI: (Q k : 0 <= k < i : m[k] = m[n - 1 - k]) e (Q k : i <= k < n : m[k] = m[k]) eMas esta solução só resolve o problema quando i <= n - 1 - i, pois só nessas circunstâncias a CI garante que m[n - 1 - i] = m[n - 1 - i]. Ou seja, só se consegue recuperar o invariante para 2 i <= n - 1, i.e., para metade da matriz! Isto acontece porque os valores originalmente à esquerda da matriz se perderam no processo de troca! I.e., o ciclo
0 <= i <= n
i = 0;quando aplicado a uma matriz, só consegue inverter a primeira metade da matriz. Por exemplo, se n = 6 e m contiver originalmente
while(i != n) {
m[i] = m[n - 1 - i];
i++;
}
|
|
|
|
|
|
quando i atingir 3 conterá
|
|
|
|
|
|
Isto demonstra que a CI desenvolvida não é apropriada! Como melhorá-la? Aparentemente seria necessário que, quando i atingisse sensivelmente metade dos elementos da matriz, esta já estivesse totalmente ordenada. Mas para isso a CI tem de obrigar não apenas os i primeiros elementos a estarem invertidos, mas também os i últimos! Ajustando a CI:
CI: (Q k : 0 <= k < i : m[k] = m[n - 1 - k]) e (Q k : n - i <= k < n : m[k] = m[n - 1 - k]) eOu seja, a nova CI divide a matriz em três partes:
(Q k : i <= k < n - i : m[k] = m[k]) e 0 <= i <= n
|
|
|
|
|
|
|
Depois de invertida, a matriz passará a conter
|
|
|
|
|
|
|
Ou seja, o elemento central pode manter-se na sua posição. Assim, quando n for ímpar, a negação da guarda deverá conduzir a que o quantificador da terceira conjunção contenha um único elemento, o que se consegue obrigando a que i = n - 1 - i, ou 2 i + 1 = n.
Resumindo, se n for par, a negação da guarda deverá ser 2 i = n, e se n for ímpar, a negação da guarda deverá ser 2 i + 1 = n. Mas estas são as equações da divisão inteira de n por 2, sendo i o quociente! Assim, a negação da guarda pode ser escrita simplesmente como i = n // 2, ou seja G: i <> n // 2.
Mas, nesse caso, a CI pode ser um pouco mais forte, pois a gama de valores admitida para i é ainda demasiado grande. Pode-se estringir esta gama para 0 <= i <= n // 2, ou seja
CI: (Q k : 0 <= k < i : m[k] = m[n - 1 - k]) e (Q k : n - i <= k < n : m[k] = m[n - 1 - k]) eEsta alteração da CI revelar-se-á muito útil para a determinação da acção do ciclo.
(Q k : i <= k < n - i : m[k] = m[k]) e 0 <= i <= n // 2
Em resumo, a CI e a G são:
CI: (Q k : 0 <= k < i : m[k] = m[n - 1 - k]) e (Q k : n - i <= k < n : m[k] = m[n - 1 - k]) eA inicialização do ciclo faz-se, mais uma vez, fazendo simplesmente
(Q k : i <= k < n - i : m[k] = m[k]) e 0 <= i <= n // 2
G: i <> n // 2
i = 0;pois conduz imediatamente à veracidade da CI. Quanto ao progresso, também é fácil verificar que a simples incrementação de i conduz forçosamente à terminação do ciclo, ou seja, o progresso é
i++;Para determinar qual a acção do ciclo, começa-se, como habitual, por observar que, no passo do ciclo, CI e G é verdadeira, ou seja
(Q k : 0 <= k < i : m[k] = m[n - 1 - k]) e (Q k : n - i <= k < n : m[k] = m[n - 1 - k]) edonde se tira facilmente que
(Q k : i <= k < n - i : m[k] = m[k]) e 0 <= i <= n // 2 e i <> n // 2
(Q k : 0 <= k < i : m[k] = m[n - 1 - k]) e (Q k : n - i <= k < n : m[k] = m[n - 1 - k]) eOra, como i < n // 2, então necessariamente 2 i < n, ou seja, 2 i <= n - 1, ou ainda i <= n - i - 1. Mas isso significa que, pela terceira conjunção da CI acima, m[i] = m[i] e m[n - i - 1] = m[n - i - 1].
(Q k : i <= k < n - i : m[k] = m[k]) e 0 <= i + 1 <= n // 2
O objectivo da acção é recuperar a CI apesar do progresso. Ou seja, desejar-se-ia que, antes do progresso,
(Q k : 0 <= k < i + 1: m[k] = m[n - 1 - k]) e (Q k : n - i - 1 <= k < n : m[k] = m[n - 1 - k]) eA quarta conjunção ficou já demonstrado que se verifica mesmo antes da acção. Quanto à terceira conjunção, um vez que o quantificador apenas perde termos no processo, verifica-se também mesmo sem a acção. Quanto às duas primeira conjunções,
(Q k : i + 1 <= k < n - i - 1 : m[k] = m[k]) e 0 <= i + 1 <= n // 2
(Q k : 0 <= k < i + 1: m[k] = m[n - 1 - k]) = (Q k : 0 <= k < i: m[k] = m[n - 1 - k]) eMas, como se viu acima, m[i] = m[i] e m[n - i - 1] = m[n - i - 1], pelo que a acção deverá simplesmente trocar os valores de m[i] e m[n - i - 1]. Por exemplo:
m[i] = m[n - 1 - i]
(Q k : n - i - 1 <= k < n : m[k] = m[n - 1 - k]) = (Q k : n - i <= k < n : m[k] = m[n - 1 - k]) e
m[n - i - 1] = m[i]
int aux = m[i];Ou seja, convertendo para um ciclo for, a função completa é (lembre-se que a // b se escreve simplemente a / b em C++, assumindo que a e b são inteiros)
m[i] = m[n - i - 1];
m[n - i - 1] = aux;
PC: n >= 0, m = m
CO: (Q k : 0 <= k < n : m[k] = m[n - 1 - k])
void inverte(int m[], int n)
{
// CI: (Q k : 0 <= k < i : m[k] = m[n - 1 - k]) e
// (Q k : n - i <= k < n : m[k] = m[n - 1 - k]) e
// (Q k : i <= k < n - i : m[k] = m[k]) e 0 <= i <= n // 2
for(int i = 0; i != n / 2; i++) {
aux = m[i];
m[i] = m[n - i - 1];
m[n - i - 1] = aux;
}
}
// PC: n >= 0ou, duma forma mais habitual em C++, mas para a qual não há CI evidente (pois não é um ciclo bem comportado, podendo nunca se chegar ao return final)
// CO: devolve (Q k : 0 <= k < n - 1 : m[k] <= m[k + 1])
bool éCrescente(int m[], int n)
{
// n >= 0
if(n == 0)
return true;
// n > 0
// CI: (é_crescente = (Q k : 0 <= k < i - 1 : m[k] <= m[k + 1])) e 1 <= i <= n
bool é_crescente = true;
for(i = 1; é_crescente && i != n; i++)
é_crescente = m[i - 1] <= m[i];
return é_crescente;
}
// PC: n >= 0que ainda se costuma ver escrito como
// CO: devolve (Q k : 0 <= k < n - 1 : m[k] <= m[k + 1])
bool éCrescente(int m[], int n)
{
if(n == 0)
return true;
for(i = 1; i != n; i++)
if(m[i - 1] > m[i])
return false;
return true;
}
// PC: n >= 0onde se usa uma guarda mais forte para garantir que o ciclo termina quando n = 0 (e também para n = - 1, n = -2, etc.). Esta última versão viola a regra de que as guardas dos ciclos devem ser tão fracas quanto possível.
// CO: devolve (Q k : 0 <= k < n - 1 : m[k] <= m[k + 1])
bool éCrescente(int m[], int n)
{
for(i = 1; i < n; i++)
if(m[i - 1] > m[i])
return false;
return true;
}
// PC: n > 0 e (E j : 0 <= j < n : m[j] = k)Note-se que a variável i foi definida fora do ciclo for, pois o seu valor é necessário fora do ciclo for para devolução posterior. Se a variável i fosse definida no próprio ciclo for não seria visível aquando do return (excepto em alguns compiladores mais antigos).
// CO: 0 <= i < n e m[i] = k e (Q j : 0 <= j < i : m[j] <> k)
int primeiraOcorrência(int m[], int n, int k)
{
// CI: 0 <= i < n e (Q j : 0 <= j < i : m[j] <> k)
int i;
for(i = 0; m[i] != k; i++)
continue; // quando a instrução é nula, costuma pôr-se continuereturn i;
}
i = n ou (i <> n e m[i] = k)que, aplicando a propriedade distributiva da disjunção, é o mesmo que
i = n ou m[i] = kAssim, a CO é
CO: 0 <= i <= n e (Q j : 0 <= j < i : m[j] <> k) e (i = n ou m[i] = k)Neste caso a CI do ciclo obtém-se recorrendo, mais uma vez, à factorização da CO:
// PC: n >= 0O ciclo é muito semelhante ao do exercício anterior, com a diferença de que a G foi reforçada de modo a forçar o ciclo a terminar depois de explorados todos os elementos da matriz (anteriormente não era necessário pois garantia-se que existia um elemento da matriz com o valor procurado).
// CO: 0 <= i <= n e (Q j : 0 <= j < i : m[j] <> k) e (i = n ou m[i] = k)
int primeiraOcorrência(int m[], int n, int k)
{
// CI: 0 <= i <= n e (Q j : 0 <= j < i : m[j] <> k)
int i;
for(i = 0; i != n && m[i] != k; i++)
continue; // quando a instrução é nula, costuma pôr-se continuereturn i;
}
int máximo(int m[], int n)
{
return m[índiceDoMáximo(m, n)];
}