sábado, 1 de outubro de 2016

Desenvolvi o WriteYourOwnGraph

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.

Vou começar esse post com uma ótima notícia. Desenvolvi uma ferramenta gratuita para o desenvolvimento de grafos.
Figura 1 - A reação de vocês

Meu planejamento para esta primeira versão dessa nova empreitada é disponibilizar o código construído no GitHub e depois transformá-lo em uma ferramenta web, onde você de qualquer parte do mundo possa acessar, construir seu grafo e exportar ele em forma de imagem, HTML, etc.

Mas da onde veio a motivação para construir o WriteYourOwnGraph ? Como tudo começou ? Muito tempo atrás estudei para uma prova de certificação de HTML 5 e acabei me dando mal, mas derrotas para um outro dia. Um dia lembrei que a especificação do HTML 5 possui diversas novas APIs e uma delas é para a criação de desenhos vetoriais, chamada SVG. Com ela é possível a criação de inúmeras formas geométricas. Agora, tudo o que eu precisava saber era como construir nós e arestas com ela. Para minha sorte, tais elementos se resumem a pequenas circunferências e linhas retas, respectivamente. Então, só o que eu precisava fazer era estudar a especificação da API e tudo ganharia forma.

Feito isso, a ferramenta WriteYourOwnGraph nasceu e adquiriu sua humilde versão 1.0 finalizada com sucesso. Pelo fato de ter sido construída com HTML 5, Javascript + JQuery e CSS é rápida e não é necessário instalar nada para executá-la na sua máquina, só basta o seu navegador. Então, é com muita felicidade que lhes apresento a primeira ferramenta desenvolvida pelo blog Preciso Estudar Sempre para o estudo e construção de grafos.
Figura 2 - Nossa que maravilha
Para executar a ferramenta, abra a página graph.html.

Se você usou e sentiu falta de algo, por favor não poupe palavras e escreva tudo o que pensa nos comentários. Faça parte da evolução.

Para baixar o código-fonte, clique aqui.

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários, me mande um e-mail ou, na nossa página do facebook.

E-mail: precisoestudarsempre@gmail.com
Facebook: https://www.facebook.com/precisoestudarsempre/
Canal Preciso Estudar Sempre: https://www.youtube.com/channel/UCUoW8dS38rXr0a5jWU57etA

Lista de figuras:
Figura 1 -  http://i.giphy.com/5Zesu5VPNGJlm.gif
Figura 2 - http://i.giphy.com/k7xgyFqsruqqc.gif
Leia Mais ››

quarta-feira, 17 de agosto de 2016

A criação de Donald Shell, o algoritmo ShellSort

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.

Em 1959, o cientista Donald Shell descobriu algo que mudaria para sempre o mundo. Nesta data, foi descoberto o algoritmo de ordenação conhecido como ShellSort. Ele tem como base um outro algoritmo já visto aqui no blog, o InsertionSort. Clique aqui para dar uma olhada, caso você não conheça.
Figura 1 - Donald L. Shell
A ordenação de Shell é boa para vetores de tamanho médio, talvez com até alguns milhares de itens, dependendo da particular implementação. Não é tão rápido quanto QuickSort (veremos aqui um dia) e outras ordenações O(N*logN), portanto, não é ótima para grandes massas de dados. Contudo, é muito mais rápida que as ordenações O(N²) como o SelectionSort (também não vimos ainda) e InsertionSort. O desempenho no pior caso não é muito pior que o do médio caso. Sua implementação é fácil, tornando seu código curto e simples. (Lafore R.)

O algoritmo citado como base do algoritmo ShellShort possui um problema: cópias demais. Imagine uma situação, onde em um conjunto um pequeno item está bem à direita, onde os itens grandes devem estar. Para mover esse pequeno item para seu devido lugar à esquerda, todos os itens intermediários, ou seja, os do meio desse intervalo, tem que ser deslocados em um espaço para a direita. Esse passo consome cerca de N cópias, apenas para um item. Nem todos os itens tem que ser movidos em N espaço completos, mas o item médio tem que ser movido em N/2 espaços, que tem N*N/2 deslocamentos para um total de N²/2 cópias. Logo, o desempenho dessa ordenação é O(N²) e isto é um grande problema. (Lafore R.)

E se existisse alguma forma de mover os itens menores para a esquerda sem mover os maiores para à direita ? Se sim, seria tal solução possível ?

Sim, é possível e é aqui onde a ordenação Shell ganha espaço. Seu segredo não é ordenar de forma direta como a ordenação Bolha faz, mas sim deixar o vetor quase ordenado para que a ordenação por inserção termine o trabalho. Mas como ele faz isso ?

Para que ele possa criar um vetor quase ordenado, ele precisa criar antes sub-vetores ordenados, os quais são independentes entre si. Tais são gerados através do uso de espaçamento entre elementos, também conhecido como incremento, e é normalmente conhecido pela letra h. Neste momento nossa cabeça com certeza se enche com mais dúvidas, pois ainda não temos idéia de como é esse espaçamento e como ele é gerado.

Figura 2 - Criação dos sub-vetores através do uso da técnica de espaçamento
Vamos entrar depois nos detalhes de como o espaçamento é gerado. Por enquanto, vamos definir que nosso h é 4, representado no passo 1. Note que no passo adiante, passo 2, três elementos são marcados pela cor vermelha no vetor e a distância entre eles é justamente o valor de h. Após marcados, o sub-conjunto ou sub-vetor é criado, e já pode ser ordenado através da troca de lugar de seus elementos, assim o passo 3 é representado. Este processo de marcação, criação do sub-vetor e ordenação se repete por todo o vetor, de tal forma que após o primeiro sub-conjunto ter sido finalizado todas as posições anteriormente marcados são acrescidas de uma casa à direita. Tal processo termina quando não existem mais elementos no vetor para formar sub-conjuntos, e assim, ganha vida os passos 4, 5, 6, 7 e 8.

Note que alguns sub-vetores não precisaram sofrer ordenação visto que, pela ordem natural de seus elementos eles já estavam ordenados entre si, e que alguns sub-conjuntos somente possuem dois elementos. Isto acontece pelo fato de que se contarmos quatro casas iniciando do último elemento marcado iremos ultrapassar o tamanho do vetor, logo se referindo a uma posição que não existe.

Contudo, e se o nosso vetor aumentasse de tamanho ? Nosso h continuaria sendo 4 ? E antes de pensarmos nisso, quem sugeriu este valor para esta variável ? Foi achismo ou fundamentado ? Bolacha ou biscoito ?

Se você acha que o valor 4 foi atribuído para h por um simples chute meu, não queria te dizer mas você está enganado. Tal foi gerado através da seguinte fórmula recursiva: h = 3*h + 1, onde inicialmente h=1 e, é conhecida como sequência de intervalo ou lacuna. A particular sequência mostrada é atribuída a Knuth (Lafore R.). É importante ressaltar que existem outras abordagens para geração da sequência de intervalos, mas nesta implementação do algoritmo de ordenação Shell utilizaremos essa.

No algoritmo de ordenação, a fórmula que gera a sequência é usada primeiro em um laço curto para descobrir o intervalo inicial. O valor 1 é usado para o primeiro valor de h e a fórmula já apresentada é aplicada para gerar a sequência 1,4,13,40,121,364, etc. Esse processo termina quando o intervalo se torna maior que o vetor. Para um vetor com 1000 elementos, o sétimo número da sequência, 1093, é grande demais. Assim, começamos o processo de ordenação com o sexto número maior, criando uma ordenação em 364. Então, a cada vez no laço externo da rotina de ordenação, reduzimos o intervalo usando o inverso da fórmula dada anteriormente: h = (h-1) / 3. (Lafore R.)

Essa fórmula inversa gera a sequência inversa 364,121,40,13,4,1. Começando com 364, cada um desses números é usado para a ordenação do vetor. Quando o vetor tiver sido ordenado em 1, o algoritmo terá terminado. (Lafore R.)

Antes de analisarmos o algoritmo uma última pergunta ainda não foi respondida. Porque a ordenação Shell é muito mais rápida que a ordenação por inserção na qual ele é baseada ? Quando h é grande, o número de itens por passagens (trocas) é pequeno e os itens se movem em longas distâncias (como visto acima). Isto é muito eficiente. Quando h fica menor, o número de itens por passagem aumenta, mas os itens já estão mais próximos de suas posições ordenadas finais, o que é mais eficiente para a ordenação por inserção. É a combinação dessas tendências que torna a ordenação Shell tão eficiente.(Lafore R.)

Eis nossa implementação em Java.

1:  private void shellSort(int[] array){  
2:       int begInterv, endInterv, h=1, temp;            
3:         
4:       while(h<=nElems/3){  
5:            h = h*3 +1;  
6:       }  
7:         
8:       while(h>0){  
9:            for (endInterv = h; endInterv < array.length; endInterv++) {  
10:                 temp = array[endInterv];  
11:                 begInterv = endInterv;  
12:                   
13:                 while (begInterv > h-1 && array[begInterv-h] >= temp) {  
14:                      array[begInterv] = array[begInterv-h];  
15:                      begInterv -= h;  
16:                 }  
17:                 array[begInterv] = temp;  
18:            }  
19:            h = (h-1)/3;  
20:       }  
21:  }  

Alguns pontos que valem a pena dar atenção.

Linha 4 até 6: Cria as partições baseado na sequência de intervalos de Knuth.
Linha 13 até 17: Algoritmo Insertion Sort já visto outrora.
Linha 13: O h-1 representa o fim do intervalo, e inner-h o valor dentro do intervalo.
Linha 15: Decrementa para percorrer outros intervalos.
Linha 19: Isto é feito para diminuir o tamanho das partições até chegar a 1.

Um mistério reside em torno da eficiência dessa ordenação, pois somente em casos especiais é possível averiguar tal. Com base em experimentos, existem várias estimativas que variam de O(N3/2)a O(N7/6), onde Nx/y representa a raiz y de N elevado a X, ou y√Nx . Assim se N for 100, N3/2 será a raiz quadrada de 1003, que é 1000. (Lafore R.)

E assim adicionamos mais um algoritmo para o nosso canivete de algoritmos de ordenação. Se você não sabe do que estou falando, dê uma clicada aqui e fique por dentro.

Para baixar o projeto utilize um dos links abaixo.

Link do Dropbox: https://www.dropbox.com/sh/8d5em1y1j9upke0/AAAvLf_sPeS4HRSBPH6YNHfra?dl=0
Link do GoogleDrive: https://drive.google.com/folderview?id=0BzDmhBY6luU6aThjTmx3OXNlUnM&usp=sharing

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários, me mande um e-mail ou, na nossa página do facebook.

E-mail: precisoestudarsempre@gmail.com

Referências
Lafore R.; ESTRUTURAS DE DADOS E ALGORITMOS EM JAVA; 2004

Figuras:
Figura 1 - http://goodnewsmag.org/wp-content/uploads/2015/11/shell2.jpg
Figura 2 - Criação própria
Leia Mais ››

quarta-feira, 6 de julho de 2016

Entrando em várias realidades com recursividade

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.

Você já viu o filme "Inception" ou, em português "A Origem" ?
Figura 1 - Filme Inception
Tá .... você que acompanha o blog deve estar se perguntando porque eu estou falando sobre esse filme, se o nosso foco aqui é outro.

Calma !!! Não viramos um blog de cinema, mas recomendo fortemente que você veja esse filme.

Se você viu, sabe que ele é bom, mas se não, fique tranquilo pois não darei spoilers. O filme trata sobre pessoas que conseguem sonhar e dentro deles, conseguem sonhar novamente. Resumidamente, eles sonham que estão sonhando e podem fazer isso de infinitamente.

Mas então ... o que isso tem haver conosco ? Onde isso se encaixa ?
Existe uma técnica de programação que utiliza esse mesmo conceito.
Sim, você não leu errado, e não, não iremos criar programas que são capazes de sonhar, pois caso fizéssemos a Skynet seria uma realidade.

Essa técnica se chama recursividade.
Figura 2 - Cena do filme Exterminador do futuro
A recursividade é algo mágico na computação. Ela nos permite abordar problemas de uma forma completamente diferente, mas deve ser usada com sabedoria, pois como já discutimos aqui, não existem balas de prata. Devemos saber quando e onde aplicar nossos novos conhecimentos.

Na computação existe uma técnica para abordar problemas, conhecida como "dividir para conquistar". Ela define que para um problema P muito grande, onde não é possível resolvê-lo de uma vez só, devemos dividi-lo por 2. Se essas metades (P/2) ainda forem grandes, devemos dividi-los novamente por 2, gerando P/4. Esse processo deve ser repetido até o momento em que o problema se torne pequeno o bastante para poder ser resolvido. A partir disso, a mesma solução é aplicada para todas as partículas geradas.

Mas o que determina quando devemos parar de dividir o problema ?

O caso base de uma solução recursiva é a parte responsável por essa tarefa. Toda abordagem recursiva deve possuir um caso base, pois caso contrário um loop infinito seria gerado. Contudo, como é possível construir um programa com tais características ?

Para que um algoritmo possa ter a inteligência recursiva, ele precisa implementar o seu caso base e para a divisão do problema, ele deve realizar chamadas a si mesmo. Porém, isso não soa errado? Como um método ou função vai se chamar ?

Ao contrário do que a maioria pensa, uma chamada à um método ou função é nada mais que uma transferência de controle de um método de origem para um de destino. No caso de uma chamada recursiva, é uma transferência para o seu próprio início.

Vamos analisar o seguinte exemplo !

Você quer criar um programa que calcule fatoriais para números inteiros positivos. É sabido que o fatorial de um número é a multiplicação dele por seus antecessores e que o fatorial de 0 é 1. Logo, 5! (lê-se cinco fatorial) é igual a 5 * 4 * 3 * 2 * 1, por exemplo.

No nosso programa não podemos engessar nossas soluções. Devemos ser capazes de receber um número e retornar seu fatorial, mas como fazer isso se não sabemos qual número será ? Contudo, se pararmos para pensar, chegamos a conclusão que um fatorial de um número n é igual a n multiplicado pelo fatorial de (n-1). Logo, é possível afirmar que 5! = 5 * 4!.

Mas, qual é o valor de 4! ? Utilizando o mesmo conceito, sabemos que 4! = 4 * 3!. Então, quando paramos ? Qual é o número que não precisa ser multiplicado pelo seu antecessor para que saibamos seu fatorial ? A resposta é clara, o número procurado é o 0. Acabamos de estabelecer o nosso caso base. Cada problema que pode ser resolvido de forma recursiva tem o seu. Cabe a você analisar e encontrá-lo.

Já podemos montar o nosso algoritmo recursivo, pois já sabemos quando devemos dividir o problema e quando devemos parar.

1:  public int fatRecursive(int valor){  
2:       if(valor == 0){  
3:            return 1;  
4:       }  
5:       return valor * fatRecursive(valor-1);  
6:  }  

Note que na linha cinco multiplicamos a variável valor pela chamada recursiva de fatRecursive, a qual recebe como parâmetro valor-1. Para que possamos entender melhor como nosso método funciona, vamos chamar essa primeira execução de e1. A partir da chamada, uma nova execução é feita, começando do início, gerando e2. Lembre-se que e1 ainda não terminou. Ela ainda está presa na linha cinco, pois está esperando e2 terminar.

A partir de e2 é gerado e3, e dessa forma são geradas outras execuções até o nosso caso base ser atingido. No momento em que é alcançado, as execuções que outrora estavam indo para frente agora voltam pelo caminho que fizeram e a última execução desbloqueia sua anterior, ou seja, se a última execução era e5, ela desbloqueia e4, que desbloqueia e3 e esse processo é repetido até e1, finalizando assim a execução completa do programa.

Agora que já temos total entendimento de como uma lógica recursiva funciona, nós devemos refletir sobre a seguinte questão: é possível resolver um problema recursivo com laços de repetição ? Alguns problemas sim mas, outros não.

Para o problema acima, poderíamos ter utilizado o seguinte código.

1:  public int fatNonRecursive(int valor){  
2:       int valorFat=1;  
3:       for (int i=1; i<=valor; i++) {  
4:            valorFat = valorFat * i;  
5:       }  
6:       return valorFat;  
7:  }  

E aí !? Qual usar ?

Como eu disse anteriormente, não existem balas de prata. Não existe maneira alguma de afirmar que a recursão é melhor que os laços de repetição, ou vice e versa. Cada abordagem tem suas características. Como nosso exemplo é algo bem simples, para fins de estudo, não justifica o uso da recursividade. A recursão é geralmente usada porque simplifica um problema conceitualmente, não porque é mais eficiente. Utilizá-la resulta em overhead, visto que várias chamadas ao mesmo método são feitas, causando assim uma lentidão se comparada à abordagem com laços (Lafore R.).

Deixo aqui disponível para download outros exemplos de algoritmos recursivos, são eles:

  • Algoritmo de potenciação numérica
  • Algoritmo para geração de palavras ao contrário
  • Algoritmo para solução do problema das torres de Hanoi
  • Algoritmo para cálculo de fatoriais

Vale a pena dar uma olhada.

Link do Dropbox: https://www.dropbox.com/s/cwwe0cr0idzaprn/AlgoritmosRecursivos.zip?dl=0
Link do GoogleDrive:https://drive.google.com/file/d/0BzDmhBY6luU6WVFkQUhTY3h6aHc/view?usp=sharing

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários, me mande um e-mail ou, na nossa página do facebook.

E-mail: precisoestudarsempre@gmail.com

Referências
Lafore R.; ESTRUTURAS DE DADOS E ALGORITMOS EM JAVA; 2004

Imagens:
Leia Mais ››

segunda-feira, 20 de junho de 2016

Minha monografia

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.

Como prometido no post anterior, vou falar aqui um pouco sobre a minha monografia e disponibilizar todo o material que recolhi para a sua construção, juntamente com ela no formado PDF.

Mas, acho que é importante discutir uma questão antes jogar os links para download.

Porque falar sobre minha monografia aqui no blog, se a missão dele não é essa ? Isso não é um pouco de auto-promoção ?

Bem, auto-promoção é de fato, até porque estarei falando aqui sobre um trabalho que eu desenvolvi, logo isso se torna óbvio. Contudo, acho que mesmo sendo uma auto-promoção ainda está alinhado com a missão do blog.

A missão que temos aqui é propagar o conhecimento e, em parte deixar nossa marca no mundo, por mais que seja bem pequena. Quando eu venho falar de algo que eu fiz, ainda estou espalhando conhecimento mas, agora, o foco não estará em trabalhos de terceiros e sim, no meu.

Se você não se sente confortável com esse formato, eu entendo. Mas, se você está e quer saber mais sobre o que eu fiz, por favor entre e sente-se, pois vamos começar agora.

O meu trabalho consiste em um estudo comparativo de técnicas para prevenção de erros de ponteiro nulo. Se você não sabe o que é um erro de ponteiro nulo ou uma Null Pointer Exception, dê uma bela clicada aqui.

Escolhi esse tema porque gosto de assuntos mais voltados para a área de compiladores (costumo dizer que são assuntos mais undergrounds) e, tenho uma certa atração por esse tipo específico de erro. Essa atração é explicada pela forma que esse erro é gerado, a lógica de programação empregada pelo programador.

Então, para prever onde esse erro pode acontecer, foram desenvolvidas ferramentas para realizar esse trabalho. Uma das mais conhecidas é a FindBugs, desenvolvida pela Universidade de Maryland.

Recomendo muito o uso dela.

Cada uma delas emprega um certo tipo de técnica para inferir os locais onde podem ocorrer os erros de ponteiro nulo. O meu trabalho compara duas dessas técnicas, explicando-as e posteriormente, realizando testes e comparando seus resultados.

Para atingir a conclusão dele foram necessários 4 meses de pesquisa e muitas noites mal dormidas.

Dividi ele em cinco etapas:
  1. Introdução: Aqui eu contextualizei o trabalho, para que o leitor possa entender onde ele se encaixa e de qual realidade ele nasceu.
  2. Fundamentação teórica: Explico toda a teoria necessária que possamos entender como as técnicas das ferramentas funcionam.
  3. O estudo comparativo: Explico e detalho o experimento que farei tendo como apoio todo o conhecimento reunido nas seções anteriores.
  4. Resultados: Apresento os resultados da seção anterior.
  5. Conclusão: Apresento a conclusão e dou sugestões para trabalhos futuros.
Figura 1 - Eu e o meu trabalho
Agradeço todo o apoio de meus amigos, familiares e a vocês, meus queridos leitores. Sem vocês nada disso seria possível.

Links para download
Google Drive: https://drive.google.com/file/d/0BzDmhBY6luU6OUMzVXRvejQtX1E/view?usp=sharing
Dropbox: https://www.dropbox.com/s/dz37xpwjvz3cbv9/Minha%20monografia.rar?dl=0

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários, me mande um e-mail ou, na nossa página do facebook.

E-mail: precisoestudarsempre@gmail.com
Facebook: https://www.facebook.com/precisoestudarsempre/
Canal Preciso Estudar Sempre: https://www.youtube.com/channel/UCUoW8dS38rXr0a5jWU57etA

Figuras
Figura 1 - acervo próprio
Leia Mais ››

quarta-feira, 1 de junho de 2016

Ausência, monografia e novidades

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.

Hoje temos que conversar !

Na verdade, quero falar com vocês sobre a minha ausência no período Março até Maio. Primeiramente, peço desculpas pela falta de posts e pelo fato de apresentar uma justificativa só agora (01/06). Mas, tudo isso aconteceu devido à minha pós-graduação.

Durante esses meses eu precisei focar totalmente na minha monografia, para conseguir terminar meu MBA em Engenharia de Software pela UFRJ. Esse trabalho vem tomando o meu tempo desde de Janeiro, mas ele só aflorou em Março pois, foi o início do prazo para a montagem do trabalho.

Várias horas de pesquisa e de sono mal dormido foram necessárias para que eu atingisse o resultado que queria e entregasse o trabalho.

Valeu a pena tanto esforço ? Não dava para entregar algo mais enxugado e simples ?

Sim, valeu a pena cada segundo investido e entregar algo inferior não era uma opção. No fim, acabei ficando satisfeito com o que foi entregue, pois para o período de tempo que eu tinha, ou seja, 2 meses, consegui entregar algo bom e equilibrado.

Estou escrevendo esse texto para vocês, porque acho que é, de fato, minha obrigação dar essa explicação. Afinal de contas, vocês me acompanham, não estou sozinho aqui. Pretendo voltar ao meu ritmo normal e fazer o que gosto: estudar, escrever, jogar meu playstation 3 e malhar.

Pretendo fazer um post exclusivo sobre o meu trabalho na pós-graduação, habilitando para download minha monografia, no formato PDF, e todo o material teórico que recolhi. Quero explicar um pouco a vocês o que eu fiz, e caso alguém tenha conhecimento na área, sinta-se a vontade para abrir uma discussão sobre o assunto.

Também trago novidades !!!

Resolvi virar Youtuber !!
Figura 1 - HUE HUE BR

Andei pensando muito e cheguei a conclusão que investir vídeos para alguns dos nossos posts, vai ser uma boa. Acho que vai melhorar a nossa experiência. Alguns assuntos são muito extensos para serem lidos e esse formato tem ganhado espaço na internet. Cada vez mais pessoas postam vídeos, então porque nós não podemos estudar de forma divertida via vídeo ?

Aproveito o jabá e já deixo aqui o link para o canal, clique aqui ou dê uma olhada lá no final do post.

Se inscreva e dê laike !

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários, me mande um e-mail ou, na nossa página do facebook.

E-mail: precisoestudarsempre@gmail.com
Facebook: https://www.facebook.com/precisoestudarsempre/
Canal Preciso Estudar Sempre: https://www.youtube.com/channel/UCUoW8dS38rXr0a5jWU57etA
Leia Mais ››

domingo, 29 de maio de 2016

Dicas de reutilização: Como fazer um HD externo a partir daquele seu notebook velho

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.

Você já pensou alguma vez na vida, que certos tipos de aparatos tecnológicos que você comprou poderiam ter sido feitos na sua casa ? E que o custo disso fosse algo muito baixo, algo do tipo R$ 15 ou, no máximo, R$ 40 ?

O que vamos aprender aqui hoje é como fazer seu próprio HD externo em casa.

Sim, você não leu errado. Eu estou afirmando que é possível fazer um HD externo em casa e gastando pouco. Se você estiver pensando que o nosso resultado final será algo capenga ou, difícil de usar, você está enganado. O HD que faremos aqui será igual aos que você compra nas lojas.
Figura 1 - HD externo da Seagate
Pensando na melhor forma de trazer esse conteúdo a vocês, eu fiz um vídeo explicando tudo o que precisamos fazer e ter. Fiz essa escolha, pois acho que assim o post não ficará maçante e vocês poderão ter a melhor experiência possível.

Parte 1 - Transformação do HD Sata

Parte 2 - Transformação do HD IDE



IMPORTANTE: O link para comprar a case IDE está no fim do post.

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários, me mande um e-mail ou, na nossa página do facebook.

E-mail: precisoestudarsempre@gmail.com
Facebook: https://www.facebook.com/precisoestudarsempre/

Link para comprar o case IDE: http://produto.mercadolivre.com.br/MLB-714837353-case-ide-usb-gaveta-externa-hd-notebook-25--_JM

Imagens:
Figura 1 - http://iacom1-a.akamaihd.net/produtos/01/00/item/114391/6/114391628_3GG.jpg
Leia Mais ››

segunda-feira, 4 de abril de 2016

Desvendando as pilhas

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.

Começamos esse post com a seguinte pergunta: como manter dados organizados da mesma forma que um vetor mas, acessando (inserindo e removendo) seus elementos somente pelo início ou fim dele ?

Você poderia responder que realizar essa tarefa é possível através do controle do índice do vetor visto que, os vetores oferecem acesso direto aos seus elementos. Contudo, isso seria algo muito complicado porque a cada inserção ou remoção, todos os elementos existentes teriam de ser deslocados para a esquerda, no caso da inserção, ou para a direita, no caso da remoção.

A idéia é: não queremos esse trabalho, queremos facilidade.

Para respondermos essa pergunta, é necessário ir além, entender um novo conceito. Precisamos entender  as pilhas ou, em inglês, stacks. É importante deixar claro que não estou falando das pilhas da Figura 1.
Figura 1 - Pilhas Duracell
Uma pilha, na ciência da computação, é uma estrutura de armazenamento de dados em que seu acesso só pode ser feito através de um topo. Imagine a seguinte situação: sua mãe te obrigou a lavar todos os pratos que estavam na pia. Depois de limpos, você começou a empilhá-los. Sua mãe viu você fazendo isso, te deu aquele belo esporro e mandou você secá-los. Quando você ia começar a secar, ela te deu um outro esporro, pois você tentou pegar um prato que estava no meio do pilha. Fazer isso, pode causar a quebra de todos os pratos que estão acima do que você tinha escolhido.

E aí, você apanhou.
Figura 2 - Sua mãe furiosa
É amigo ou amiga, a vida não é fácil. Mas então, como você seca os pratos ?

A resposta é simples. Para manter a integridade da sua pilha de pratos, você deve acessar somente o topo dela e assim, pegar um prato de cada vez, secá-lo e por numa nova pilha de pratos secos. Assim, você não apanha.
Figura 3 - Sua mãe feliz
Assim como é necessário manter a integridade da pilha de pratos, também é necessário manter a integridade da nossa pilha de dados. Logo, as mesmas regras devem ser seguidas. Contudo, uma pergunta ainda permanece: porque usar pilhas ?

Com elas é possível modelar soluções de uma forma simples e clara. Soluções, as quais se fossem modeladas com o uso de vetores resultariam em mais trabalho.

As pilhas são entidades mais abstratas que vetores e muitas outras estruturas de armazenamento de dados. Elas são definidas principalmente por sua interface, ou seja, as operações permitidas que podem ser executadas nelas. O mecanismo subjacente usado para implementá-las não é visível para o usuário.

Um problema que pode ser resolvido com o uso de pilhas, é o problema da palavra inversa. Esse problema consiste que dada uma determinada palavra, o resultado desejado é ela ao inverso, ou seja, para a palavra "casa", o retorno esperado é "asac".

Como resolver isso ?

Para que possamos começar, precisamos entender as duas operações básicas de uma pilha: push e pop.

Push significa empurrar logo, essa operação consiste em adicionar um item pelo topo na pilha. Pop significa remover um item pelo topo da pilha. Simples não !?
Figura 4 - As operações de uma pilha
Então, nosso problema da string reversa já pode ser resolvido. O que precisamos fazer é recuperar uma string digitada pelo usuário, empilhar letra por letra e depois, desempilhar completamente a pilha. Com isso, atingimos uma string reversa para qualquer uma fornecida pela usuário.

Já temos tudo o que precisamos para criar nossa implementação de pilha.

A classe Stack

1:  class Stack {  
2:         
3:       private char[] items;  
4:       private int counter;  
5:       private int size;  
6:    
7:       public Stack(int size){  
8:            items = new char[size];  
9:            this.size = size;  
10:            counter = 0;  
11:       }  
12:    
13:       public void push(char item) throws FullStackException{  
14:            if(isFull()){  
15:                 throw new FullStackException("Can't push. Stack is full.");  
16:            }  
17:            items[counter] = item;  
18:            counter++;  
19:       }  
20:    
21:       public char pop() throws EmptyStackException{  
22:            if(isEmpty()){  
23:                 throw new EmptyStackException("Can't remove. Stack is empty.");  
24:            }       
25:            counter--;  
26:            char item = items[counter];            
27:            return item;  
28:       }  
29:    
30:       public boolean isEmpty(){  
31:            return counter == 0;  
32:       }  
33:    
34:       public boolean isFull(){  
35:            return counter == size;  
36:       }  
37:    
38:       public char peek() throws EmptyStackException{  
39:            if(isEmpty()){  
40:                 throw new EmptyStackException("Can't remove. Stack is empty.");  
41:            }  
42:            return items[counter];  
43:       }  
44:  }  

Nossa pilha trata dados do tipo char visto que, precisamos tratar letra por letra. Os métodos push e pop fazem exatamente o que discutimos acima. Contudo, é importante ressaltar que na nossa implementação de método pop, o item removido é retornado para o usuário.

Os métodos isEmpty() e isFull() avaliam, respectivamente, se a pilha está vazia ou cheia. Essas verificações são feitas através do contador interno da pilha, a variável counter. Se esta variável for igual 0, a pilha está vazia e se for igual ao tamanho da pilha, o qual foi definido no construtor da classe, a pilha está cheia. O método peek() retorna o valor que está no topo da pilha, sem removê-lo.

Foram utilizadas exceções para todos os tratamentos de erro da pilha. Caso a pilha esteja vazia, em qualquer situação de acesso, ou seja, no uso do método pop e do método peek, uma EmptyStackException é propagada para informar ao usuário que não será possível completar a operação pois, a pilha está vazia. Uma FullStackException é propagada pelo método push, caso a pilha esteja cheia.

A classe ReverseString

1:  import java.util.Scanner;  
2:    
3:  class ReverseString {  
4:       public static void main(String[] args) {  
5:            ReverseString reverseString = new ReverseString();            
6:            String sourceString = reverseString.getSourceString();  
7:            char[] charArray = sourceString.toCharArray();  
8:            Stack stack = new Stack(sourceString.length());  
9:    
10:            for(int i=0;i<charArray.length;i++){  
11:                 try{  
12:                      stack.push(charArray[i]);  
13:                 } catch (FullStackException e) {  
14:                      e.printStackTrace();  
15:                      System.exit(1);  
16:                 }  
17:            }  
18:    
19:            System.out.print("String reversa: ");  
20:    
21:            while(!stack.isEmpty()){  
22:                 try{                      
23:                      System.out.print(stack.pop());  
24:                 } catch (EmptyStackException e) {  
25:                      e.printStackTrace();  
26:                      System.exit(1);  
27:                 }  
28:            }  
29:       }  
30:    
31:       public String getSourceString(){  
32:            Scanner input = new Scanner(System.in);  
33:            System.out.print("Informe a string: ");  
34:            return input.nextLine();  
35:       }  
36:  }  

Essa é a nossa classe cliente que utiliza nossa implementação de pilha. O método getSourceString() recupera o que foi digitado no console. No método main, utilizamos a string recuperada e realizamos a inversão, através dos métodos push e pop da pilha.

Vamos ver funcionando ?

Compile todos os arquivos .java.

Figura 5 - Compilação dos arquivos fonte
Execute e veja o resultado !!!

Figura 6 - Execução do programa
Terminamos !!

Link no dropbox:
Link no github: https://github.com/PrecisoEstudarSempre/Stacks.git

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários, me mande um e-mail ou, na nossa página do facebook.

E-mail: precisoestudarsempre@gmail.com
Facebook: https://www.facebook.com/precisoestudarsempre/

Referências bibliográficas:
Estruturas de dados e algoritmos em Java - Lafore, Robert

Imagens:
Figura 2 - http://noticiasdatv.uol.com.br/media/_versions/tichina_arnold_free_big.jpg
Figura 3 - http://www.clickcultural.com.br/foto/45/per_ft1_145.jpg
Figura 4 - http://botbench.com/blog/wp-content/uploads/2013/01/image5.png
Leia Mais ››