123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- <!--
- Por que evitar entrada/saida de dados em funções?
- http://saw.atp.usp.br/mod/page/view.php?id=
- -->
- <!DOCTYPE html>
- <html itemscope itemtype="http://schema.org/QAPage">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <title>Por que evitar entrada/saida de dados em funções?</title>
- <style type="text/css">
- .tbCodeLinCol { border-collapse:collapse; }
- .tbCodeLinCol th { background-color:#8aaada; }
- .tbCodeLinCol td, .tbCodeLinCol th { padding:5px;border:1px solid #000; font: 10px/12px Arial, Helvetica, sans-serif; }
- .tbCode td, .tb th { border: 0; font: 10px/12px Arial, Helvetica, sans-serif; }
- .tableCd { border-collapse:collapse; }
- .thBg th { background-color:#8aaada; }
- /* .trCd td, .tbCd td, .tbCd th { vertical-align: top; padding:5px; border-right:1px solid #000; font-family: courier } */
- .trCd td, .tbCd td, .tbCd th { vertical-align: top; padding:5px; border-right:1px solid #000; border-bottom:1px solid #000; font-family: courier }
- .cmd { color: #0000AA; }
- .fnc { color: #00AAAA; }
- .head { color: #000000; }
- .var { color: #009A00; }
- .tipo { color: #005A5A; }
- .com { color: #AA0000; }
- .def { color: #008181; font-weight: bold; } /* definicao */
- .versao { font-size: .6em; }
- </style>
- </head>
- <body>
- <span style="color: #0055AA;font-size:1.2em"><b>Por que evitar entrada/saida de dados em funções</b></span>
-
- <p>
- Para explicar as razões pelas quais deve-se <b>evitar comandos de entrada e de saída dentro da definição de uma função</b>,
- devemos primeiro enfatizar o que é a uma função em linguagens como <i>C</i> ou <i>Python</i>:
- <ul>
- <li>é um código que recebe seus <em>dados de entrada</em> a partir de uma <i>lista de parâmetros</i> (<i class="def">parâmetros formais</i>);</li>
- <li>é um código que processa os dados de entrada e geralmente devolve algum valor como resposta (em seu comando <tt class="cmd">return</tt>);</li>
- <li>é um código que pode ser invocado a partir de outros trechos do programa, que providenciam os valores para os parâmetros
- (<i class="def">parâmetros efetivos</i>).</li>
- </ul>
- </p>
- <p>
- Desse modo, usando como exemplo a <i>função fatorial</i> (<i>fat(0)=1</i> e <i>fat(n)=n*fat(n-1), n>0</i>), a razão para
- se evitar comandos de entrada e de saída dentro da definição da função pode ser resumida em:
- <ul>
- <li><i>Facilita o reúso de código</i>, ou seja, sempre que precisar de um algoritmo que compute o fatorial,
- basta copiar o código que já implementou (e testou);</li>
- <li><i>Facilita o desenvolvimento de programas maiores</i>, pois pode-se agrupar os blocos lógicos que tenham um propósito bem definido como
- uma função.</li>
- </ul>
- </p>
- <p>
- A fim de ficar mais clara a vantagem de <i>passar os valores via parâmetro</i>, pense na implementação <i>errônea</i> da função
- <tt class="var">fat</tt>, suponha que em seu código tenha sido usado um comando para <i>leitura</i> (via teclado) dos dados.
- Então, se precisar computar a combinação de <tt>n</tt>, tomados <tt>k</tt> a <tt>k</tt> (<i>C<sub>n,k</sub>=n!/((n-k)!k!)</i>),
- esse valor <b>não</b> poderá ser computado usando a expressão seguinte (com chamada à função <tt class="var">fat</tt>):
- <tt class="var">fat(n)/(fat(n-k)fat(k))</tt>.
- <br/>
- Por que mesmo? Porque, os valores a partir dos quais desejamos saber a combinação já são <b>dados</b>, <b>não</b> faz sentido obrigar
- o usuário a digitar os valores de <tt>n</tt>, de <tt>n-k</tt> e de <tt>k</tt> (além disso ele pode digitar errado!).
- </p>
- <p>
- Assim, nos exercícios que apresentam o conceito de função (portanto envolvendo um bloco lógico com propósito bem definido),
- exigiremos que o aprendiz <b>não</b> use comandos de entrada ou de saída de dados <b>dentro da função</b>.
- Com isso esperamos que ele adquira <i>bons hábitos de programação</i>.
- </p>
- <p>
- <!-- F<sub>0</sub>=1, F<sub>1</sub>=1, F<sub>k</sub> = F<sub>k-1</sub> + F<sub>k-2</sub>, k>2 -->
- Entretanto, existem as exceções, exemplos nos quais é útil usar comando de entrada ou de saída dentro da função. Ilustraremos
- o caso de utilidade de um comando de saída dentro da função, isso ocorre se desejamos computar (e imprimir) os <tt>n</tt> primeiros termos da
- <i title="F_{0}=1, F_{1}=1, F_{k} = F_{k-1} + F_{k-2}, k>1">sequência de Fibonacci</i>.
- Então, naturalmente precisaremos do comando de impressão/saída e como isso pode ser feito para qualquer
- <tt>n</tt> é adequado agrupar esse código na forma de um procedimento separado (no caso uma função <b>sem</b> valor de retorno).
- <br/>
- Porém se o objetivo fosse conhecer apenas o termo <tt>n</tt> da sequência, a função <b>não deveria ter o comando de saída</b>.
- </p>
- <p>
- Uma última observação sobre os exercícios envolvendo funções. <b>O programa principal sempre deve ter ao menos um comando para entrada
- e ao menos um para saída de dados</b>. Sem eles não seria possível testar se a função do exercício foi implementada corretamente ou não.
- </p>
- <span style="color: #0055AA;font-size:1.1em">Exemplo 1. Dados naturais <tt>n</tt> e <tt>k</tt>, computar a combinação de <tt>n</tt> <tt>k-a-k</tt></span>
- <p>
- Suponha que precisemos fazer um program para computar
- quantas são as combinações de <tt>n</tt> termos combinados <tt>k</tt>-a-<tt>k</tt>, ou seja, tendo <tt>n</tt> elementos,
- desejamos saber de quantos modos distintos podemos formar grupos com <tt>k</tt> dos elementos.
- </p>
- <p>
- Esse conceito aparece em <i>probabilidade</i> e em <i>combinatória</i>, sabemos que esta combinação corresponde ao agrupamentos de
- elementos nos quais a ordem não importa (tanto faz a ordem dos <tt>k</tt> elementos do tipo <tt>a</tt>) e que a fórmula que fornece
- o total desses subconjuntos é a seguinte:
- <center><a href="https://en.wikipedia.org/wiki/Binomial_theorem" title="explicacao mais detalhada na WikiPedia">C(n,k) = n! / ((n-k)! k!)</a>.</center>
- </p>
- <p>
- Por exemplo, combinar <tt>6</tt> valores distintos, tomados <tt>2</tt> a <tt>2</tt> resulta <tt>15=6!/((6-2)!2!)=6!/(4!2!)</tt>, que
- são os <tt>15</tt> subconjuntos:
- <tt>(6,5)</tt>, <tt>(6,4)</tt>, <tt>(6,3)</tt>, <tt>(6,2)</tt>, <tt>(6,1)</tt>,
- <tt>(5,4)</tt>, <tt>(5,3)</tt>, <tt>(5,2)</tt>, <tt>(5,1)</tt>,
- <tt>(4,3)</tt>, <tt>(4,2)</tt>, <tt>(4,1)</tt>,
- <tt>(3,2)</tt>, <tt>(3,1)</tt>,
- <tt>(2,1)</tt>.
- </p>
- <p>
- Assim, existem duas possibilidades de implementar esse código, uma implementando a função fatorial (<tt>fat(n)</tt>) que seria
- invocada três vezes pelo programa principal (com algo como <tt>imprima(fat(n) / (fat(k) * fat(n-k))</tt>.
- Outra possibilidade é implementar também o cômputo de <tt>fat(n) / (fat(k) * fat(n-k))</tt> também como uma função.
- Apesar dessa segunda possibilidade não ajudar tanto na organização do código (pois a segunda função (<tt>comb(n,k)</tt>) seria muito
- simples, ilustraremos dessa forma por ela usar função chamando função que chama função (a principal chama <tt>comb(n,k)</tt> e essa
- chama <tt>fat(n)</tt>).
- Veja como ficaria o código em uma <i>pseudo-linguagem</i> de programação:
- <pre>int fat(n) { //# estou supondo que o valor digitado pelo usuario seja um natural maior ou igual a 0 (n>=0)
- valfat = 1;
- cont = 2;
- enquanto (cont<=n) { //# continue computando enquanto nao chegar ao valor cont==n
- valfat = valfat * cont;
- cont = cont + 1;
- }
- devolva valfat;
- }
- int comb(n, k) {
- devolva fat(n) / (fat(n-k)*fat(k));
- }
- principal() {
- declara_inteiros N, k; //# note que os nomes de variaveis daqui tem relacao NULA com os usados em 'fat'
- declara_inteiros a, b, c; //# para uma segunda versao
- leia(N, k); //# leia 2 valores naturais e armazene-os respectivamente em N e em k (espera-se que digite N>=k)
- //# pode-se invocar seguidamente fat 3 vezes como abaixo:
- imprima(comb(N,k);
- //# pode-se invocar a funcao fat 3 vezes e armazenar cada resultado e depois "imprimir"
- a = fat(N);
- b = fat(k);
- c = fat(N-k);
- imprima(a/( b * c))); //# este exemplo e' ilustrado na figura abaixo
- }</pre>
- </p>
- <center><img src="img/exemplo_exec_funcao.jpg" title="exemplo de execucao da funcao"/>
- </center>
- <p>
- Novamente para enfatizar as desvantagens de usar leitura ou impressão dentro de funções,
- procure implementar o código acima, em sua linguagem favorita, e colocar nele comandos para
- leitura ou saída dentro da função <tt>fat(n)</tt>, como discutido abaixo.
- </p>
- <span style="color: #0055AA;font-size:1.1em">Observação 1. Vantagem de uso de função sem comandos de entrada/saída - combinação n, k-a-k</span>
- <p>
- Note que se dentro da função <tt>fat</tt> houvesse um comando do tipo <tt>leia(n);</tt>
- a função NÃO poderia ser usada, pois ao ser invocada as 3 vezes, em cada uma delas exigiria que o
- usuário digitasse novamente o valor para <tt>N</tt> inicialmente (estragando o valor de <tt>N</tt> passado para a função),
- depois exigiria a digitação novamente do <tt>k</tt> e por último haveria o absurdo de exigir que o usuário
- digitasse o valor para <tt>N-k</tt>.
- </p>
- <p>
- De forma análoga, se a função <tt>fat</tt> tivesse algum comando de saída, ao usar a função 3 vezes, o usuário
- receberia informações confusas: ele deseja a combinação de <tt>n</tt> combinados <tt>k</tt>-a-<tt>k</tt>, mas
- receberia 4 infomações (<tt>n!</tt>, <tt>k!</tt>, <tt>(n-k)!</tt> e <tt>n!/(k! (n-k)!))))</tt>).
- </p>
- <span style="color: #0055AA;font-size:1.1em">Observação 2. Propriedade interessante de C(n,k) - Triângulo de Pascal</span>
- <p>
- Se rodarmos este programa seguidamente, na ordem das colunas (primeiro a única entrada de <tt>Entradas 0</tt>, depois a primeira
- linha de <tt>Entradas 1</tt>, depois sua segunda linha e assim por diante até a última linha de <tt>Entradas 4</tt>) e
- agruparmos as saídas de cada coluna em uma única linha, obteremos a tabela na última coluna abaixo (<tt>Saídas (cada linha um dos blocos de entradas)</tt>).
- Destacamos três dos valores para mostrar a relação do
- <a href="https://en.wikipedia.org/wiki/Pascal%27s_triangle" title="seguir para WikiPedia">Triângulo de Pascal</a>
- <table class="tableCd">
- <tr class="trCd" valign="top">
- <td>Linhas </td><td>Entradas 0 </td><td>Entradas 1 </td><td>Entradas 2 </td>
- <td>Entradas 3 </td><td>Entradas 4 </td>
- <td>Saídas (cada linha um dos blocos de entradas)</td></tr>
- <tr class="trCd" valign="top"><td><pre> 0
- 1
- 2
- 3
- 4</pre></td><td><pre> 1 0</pre></td> <td><pre> 1 1
- 1 0</pre></td><td><pre> 2 0
- 2 1
- 2 2</pre></td><td><pre> 3 0
- 3 1
- 3 2
- 3 3</pre></td><td><pre> 4 0
- 4 1
- 4 2
- 4 3
- 4 4</pre></td><td><pre> 1
- 1 1
- 1 2 1
- [1] [3] 3 1
- 1 [4] 6 4 1</pre></td></tr></table></p>
- <p>
- Examinando as saídas (ũltima coluna) notamos uma propriedade interessante:
- o elemento da linha <tt>n+1</tt>, coluna <tt>k+1</tt> = o elemento da linha <tt>n</tt>, coluna <tt>k</tt> + o elemento da linha <tt>n</tt>, coluna </tt>k+1</tt>.
- Veja por exemplo, a última linha, linha <tt>4</tt>: o segundo elemento = <tt>4</tt> = <tt>1 + 3</tt> (da linha acima).
- </p>
- <!--
- <pre>1 0
- ---
- 1 1
- 1 0
- ---
- 2 0
- 2 1
- 2 2
- ---
- 3 0
- 3 1
- 3 2
- 3 3
- ---
- 4 0
- 4 1
- 4 2
- 4 3
- 4 4</pre>
- -->
- <!-- https://pt.wikipedia.org/wiki/Produtos_not%C3%A1veis -->
- <!--
- <span style="color: #0055AA;font-size:1.1em">Exemplo 2. Coeficientes da expansão do produto notável <tt>(a+b)^n</tt></span>
- <p>
- Suponhamos que precisemos fazer um program que computa os termos dos coeficientes da expansão de <tt>(a+b)^n</tt>.
- Ou seja, desejamos um programa que dado <tt>n</tt> liste quantos termos do tipo <tt>a^i x b^j</tt> existe, para cada
- <tt>i</tt> e cada <tt>j</tt> tais que <tt>i+j=n</tt>.
- o valor da combinação de <i>N</i> tomado <i>k</i> a <i>k</i>, ou seja,
- C(N,k) = N! / ( k! (N-k)!).
- Para isso percebe-se que é necessário implementar o cômputo de fatorial que será utilizado 3 vezes.
- Para facilitar a compreensão, podemos escrever um código com 3 variáveis auxiliares para armazenar, respectivamente,
- <i>N!</i>, <i>k!</i> e <i>(N-k)!</i>.
- </p>
- <pre>(a+b)^0 = 1 a^0 b^0
- (a+b)^1 = (a+b) (0 a^0 b^0 + 0 a^0 b^0) = 1 a^1 b^0 + 1 a^0 b^0
- (a+b)^2 = (a+b) (1 a^1 b^0 + 0 a^0 b^1) = 1 a^2 b^0 + 2 a^1 b^1 + 1 a^0 b^2
- (a+b)^3 = (a+b) (1 a^2 b^0 + 2 a^1 b^1 + 1 a^0 b^2) = 1 a^3 b^0 + 3 a^2 b^1 + 3 a^1 b^2 + 1 a^0 b^3
- (a+b)^4 = (a+b) (1 a^3 b^0 + 3 a^2 b^1 + 3 a^1 b^2 + 1 a^0 b^3) = 1 a^4 b^0 + 4 a^3 b^1 + 6 a^2 b^2 + 4 a^1 b^3 + 1 a^0 b^3a
- </pre>
- <pre>1
- 1 1
- 1 2 1
- 1 3 3 1
- 1 4 6 4 1
- </pre>
- -->
- <p>
- Leônidas de Oliveira Brandão<br/>
- http://line.ime.usp.br
- <!--
- 2017/04/06: primeira versao
- 2019/04/07: versao 2 (acrescentado novos paragrafos, melhorando explicacoes e frases)
- -->
- </p>
- </body>
|