<!-- 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>