Toda a vez que você declara uma variável, o computador separa um espaço para ela em algum lugar da memória. Se você declara uma variável dentro de uma função, a função `main()` por exemplo, o computador a guardará em uma seção da memória chamada stack. Se a variável foi declarada fora de qualquer função, então ela será guardada na seção globals da memória.

Digamos que o endereço de memória que o computador alocou para a variável x foi 4100000. Se você atribui o número 4 a esta variável o computador vai armazenar o valor 4 no endereço 4100000.

Se você quer descobrir o endereço de memória de uma variável, você utiliza o operador &, como exemplificado abaixo.

//gist O endereço de uma variável indica aonde encontrar esta variável na memória, é por isso que um endereço também é chamado de ponteiro - porque aponta para uma variável na memória.

Passando variáveis como paramêtros para funções

É importante conhecer os ponteiros pois o C passa as variáveis por valor, ou seja, se você passa o valor de x como parâmetro para outra função esta função recebe uma cópia deste valor, e portanto qualquer alteração nesta variável não é refletida quando a função retorna. Veja o exemplo:
//gist

Para resolvermos este problema e atualizarmos o valor como esperado, precisamos passar como parâmetro o local onde a variável se encontra, para que a nossa função possa visitar o endereço (por exemplo) 4100000, encontrar o valor que está lá naquele espaço de memória e atualiza-lo corretamente.

Ponteiros tornam o compartilhamento de memória mais fácil, portanto esta é uma das principais utilizações dos ponteiros; permitir que funções compartilhem memória.

Armazenando ponteiros em variáveis – operador *

Antes de resolvermos nosso problema, veremos como podemos armazenar um ponteiro como uma variável.

Uma vez que já sabemos como obter um endereço de memória utilizando o operador `&`, podemos ter interesse em armazenar este endereço. Para isso existem as variáveis de ponteiro. Sua declaração é bastante simples:

//gist

Na primeira linha indicada, nós vemos que precedemos o nome da variável com o operador *, que significa que está variável é um ponteiro para um endereço que armazena um int – ou seja, também precisamos indicar o tipo que está armazenado no endereço.

Podemos entender que o operador `&` e `*` são opostos: & pega uma variável e nos diz o endereço em que ela está armazenada, enquanto que * pega um endereço e nos retorna o valor lá armazenado.

Se você deseja alterar o valor de um endereço a partir de uma variável de ponteiro, simplesmente faça uma atribuiçao assim `*x = 100`.

Agora podemos solucionar o problema com o código abaixo:

//gist

A única novidade no código acima, é a sintaxe para recebermos endereços (ponteiros) como parâmetros: `int* x`.

Dividir e Conquistar – Merge Sort

Quando um problema pode ser dividido em vários subproblemas que são iguais ao original, porém menores em tamanho você pode resolver estes problemas recursivamente, dividindo-os em partes cada vez menores, até que sua solução seja trivial, e depois juntar o resultado das resoluções em partes maiores para se obter a solução do problema original. O nome desta técnica é Dividir e Conquistar e utiliza a recursão. Você pode ler mais sobre recursão neste artigo e também neste outro, da Wikipedia.

Portanto os três passos que esta técnica envolve, em cada nível de recursão são.

  1. Dividir – Dividir o problema em um conjunto de sub problemas que são instâncias menores do mesmo problema principal.
  2. Conquistar – Resolver os subproblemas  a partir da mesma resolução recursiva. Se o tamanho do subproblema é pequeno o suficiente, aplicar uma resolução trivial.
  3. Combinar – Combinar a solução dos subproblemas na solução do problema original.

O algoritmo que veremos neste post se chama Merge Sort. Ele realiza o mesmo trabalho que o Insertion Sort porém em um tempo de execução bem menor, mas com o custo de memória da recursão.

O Merge Sort aplica os três passos descritos acima da seguinte maneira:

  1. Dividir – Divide a seqüência de “n” elementos em duas subseqüências de “n/2″ elementos cada.
  2. Conquistar – Ordena as duas subseqüências recursivamente com o próprio Merge Sort.
  3. Combinar – Combina (“Merge”, em inglês) as duas subseqüências ordenadas.

A condição de parada da recursão no Merge Sort acontece quando o array a ser ordenado têm tamanho 1. Ou seja, não há trabalho a ser feito e ele já se encontra trivialmente ordenado. Desta forma a operação principal deste algoritmo é a combinação dos arrays ordenados, no terceiro passo. Este procedimento de junção é chamado de “Merge” e é feito por uma função auxiliar.

Antes de exibirmos o algoritmo completo do Merge Sort vamos explicar e analisar um pouco melhor o algoritmo de merge.

Merge

Imagine que você têm duas pilhas de cartas de baralho e que inicialmente elas já se encontram ordenadas (com as menores cartas no topo). Nosso objetivo é formar uma única pilha a partir destas duas, de maneira que o resultado final continue ordenado. Intuitivamente o que fariamos seria:

  1. Pegar uma carta da pilha da direita e uma da pilha da esquerda, descobrir qual é a menor e coloca-la em uma terceira pilha (que representará o resultado final).
  2. Supondo que a carta menor foi a da pilha da direita, pegamos novamente outra carta da pilha da direita e comparamos novamente com a outra carta, que já estava em nossa mão (vinda da pilha esquerda).
  3. Repetimos os passos um e dois até que não haja cartas em ambas as pilhas. Se uma pilha se esgota primeiro, simplesmente continuamos pegando as cartas da pilha remanescente.

Como partimos da premisa que antes do processo as duas pilhas já se encontravam ordenadas, e que a cada iteração a pilha resultante também continua ordenada, podemos concluir que ao fim do processo nosso objetivo será alcançado.

O procedimeto de Merge leva tempo linear, null, ou seja, o tempo de execução deste algoritmo está relacionado ao número de itens sendo ordenados. Computacionalmente cada passo leva um tempo constante já que apenas uma comparação entre os elementos do topo é feita. Já que realizamos no máximo null passos, podemos afirmar que o algoritmo de Merge leva um tempo linear.

O código apresentado abaixo apresenta esta idéia com a diferença de que adicionamos a lista de números que estamos ordenando um valor máximo, para que evitemos verificar a cada iteração se a lista já está vazia ou se chegou ao seu limite (e assim evitamos exceções de “array fora dos limites”). Chamaremos este valor de “sentinela”.

Abaixo apresento o pseudo-código do algoritmo de merge. Esta imagem foi retirada do livro Introduction to Algorithms, que tem guiado esta série de posts. Em seguida apresento uma implementação em Java Script.

A linha 1 computa o tamanho n1 do sub array A[p..q], e a linha 2 computa o tamanho n2 do sub array A[q + 1..r]. Criamos os arrays “esquerda” e “direita”, de tamanhos n1 + 1 e n2 + 1 respectivamente. Na linha 3 a posição extra em cada array guarda o valor “sentinela”. O loop for nas linhas 4-5, e 6-7 copiam os sub arrays nos novos arrays “esquerda” e “direita”. Linhas 8-9 colocam as sentinelas no fim dos arrays.  As linhas 10 a 17 representam os passos para reordenar os valores dentro do array principal.

A implementação em Java Script abaixo contém algumas modificações em relação aos indices já que os arrays em Java Script iniciam em zero, e a notação do livro adotou o inicio em 1. O array não é retornado pois arrays são passados por referência. Se você usa o browser Chrome é muito fácil testar este código, caso use Windows aperte Control – Shift – J, caso use Mac, aperte Command – Option – J então copie e cole este código no console de testes do navegador. Passe alguns valores para a função Merge e verifique o resultado.

Abaixo segue uma versão em C#, bem mais legivel, na minha opinião, porém trabalha recebendo as duas listas já prontas ao invés de extrai-las do array principal.

Merge Sort

Abaixo mostramos o pseudo código do Merge Sort e em seguida sua implementação em JavaScript. Perceba que na linha 3 o valor da divisão é truncado.


Em resumo, o algoritmo divide a coleção em duas, e chama o algoritmo recursivamente em ambas as partes, até que seu tamanho seja unitário, nessa ocasião a recursão inicia seu retorno e o algoritmo de merge as junta em um pedaço ordenado maior, a medida que a recursão volta todas as partes ordenadas são juntadas até que o array principal esteja completamente ordenado.

A análise de algoritmos do tipo “Dividir e Conquistar” será feita no próximo post da série de análise de algoritmos.

This post is a translation to portuguese from a response to a thread at Stackoverflow.com, by the user Action Jake.

Esta é uma tradução de uma resposta a seguinte pergunta:

Como Albert Einstein disse: ‘Se você não pode explicar isto a uma criança de seis anos de idade, você realmente não entende isto’. Bem, eu tentei explicar os closures do Java Script para um amigo de vinte e sete anos de idade e falhei completamente. Como você explicaria os closures do Java Script para uma criança de 6 anos de idade estranhamente interessada neste assunto?

Se você consegue ler em inglês recomendo a leitura de todas as respostas neste link. A resposta que mais me agradou foi:

Era uma vez uma princesa… Ela vivia em uma terra maravilhosa, cheia de aventuras. Certa vez ela encontrou um príncipe que cavalgava em um unicórnio, lutava com dragões, encontrava criaturas falantes e muitas outras coisas fantasticas…
Mas ela sempre tinha que voltar para o seu mundo cheio de chatisses e pessoas crescidas…
E eventualmente ela contava para as pessoas suas últimas aventuras como uma princesa…
Mas tudo o que as pessoas viam era uma menininha…
Contando historias sobre mágica e fantasia…
E embora os “crescidos” soubessem de pricesas verdadeiras, eles nunca acreditariam em unicórnios e dragões porque eles nunca puderam velos. Os “crescidos” diziam que isso só existia na imaginação da menininha…

Mas nós sabemos a verdade; que a menininha com uma princesa por dentro é na verdade uma princesa com uma menininha por dentro.


Resumindo: Os closures podem ser usados para manter dados privados do código externo. Nesse caso os adultos (qualquer código fora do escopo da função princesa) não tem acesso ao “unicornio” (dados do closure), embora ele realmente exista e a princesa (função) tenha acesso completo a ele. Portanto a lição a criança de 6 anos de idade é que um closure é como o “mundo encantado” que você pode ir eventualmente e que contém coisas que não são visiveis para o “mundo real”.

Funções são objetos em Java Script. Elas podem ser armazenadas em variaveis, ter atributos e tambem ser passadas como parâmetros para funções. Elas são usadas para controlar o escopo de variáveis (leia mais neste outro post), além de permitir variáveis privadas e métodos, é por isso que falaremos um pouco de funções.

Funções

Abaixo seguem quatro maneiras de se declarar uma função em Java Script.

A maneira 1 representa uma declaração normal, uma função nomeada, que pode ser chamada assim: teste(); – a maneira 2, também é uma função nomeada, porém atribuída a uma variável – pode ser chamada da mesma forma, teste();. Já a maneira 3 é uma função anônima, pois ela não foi nomeada, entretanto, como foi atribuída a uma variável ela pode ser chamada exatamente da mesma maneira que as outras, teste();. A maneira 3 apresenta uma redução de redundância em relação a maneira 2. A maneira 4 atribui a função a propriedade teste do objeto, e é invocado de maneira similar: objeto.teste();.

Funções anônimas auto-executaveis

É possível executar uma função anônima imediatamente após o Java Script ter sido carregado.
A unica ação necessária para isto é englobar a declaração entre parenteses,  e adicionar os parenteses de chamada de função logo após, o que indica ao interpretador Java Script para executar a função com quaisquer parâmetros fornecidos dentro dos parenteses.

Entendendo Closures

Para entendermos as funções mais a fundo, precisamos entender os closures - que é um conceito chave no Java Script. Recomendo a leitura do post “Explicando os closures do Java Script para uma criança de 6 anos de idade”.

O java script permite definir funções dentro de funções, ou seja, funções dentro de outras. Estas funções internas, tem acesso irrestrito a parâmetros, variáveis e outras funções internas da função que o fecha, ou seja, da sua função externa. Veja o exemplo abaixo.

Até aqui não temos novidades. Mas o que aconteceria se expusessemos publicamente uma função interna de escopo() como retorno da função? Ela continuaria tendo acesso aos valores internos de escopo() – via referência destes objetos – uma cópia não é criada. A criação acidental de closures é a causa de muitos vazamentos de memória. O exemplo acima não representa um closure porque não retorna uma função de dentro de uma outra, enquanto o exemplo abaixo exemplificam os closures.:

O design pattern “module”

Em Java Script, o padrão Module é usado para emular o conceito de classes de tal maneira que possamos ter membros privados e públicos, como variáveis e métodos – e dessa forma proteger partes do objeto do escopo global. Isso resulta na diminuição da probabilidade de conflito entre nossas funções e variáveis com outras definidas em outras bibliotecas importadas em nossas páginas, além de nos permitir controle total sobre a API do objeto. Vamos analisar o primeiro exemplo:

Este post ainda esta sendo finalizado, aguarde

© 2013 Renato Gama Suffusion theme by Sayontan Sinha