martes, 8 de septiembre de 2020

Nociones Básicas de lenguaje de programación JAVA

Estructura Básica de JAVA

Java es un lenguaje muy funcional, fue diseñado para ser parecido a C++, concretamente para facilitar un rápido y fácil aprendizaje para un gran número de programadores.

Java elimina muchas de las características de C++, para mantener la simplicidad del lenguaje añade caracteristicas muy útiles como el "garbage collector"(reciclador de memoria dinámica o recolector automático de basura).

No es necesario preocuparse de liberar memoria, el recolector de basura se encarga de ello. Como es una aplicación que se ejecuta "en segundo plano" (programa que se ejecuta en memoria de forma concurrente a otras a otroas aplicaciones) y es de baja prioridad, cuando entra en acción, permite liberar bloques de memoria muy grande, lo que mejora la gestion de la memoria.

Java reduce en un 50% los errores más comunes de programación con lenguajes como C y C++ al eliminar michas de las características de éstos, entre las que destacan:

    1.- Aritmética de punteros
    2.- registros (struct)
    3.- definición de tipos(typedef)
    4.- macros(#define)
    5.- necesidad de liberar memoria(free)


Lenguaje Orientado a Objetos.

Java implementa la tecnología básica de C++ con algunas mejoras y elimina algunas cosas para mantener el objetivo de la simplicidad del lenguaje.

    * Java trabaja con sus datos como objetos y con interfaces a esos objetos.
    * Soporta las tres características propias de la orientación a objetos:
        1. encapsulación
        2. herencia
        3. polimorfismo

    * Las plantillas de objetos son llamadas, clases y sus copias, instancias.
    * Las instancias necesitan ser construidas y destruidas en espacios de memoria.
    * Java incorpora funcionalidad llamada resolución dinámica de métodos(no existe en C++).
    * C++ trabaja con librerías dinámicas(DLLs) que obligan a recopilar la aplicación cuando se retocan las funciones que se encuentran en su interior. Este inconveniente es el resulto por Java, mediante una interfaz específica llamad RTTI(RunTime Type Indentification, Identificación de Tipo en Tiempo de Ejecución). Las clases en Java tiene una representación que permite a los programadores interrogar en tiempo de ejecución por el tipo de clases y asociar dinamicamente la clase con el resultado de la búsqueda.

Distribuido

Java se ha construido con grandes capacidades de interconexión TCP/IP. Existen librerías de rutinas para acceder e interactuar con protocolos como el protocolo HTTP y el protocolo FTP. Esto permite a los programadores acceder a la información a través de la red con tanta facilidad como a los ficheros locales.

Lo que realmente hace Java es proporcionar las librerías y herramientas para que los programas puedan seer distribuidos, es decir, que se corran en varias maquinas  interactuando entre ellas.

Verificaciones


Java realiza verificaciones en busca de problemas en tiempo de compilación y en tiempo de ejecución.

- La comprobación de tipos en java ayuda a detectar errores, lo antes posible, en la fase de implementación. En Java la declaración de un método se hace justo antes de su definición indicando en ella los tipos de datos pasados como argumentos y el tipo de dato envuelto, reduciendo así las posibilidades de error.
- Maneja la memoria para eliminar las preocupaciones por parte del programador de la liberación de memoria o de la corrupción de memoria.
- Implementa los arrays como bloques de memoria contiguos con comprobación de límites, para evitar la posibilidad de sobrescribir o corromper memoria a causa de punteros que señalan a zonas equivocadas. Estas características reducen drásticamente el tiempo de desarrollo de aplicaciones Java.
- Para asegurar el funcionamiento de la aplicación, realiza una verificación de los bytecodes. No es el código máquina directamente entendible por el hardware, pero ha pasado la fase del compilador, como el análisis de instrucciones, orden de operaciones, etc., y ya tiene generada la pila de ejecución de órdenes.

Arquitectura neutral

Para establecer Java como parte integral de la red, el compilador Java compila su código a un fichero objeto de formato independiente de la arquitectura de la máquina en que se ejecutará. Cualquier máquina que tenga el sistema de ejecución(run-tine o maquina virtual Java) puede ejecutar ese código objeto, sin importar en modo alguno la máquina en que ha sido generado. Actualmente existen sistemas run-time para Solaris, SunOs, Windows, Linux, Irix, Aix, Mac, Apple.

El código fuente de Java se "compila" a un código de bytes de bajo nivel independiente de la máquina. Este código(bytecodes) está diseñado para ejecutarse en máquina hipotética que es implementada por un sistema run-time, que sí es dependiente de la máquina. Lo que es realmente dependiente de la máquina es la Máquina Virtual Java (JVM) y las librerías fundamentales, que también nos permitirían acceder directamente al hardware de la máquina.

Seguro

En el lenguaje Java, las características como los punteros o el casting(cambio de tipo de un objeto) implícito que hacen los compiladores de C y C++ se eliminan para prevenir el acceso ilegal a la memoria. El lenguaje C, por ejemplo, tiene lagunas de seguridad importantes. Por ejemplo, al desarrollar en C se utilizan punteros junto a operaciones aritméticas. Esto le permite al programador que un puntero haga referencia a un lugar conocido de la memoria y puede sumar o restar algún valor, para referirse a otro lugar de la memoria. Si otros programadores conocen nuestras estructuras de datos pueden extraer información confidencial de nuestro sistema.

El código Java pasa unas pruebas antes de ejecutarse en un máquina. El código se pasa a través de un verificador de bytecodes que comprueba el formato de los fragmentos de código y aplica un probador de teoremas para detectar fragmentos de código ilegal(código que falsea punteros, viola derechos de acceso sobre objetos o intenta cambiar el tipo o clase de un objeto.

Portable

Más allá de la portabilidad básica por ser de arquitectura independiente, Java implementa otros estandares de portabilidad para facilitar el desarrollo. Los enteros son siempre enteros además de ser enteros de 32 bits en complemento de 2, también Java construye sus interfaces de usuario a través de un sistema abstracto de ventanas de forma que las ventanas puedan ser implantadas en entornos unix, Pc o Mac.


Interpretado

El intérprete Java(sistema run-time o máquina virtual Java) puede ejecutar directamente los bytecodes. No obstante, Java es más lento que otros lenguajes de programación, más que C++, ya que debe ser interpretado antes de ser ejecutado en vez de ser ejecutado directamente como sucede en cualquier programa compilado de forma tradicional.



En definitiva Java para conseguir ser un lenguaje independiente del sistema operativo y del procesador que incorpore la máquina utilizada, es tanto interpretado como compilado. El código fuente escrito en cualquier editor se compila generando los bytecodes. Este código intermedio es de muy bajo nivel, pero sin alcanzar las instrucciones en código máquina propias de cada plataforma. Ese mismo código es el que se puede ejecutar sobre cualquier plataforma. Para ello hace falta la JVM, que sí es completamente dependiente de la máquina y del sistema operativo, que interpreta dinámicamente los bytecodes. Con este sistema es fácil crear aplicaciones multiplataforma, pero para ejecutarlas es necesario que exista la JVM correspondiente al sistema operativo utilizado.

Multihebrado

Al sr multihreaded(multihebrado), Java permite muchas actividades simultáneas en un programa. Los hilos(a veces llamados, procesos ligeros), que son piezasa independientes(líneas de ejecución) de un proceso. No ha de confundirse un hilo con un proceso. Hilo es la unidad mínima ejecutable, mientras que proceso es la unidad mínima planificable. Un proceso está formado por hilos. Al estar los hilos construidos en el lenguaje, son más fáciles de usar y más robustos que sis homólogos en C o C++.

Por ejemplo, es posible que el interfaz gráfico de una aplicación(las ventanas, botones, controles, etc) se ejecutan en un hilo(thread) encargado de dibujarlos en la pantalla, mientras que las acciones que llevan asociadas se ejecutan en otro, consiguiendo una respuesta más rápida de la aplicación.
 


Dinámico

Java se beneficia todo lo posible de la tecnología orientada a objetos. Java no intenta conectar todos los módulos que comprenden una aplicación hasta el tiempo de ejecución. Las librerías nuevas o actualizadas no paralizan las aplicaciones actuales(siempre que mantengan el API anterior).

Java también simplifica el uso de protocolos nuevos o actualizados. Sí un sistema dado ejecuta una aplicación Java sobre la red y encuentra una pieza de la aplicación que no sabe manejar, Java es capaz de traer automaticamente cualquiera de esas piezas que el sistema necesita para funcionar.

JDK

La herramienta básica para empezar a desarrollar aplicaciones o applets en Java es el JDK(Java Development Kit o kit de desarrollo Java), que consiste basicamente en un compliador y un intérprete(JVM) para la línea de comandos. No dispone de un entorno de desarrollo integrado (IDE), pero es suficiente para aprender el lenguaje y desarrollar pequeñas aplicaciones. Es todo lo que necesitamos para poder compilar y ejecutar nuestros programas Java.

La API de Java está formada por varios paquetes que se distribuyen con el JDK como biblioteca de clases. Estos paquetes proporcionan una interfaz común para desarrollar aplicaciones Java en todas las plataformas Java.

La API de Java está formada por varios paquetes de desarrollo principales y de un paquete de soporte de depuración. Estos paquetes son colecciones de objetos relacionados. Por ejemplo, vienen separados según se trate de programas de ventas, mini-aplicaciones, software de conexión, etc.
 

Nivel léxico, gramatical y semántico de los lenguajes de programación

Uno de los objetivos de los lenguajes de programación de alto nivel es que son fáciles de entender, por eso todos los lenguajes de programación de alto nivel como Java, C, C++, python y otros tienen una estructura similar al lenguaje natural(ingles), Aunque existen construcciones concretras que las pueden diferenciar entre diferentes lenguajes.

Una de las diferencias y que no esta a simple vista se concretra en la forma de compilar e interpretar que se traducen en el texto escrito en el lenguaje de alto nivel a código máquina y ejecutable por el ordenador.

Como ya hemos visto, Java no es muy distinto de cualquier otro lenguaje, incluido el castellano. Casi todos hemos empezado aprendiendo las letras que forman parte del alfabeto de nuestra lengua, y en todo caso, usemos el método que usemos para aprender a leer, seguro que tenemos que usarlas de una en una al escribir un texto. Igual ocurre en Java. El lenguaje se construye sobre un alfabeto, un conjunto de símbolos que son los que podemos emplear para escribir nuestros programas.

Tradicionalmente, el alfabeto que venía usándose para todos los lenguajes de programación era el código ASCII de 8 bits, lo que permitía un conjunto de 256 símbolos distintos, que más o menos coincidían con los caracteres usados en la mayoría de los países occidentales. Pero eso imposibilitaba el uso de caracteres propios de los idiomas de muchos paises.

Para evitar ese problema, Java ha incorporado como alfabeto el código Unicode, que es un nuevo código estándar de E/S que isa 16 bits para representar cada carácter, lo que da un número suficientemente amplio(2^16 = 65.536 posibles caracteres distintos).

El código Unicode es totalmente compatible con ASCII, ya que respeta el código ASCII, lo que realmente hace es colocar 8 bits a cero delante para convertirlos en Unicode.

Nivel léxico: Tokenización

Inicialmente el compilador percibe nuestro fichero de texto con el código fuente en Java como una secuencia de caracteres, que le van siendo suministrados uno a uno, tal como se escribre y de los cuales  tiene que ir extrayendo las palabras con significado propio(tokens, en la jerga informatica que se refiere a la compilación). Este proceso e el análisis léxico de nuestro programa, y se denomina tokenización. Para ello, algunos caracteres tendrán que interpretarse de forma especial por el compilador, indicándole que ahí termina un token y comienza otro. Son los separadores, y así tenemos la primera noción léxica que introduciremos, la de separador.

En Java se consideran separadores los siguientes caracteres/grupos de caracteres:

- Espacio en blanco: es el típico separador. Es un carácter más del alfabeto(Unicode), pero cuya misión es introducir un corte entre dos palabras.
- Return: es el símbolo de return, intro o nueva línea. Es un separador con exactamente las mismas propiedades que un espacio en blanco.
- Tabulador: denominado también otro separador.
- comentario: un comentario cumple asimismo las funciones de separador. Los comentarios poseen la función de documentación sobre el código fuente, pero desde el punto léxico del lenguaje, son más que separadores.
- Operador: actúan como separadores que permiten defierenciar tokens distintos, pero con la peculiaridad de que se mantienen a sí mismos como tokens significativos, por lo que no podrán ser sustituidos por ningún otro separador. Son las excepción a la regla que mecionamosen los párrafos siguientes.

Ejemplo: la expresión a=3+4 incluye dos operadores (= y +) que permiten separar el nombre de la variable "a" del primer operadory éste del segundo operando 4, generando la secuencia de tokens a, =, 3, +, 4.

Durante la primera fase de análisis del código fuente, el análisis léxico, el compilador de Java aplica la siguiente regla:

Toda secuencia de uno o más separadores se sustituye por un único espacio en blanco que establece una división entre tokens.

Tipos de tokens


no todos los tokens son iguales ni todos deben ser interpretados de  la misma forma.  Los tipos de tokens en Java son:

- Palabras reservadas
- Operadores
- Literales
- Identificadores

Ejemplo:

int numero = 8;

* int, se usa para indicar lo que ponemos a continuación va a ser una variable de tipo entero. El lenguaje Java le da un significado concreto, y no podemos usarla con ningún otro sentido que no sea el reservado para ella por el lenguaje. Por eso es una palabra reservada.
* numero, es el nombre que he decidido darle a la variable. Cualquier elemento que yo use en el lenguaje debe tener un nombre (variables, constantes, clases, métodos, etc.) y ese nombre lo elige el programador que lo crea, de esta forma nos permite identificarlo y distinguirlo de otros elementos. Es un identificador.
* =, es un operador de asignación que indica ina operación o acción a realizar, en este caso se le asigna a la variable con nombre "numero" el valor que lleva detras de "=", en este caso 8.
* 8, representa el valor concreto que forman la categoría de los enteros en Java. Es un valor literalm un literal de tipo entero.


Tipos básicos o primitivos

Cada uno de los tipos básicos tiene una palabra reservada asociada que permite declarar variables como pertenecientes a ese tipo. En la unidad 2, donde hablábamos de los tipos de datos y sus características, ya hicimos una introducción a los tipos básicos de Java.

La declaración de una variable consiste en indicar el tipo de los valores que va a almacenar seguido del nombre de esa variable, que nos va a permitir referirnos de forma cómoda al valor que contiene.

Java es un lenguaje fuertemente tipado, que quiere decir que no podremos usar ninguna variable si previamente no le hemos asignado un tipo. Además el lenguaje se preocupa de comprobar exhaustivamente que cualquier valor que se intenta guardar en una variable sea exactamente del mismo tipo que la variable. Además, cada tipo nos proporciona una serie de operaciones disponibles para usar con los datos de ese tipo.

Los tipos primitivos en Java son: boolean, char, byte, short, int, long, float, double.

Literales de los tipos primitivos.

Cada tipo tiene por tanto su conjunto finito de valores válidos. Por ejemplo, de tipo Boolean, sólo permite dos valores posibles, que son true(verdadero) y false(falso). Estos valores, aunque son literales, funcionan en la práctica como palabras reservadas, ya que no pueden usarse con ningún otro significado en Java. Por ejemplo, no se puede crear una variable llamada true, o llamada false.

Por otro lado, los literales de tipo Char se representan mediante comillas simples. Cualquier símbolo Unicode situado entre comillas simples es un literal de tipo carácter. Ejemplo: 'A', 'a', '7', '?', '+'.

Los símbolos de Unicode correspondientes al ASCII (primer bloque de 8bits que forman Unicode, concretamente los 8bits menos significativos de unicode), se pueden representar asimismo en notación octal. Para ello, conociendo el valor octal de un carácter(N), el literal correspondiente se representa como'\N'. Por ejemplo si sabemos que tanto en ASCII como unicode, la letra A(mayúscula) es el símbolo número 65, y que 65 en octal es 101, podemos representar esta letra como '\101'.

Cualquier símbolo Unicode se puede especificar utilizando su valor hexadecimal, precedido por la secuencia de escape \u. Por ejemplo, si sabemos que el símbolo Pi mayúscula del griego se representa en unicode con el valor hexadecimal 0370, dicho carácter se especificará en Java como el literal '\u0370'.

Literales de tipo entero.

Un número entero se define como una secuenncia de dígitos(0-9) que puede llevar delante signo o no. La única precaución que hay que tener es que para cada tipo primitivo de enteros(byte, short, int y long) se deben utilizar valores comprendidos en su rango de valores permitidos.

Los números enteros se pueden especificar en tres notaciones: decimal, octal y hexadecimal.

Todos los números por defecto están escritos en base decimal, a excepcion de los números que comienzan por 0, que se consideran que están en base octal, y los que comienzan por Ox o 0x(es decir, de un cero seguido de una x, mayúscula o minúscula), que se tratan como números en hexadecimal, pudiendo contener en dicho caso, además de dígitos(los números del 0 al 9), las letras A, B, C, D, E, o F(en mayúsculas o minúsculas). Ejemplo:

- 90
- 0132
- 0X5A
- 0x5A
- 0X5a
- 0x5a

Literales de tipo real.

En Java hay dos tipos de números reales, que son float y double. La diferencia básica es la precisión de la representación y el rango de valores representables, debido al mayor tamaño de double, que usa 64 bits en vez de 32 de float para representar cada valor.

La diferencia entre float y double es que la utilización de un mayor número de bits en el caso de double permite aumentar tanto la precisión de mantisa(número de dígitos significativos) como los valores extremos del exponente.

En cualquier caso, lo que aquí nos interesa es la especificaciónv de literales de este tipo.

Un número se considera real si posee decimales(lo que supone que se utilice el punto de separación entre la parte entera y la decimal, aunque cualquiera de las dos puede estar vacía), o poseé un exponente(el exponente se introduce mediante la letra e ò E), o va precedido por una letra identificativa de tipo(que es f ò F para float y d ò D para double).

Según el criterio de utilización del punto(en ingles se utiliza el punto para la separación de los decimales, por lo que a estos números se los denomina de punto  flotante), los siguientes números son reales y no enteros:

- 137.36
- 137.
- .56

Si la parte entera o decimal es nula(valor igual a 0) se puede omitir. Mientras que 137 es número entero, 137. es un número real.

Un número real se puede representar como NeM(equivalente NEM), donde N es un número real y M es un entero. Ejemplo de números son todos reales y poseen en mismo valor:

- 1584.0
- 1584E1 = 1584e1 = 1584e+1
- 15.84E2
- 1.584E3
- .1584E4

Tipo float:

- 16f
- 16F
- 115.94f

Tipo double:

- 56823d
- 56823D
- 98.234d

es importante tener en cuenta que en caso de no especificar la letra postfija correspondiente al tipo float, todos los números reales se consideran de tipo double por defecto.

miércoles, 4 de septiembre de 2019

Recursividad y función Fibonacci

Concepto

La recursividad es un mecanismo que parece no aportar nada y que le resulta muy es algo complicado de entender. Se puede resumir como un mecanismo muy potente con el que se consigue que determinados programas mejoren su rendimiento y se escriban de forma más simple y clara, facilitando la actualización y modificación del código.

En programación, se usa el término recursividad para referirnos a procedimientos o métodos que contienen en su implementación llamadas a sí mismo, es decir un método es recursivo si para ejecutarse se llama así mismo.

Aspecto de la declaración de un metodo recursivo:

metodoRecursivo() {
      Sentencia;
}

metodoRecursivo();


En negrita es la llamada que el método se hace a sí mismo.

Pero, si para ejecutar a un método hay que haer uina llamada al mismo método que a su vez hará una llamada al mismo método, y así sucesivamente, ¿no habremos entrado en un bucle infinito que hará que nunca terminemos?.

No, si las cosas se hacen bien, aunque evidentemente, es posible hacerlas mal.

¿Qué debemos tener en cuenta a la hora de saber si un método recursivo está bien definido?

* Además de la llamada al propio método, deben existir otras sentencias en la definición del método que establezcan al menos un caso base, es decir, un caso para el que se conoce la solución, y para el que la llamada al método puede devolver un valor o terminar la ejecución sin necesidad de volver a invocar de nuevo al propio método.
* Cada nueva llamada al propio método debe reducir el problema, es decir, debe acercarnos más al caso base.
 * La solución debe ser correcta para los caso no base. Es decir, debemos ser capaces de expresar correctamente la solución de un caso a partir de las soluciones de casos menos complejas, para poder construir la solución de cualquier caso a partir de las soluciones del caso base.

ejemplo del calculo factorial de un número:

El factorial de un número entero positivo "n" se representa como n!(que se lee como "n factorial").

Sabemos que dado un número entero positivo, el factorial se puede definir de dos formas:

Definición Iterativa:

Como el producto de todos los números enteros compredidos entre 1 y el propio número:

n!=n*(n-1)*(n-2)*...*3*2*1. Además, por convenio, para extender la definición también al cero, se define 0!=1

Para calcular el factorial de esta forma, tenemos que repetir un ciclo en el que se vaya acumulando el producto del númeropor el anterior, hasta que lleguemos a uno.

Definición Recursiva

Como el producto del propio número por el factorial del número anterior.

Es evidente que (n-1)! = (n-1)* (n-2)* ... *3*2*1, lo que nos lleva a poder escribir que n!=n*(n-1)!, siendo además 0!=1 igual que antes. Acabamos de hacer una definición por recurrencia del factorial de un número. "el factorial de un número vale 1 si el número es 0, y el producto del propio número por el factorial del anteior, en otro caso."

Para calcular el factorial de esta forma, es como su le encargáramos la tarea a una persona que no sabe calcular el valor final del factorial n, pero sabe que n * (n-1)*(n-2)!, y le pregunta a su vez a a otra persona el valor de (n-2)!, y así sucesivamente hasta que llegamos a preguntarle a una persona el valor de 0! y esa persoba responde que 0! = 1. A partir de ese momento, la persona que le preguntó tiene toda la información para calcular 1!, lo calcula, y le responde  a la persona que le preguntó, que completa el cálculo de 2!, y así sucesivamente, hasta llegar a la persona que calcular(n-1)!, que completa el cálculo y le proporciona su valor a quien le preguntó, que multiplica ese valor por n y obtiene el valor de n!.

La recursividad ees una poderosa herramienta en programación y ofrece una alternativa a las soluciones iterativas complejas de algunos algoritmos.

Razones por las que se debe usar la recursividad:

1.- Los problemas resultan más fáciles de resolver que con estructuras iterativas.
2.- Proporciona soluciones más simples.
3.- Proporciona soluciones elegantes.


En el caso del factorial se a podido ver que habia dos posibles soluciones, una iterativa y ora recursiva.

No solo no existen casdos en los que sea obligatorio, sino que siempre existe una solución iterativa, y ésta siempre será más eficiente, es decir, necesitará cosumir menos recursos de memoria y de CPU, y obtendrá la solución en menos tiempo.

Lo que ocurre es que a veces, por la naturaleza recursiva del problema, no es fácil encontrar la solución iterativa, o resulta complicada y difícil de entender.

La recursividad se debe usar cuando sea realmente necesaria, es decir, cuando no exista una solucion iterativa simple. Cuando las dos soluciones son fácilmente explicables, siempre será preferible la solución iterativa.

Por otro lado, la recursividad requiere hacer una asignación dinámica de memoria, que no es posible en todos los lenguajes de programación. Ejemplos, en Java si lo es.

Cada vez que se hace una llamada al método recursivo, es necesario guardar en la pila(stack) todos los datos de la llamada que aún no ha terminado(son las variables de memoria que tenga definidas el método), lo que se conoce como entorno volátil de esa ejecución.

Essto incluye almacenar en la memoria todos los valores de registro del microprocesador, para poder restablecerlos posteriormente y continuar por el mismo punto de la ejecución de esa llamada que se habia dejado.

Cuando hay muchas llamadas sucesivas, estas llamadas se van almacenando en la pila(stack), de forma que se irán retirando de ella en orden contrario al que entraron o se introdujeron.

Ejemplo, el último en entrar es el primero en salir. (estructura LIFO: Last-IN, First-OUT)



Todo ese tiempo de CPU dedicado a:

  • Guardar los datos de cada ejecución en la pila,
  • cargar los de la nueva llamada para cuando al final ésta termine
  • ir de nuevo sacando esos valores de la pila en orden contrario a como se introdujeron
  • para restaurarlos en la CPU y completar la llamada, 
Es la causa resonsable de que las soluciones recursivas requieran mucho más tiempo de cpu y mucha más memoria.

Con una solución iterativa sólo tendremos un método ejecutándose, por lo que sólo necesitaremos memoria para una copia de cada una de las variables que usa ese método. Por otro lado, no hay que hacer ninguna operación de almacenar en la pila los datos de ejecución para posteriormente reanudarla, ya que la llamada al método iterativo se ejecuta hasta finalizar, sin intervención de ninguna otra llamada.

Función Fibonacci

LA función Fibonacci calcula el valor del término n-ésimo de la sucesión de Fibonacci, en la que el primer término es 0, el segundo1 y a partir de ahí, cada nuevo término se calcula como la suma de los dos inmediatamente anteriores. Puede también inicializarse los dos primeros términos con otros valores, pero lo que no cambia es la forma de obtener los sucesivos términos.

Por tanto, la función Fibonacci se puede definir como:
  • Fibonacci(n)=n, si n=0 ó n=1
  • Fibonacci(n)=Fibonacci(n-1)+Fibonacci(n-2), si n > 1
Ejemplo de código Java:

static int fibonacci (int n) {  
   if ((n == 0) || (n == 1)) { 
     return 1;
   } else {
     return fibonacci(n-1) + fibonacci(n-2); 
   }
}

El algoritmo recursivo que implementa a esta función es muy sencillo de hacer, pero muy ineficiente. Si intentas calcular el término 40 de la sucesión, verás cómo tarda un tiempo considerable en resolverlo, y si introduces un valor más alto, posiblemente parezca que el ordenador se ha bloqueado debido a lo que tardar en responder. Este tiempo depende del ordenador en el que se ejecuta.

martes, 4 de junio de 2019

Busqueda de datos en un ArrayList

Codigo ejemplo interesante para realizar busqueda de datos dentro de un ArrayList:

       if (!listaTenistas.isEmpty()) {
          int i = 0;
          Tenista t = null;
          boolean encontrado = false;
          while (!encontrado && i < listaTenistas.size()) {
             t = listaTenistas.get(i);
             if (t.getNombre().equalsIgnoreCase(jtNombreBuscar.getText())) {
                encontrado = true;
             } else {
                i++;
             }
          }
          if (encontrado) {
             indice = i;
             mostrarDatosTenista(indice);
          } else {
             JOptionPane.showMessageDialog(this, "El tenista " + jtNombreBuscar.getText() + " no ha sido encontrado", "No encontrado", JOptionPane.ERROR_MESSAGE);
          }

       } else {
          JOptionPane.showMessageDialog(this, "No se han añadido tenistas a la lista", "Lista vacía", JOptionPane.ERROR_MESSAGE);
       }


listaTenista es el ArrayList, de la clase Tenista, en la que se crea un metodo privado que devuelve el nombre escrito en el campo de texto jtNombreBuscar. metodo llamado getNombre().

martes, 12 de septiembre de 2017

Cambiar dirección de rutina

Este ejemplo hace lo mismo que este, pero POKEamos antes a la dirección 50000 una vez ensamblada el byte 162 o cualquier otro que probemos.

Este sería el listado para ensamblar:

   ; Listado 3: Rellenado de pantalla
   ; recibiendo el patron como parametro.
   ORG 40000
 
   ; En vez de 162, ponemos en A lo que hay en la
   ; dirección de memoria 50000
   LD A, (50000)
 
   ; El resto del programa es igual:
   LD HL, 16384
   LD (HL), A
   LD DE, 16385
   LD BC, 6911
   LDIR
   RET

Por lo tanto una vez cargado en ensamblador, hacemos esto.

POKE 50000, 162 


Lo que hace este poke es llamar llamar a la direccion 50000 y le ponemos el byte 162, para que cuando hagamos un "RANDOMIZE USR 40000" en la dirección 50000 este el byte 162 y puede funciona la rutina que se a programado en "ORG 40000".

jueves, 7 de septiembre de 2017

Entendiendo las primeras instrucciones

Este será mi primer articulo para entender el lenguaje de ensamblador(ASM) del microprocesador Z80.

Bien pues vamos a ver que es lo que hace este código:

ORG 40000
   LD HL, 16384
   LD A, 162
   LD (HL), A
   LD DE, 16385
   LD BC, 6911
   LDIR
   RET

Este código se compila, se ensambla en unos bytes, que es binario. Lo que hace este código es poner toda la pantalla con el byte 162.

Paso a paso:

ORG 40000 Es la directiva del compilador, donde se indica dónde debe compilar el código que le sigue.

LD HL, 16384 carga el valor 16384 en el registro HL(16384 =  $4000 -> H=$40 L = $00)

Hay que decir que a partir de 16384 se ubica la memoria de video. Los primeros bytes son el contenido de pixels activos y no activos, y a partir de 22528 se ubican los atributos de color [1 byte por cada bloque de 8x8]

LD A, 162 carga el valor 162 en el registro A(A=162).

LD(HL), A mete el valor de A en la dirección de memoria apuntada por HL. El HL vale $4000(Hexadecimal) y A vale 162; quiere decir que en la dirección de memoria 16384 va a meter el valor 162.

LD DE, 16385 carga 16385 en el registro DE. 16835 es la siguiente dirección de vídeo, es decir, la siguiente dirección después de 16384 que se metio un 162(LD A, 162). DE suele ser un registro de "DE"stino.

LD BC, 6911 carga 6911 en el registro BC. BC suele ser el registro contador, tanto en B como en C.

LDIR es un incrementador repetido, quiere decir LoaD Incrementando Repetido. que lo que hace es leer el contenido de memoria apuntada por DE, incrementa HL y DE, decrementa BC, y se repite hasta que BC valga 0.

Es decir, lo que hace en general este  código es arrastrar el 162 desde la primera dirección de memoria que le indicamos, en este caso el primer valor de HL 16384($4000 hexadecimal), pasando por 16385, 16386, etc...

Y en ámbitos generales lo que hace este programa es rellenar con el valor 162, 6912 bytes, empezando por el primer valor de HL(16384). En lugar de 6192(pixeles más atributos de color), rellena solo 6144(BC = 6143).

Otro ejemplo:

ORG 40000
   LD HL, 0
   LD DE, 20000
   LD BC, 16
   LDIR
   RET

En este ejemplo, copia 16 bytes desde la posición "0" de memoria, a la posición 20.000 de memoria.

lunes, 27 de marzo de 2017

El objeto Event

Los generadores de eventos necesitan información adicional para procesar las tareas que tienen que realizar. Si una función procesa, por ejemplo, el evento click, lo más probable es que necesite conocer la posición en la que estaba el ratón en el momento de realizar el click; aunque esto quizás tampoco sea muy habitual, a no ser que estemos programando alguna utilizadad de tipo gráfico.

Lo que sí es más común es tener información adicional en los eventos del teclado. Ejemplo, cuando pulsamos una tecla nos interesa saber cuál ha sido la tecla pulsada, o si tenemos a mayores alguna tecla especial pulsada como Alt, Control. Etc.

Los navegadores gestionan de forma diferente los objetos Event. Por ejemplo, en las versiones antiguas de Internet Explorer, el objeto Event forma parte del objeto window, mientras que en otros navegadores como Firefox, Chrome, etc., para acceder al objeto Event lo haremos a través de un parámetro, que escribimos en la función que gestionará el evento.

Nota: En Internet Explorer no se puede programar que un evento se dispare en la fase de captura, ya que el IE los eventos siempre burbujean.




El DOM, sus propiedades y métodos

El DOM (Modelo de Objeto del Documento), es esencialmente una interfaz de programación de aplicaciones (API), que proporciona un conjunto estándar de objetos, para representar documentos HTML y XML, un modelo estándar sobre cómo pueden combinarse dichos objetos, y una interfaz estandar para acceder a ellos y manipularlos. A través del DOM los programas pueden acceder y modificar el contenido, estructura y estilo de los documentos HTML y XML, que es para lo que se diseño principalmente. El responsable del DOM es el W3C.

El DOM está separado en 3 partes / niveles:

DOM Core - modelo estándar para cualquier documento estructurado.
DOM XML - modelo estándar para documentos XML.
DOM HTML - modelo estándar para documentos HTML.

El DOM HTML es un estándar dónde se define cómo acceder, modificar, añadir o borrar elementos HTML.

En el DOM se definen los objetos y propiedades de todos los elementos del documento, y los métodos para acceder a ellos.

Hay que tener en cuenta que muchas de las funciones de Internet Explorer 4 y posteriores del modelo de objetos, no forman parte de la expecificación DOM W3C.



El DOM transforma todos los documentos XHTML en un conjunto de elementos, a los que llama nodos. En el HTML DOM cada nodo es un objeto. Estos nodos están conectados entre sí y representan los contenidos de la página web, y la relación que hay entre ellos. Cuando unimos todos estos nodos de forma jerárquica, obtenemos una estructura similar a un arbol, por lo que muchas veces se suele referenciar como árbol DOM, "árbol de nodos", etc.

Ejemplo de arbol de nodo:


Los rectángulos del gráfico representa un nodo del DOM, y las lineas representan la relación entre los nodos. La raíz del árbol de nodos es un nodo especial, denominado "document". A partir de ese nodo, cada etiqueta XHTML se transformará en nodos de tipo "elemento" o "texto". El nodo inmediantamente superior será el nodo padre y todos los nodos que están por debajo serán nodos hijos.

Algunos de los tipos de nodos:

document, es el nodo raíz y del que derivan todos los edemás nodos del árbol.
element, representa cada una de las etiquetas XHTML. Es el único nodo que puede contener atributos y el único del que pueden derivar otros nodos.
Attr, con este tipo de nodos representamos los atributos de las etiquetas XHTML, es decir, un nodo por cada atributos = valor.
Text, es el nodo que contiene el texto encerrado por una etiqueta XHTML.
comment. representa los comentarios incluidos en la página XHTML.

Los otros tipos de nodos pueden ser: CdataSection, documentFragment, documentType, EntityReference, entity, Notation y processingInstruction.

Acceso a los nodos.

El accededr a un nodo específico lo podremos hacer empleando dos métodos: a través de los nodos padres, o bien usando método de acceso directo. A través de los nodos padres partiendo del nodo raiz, accediendo a los nodos hijos y asi sucesivamente hasta llegar al elemento que queriamos.

GetElementsName(), Esta función obtiene una colección, que contiene todos los elementos de la página XHTML cuyo atributo name coincida con el indicado como parámetro.

var elementos = document.getElementsByName("apellidos");

Una colección no es un array, aunque se le parezca mucho, ya que aunque puedas recorrerla y referenciar a sus elementos como un array, no se pueden usar métodos de array, como push o pop, en la colección.

getElementById(), esta función es la más utilizada, ya que nos permite acceder directamente al elemento por el ID. Entre paréntesis escribiremos la cadena de texto con el ID. Es muy importante que el ID sea único para cada elemento de una misma página. La función nos devolverá únicamente el nodo buscando.

var elemento= document.getElementById("apellidos");

NOTA: Cuando escribimos los atributos en HTML, da igual que se pongan en mayúsculas o minúsculas, pero si estamos trabajando con XHTML debermos escribirlos siempre en minúculas , ejemplo type, value, size, etc.

El tamaño máximo de lo que se puede almacenar en un nodo de texto, depende del navegador, por lo que muchas veces, si el texto es muy largo, tendremos que consultar varios nodos para  ver todo el contenido.

En el DOM de HTML, para acceder al valor de un nodo de texto, o modificarlo, es muy común ver la porpiedad innerHTML. Esta propiedad es de microsoft al igual que outerHTML. Aunque hasta HTML5 no estaba en el estándar de W3C esta propiedad está soportada por todos los navegadors, y es muy rápida en su uso para hacr modificaciones, del contenido de un DIV por ejemplo. outerHTML no se soporta en el estandar.

Para modificar el contenido del nodo, modificaremos la propiedad nodeValue.

document.getElementsByTagName("p")[0].childNodes[1].firstChild.nodeValue = "Texto MODIFICADO";

Lo que hace es cambiar la cadena de texto "texto HTML", por "texto MODIFICADO".

Creación y borrado de nodos

La creación y borrado de nodos fue uno de los objetos para los que se creó el DOM. Podremos crear elementos y luego insertarlos en el DOM, y la actualización quedará reflejada automaticamente por el navegador. También podremos mover nodos ya existentes, simplemente insertándolo en cualquier otro lugar del árbol del DOM.

Ten en cuenta que cuando estemos creando nodos de elementos, el elemento debe estar en minúsculas. Aunque en HTML esto daría igual, el XHTML sí que es sensible a mayúsculas y tendrá que ir, por lo tanto, en minúsculas.

para crear se usan los metodos createElement(), createTextNode() y appendChild(), que nos permitirán crear un elemento, crear un nodo de texto y añadir un nuevo nodo hijo.


//Creamos tres elementos nuevos: p, b, br var elementoP = document.createElement('p'); var elementoB = document.createElement('b'); 
var elementoBR = document.createElement('br'); 

 //Le asignamos un nuevo atributo title al elementoP que hemos creado. elementoP.setAttribute('title','Parrafo creado desde JavaScript'); 

 //Preparamos los nodos de texto var texto1 = document.createTextNode('Con JavaScript se '); var texto2 = document.createTextNode('pueden realizar '); var texto3 = document.createTextNode('un monton'); var texto4 = document.createTextNode(' de cosas sobre el documento.'); 

 //Añadimos al elemento B los nodos de texto2, elemento BR y texto3. elementoB.appendChild(texto2); elementoB.appendChild(elementoBR); elementoB.appendChild(texto3); //Añadimos al elemento P los nodos de texto1, elemento B y texto 4. elementoP.appendChild(texto1); elementoP.appendChild(elementoB); elementoP.appendChild(texto4); 

 //insertamos el nuevo párrafo como un nuevo hijo de nuestro párrafo document.getElementById('parrafito').appendChild(elementoP);

Propiedades y métodos de los objetos nodo

Todo lo que se utiliza en la actualidad es el DOM 2, y algunas de las extensiones de DOM 3. Por que el DOM 3 esa creado para XHTML.

Nota: hay que procurar evitar utilizar funciones especificas de DOM 1 por los problemas inesperados que se pueddad dar sobre todo si se utilizan en XHTML.

 Gestión de eventos

Cuando el usuario hace algo, se produce un evento. Tambien habrá algunos eventos que no están relacionados directamente con acciones de usuario: ejemplo: el evento carga de un documento, que es el caso de "load", se dispara automaticamente cuando un documento ha sido cargado en el navegador.

Existen diferencias, en lo que es la gestión de eventos, por unos navegadores u otros. Esas diferencias provocan que los programadores tengan que tener mucha precaución con los métodos y propiedades que usan, dependiendo del navegador que ejecutará la página de javascript.

un ejemplo de solución es cross-browser para asignar evento, independientemente del navegador utilizado. Ejemplo:

function crearEvento(elemento, tipoEvento, funcion) {
    if (elemento.addEventListener) {
        elemento.addEventListener(tipoEvento, funcion, false);
    } else if (elemento.attachEvent) {
        elemento.attachEvent("on" + tipoEvento, funcion);
    } else {
        elemento["on" + tipoEvento] = funcion;
    }
}
var miparrafo = document.getElementById("parrafito");
crearEvento(miparrafo, 'click', function () {
    alert("hola")
});
el método addeventListener() no funciona en Internet Explorer 8, pero sí en cambio está ya implementado en Internet Explorer 9. Si tienes Windows XP no podrás usar ese evento ya que la última versión de IE que se puede usar en Windows XP es la 8 y no deja instalar la versión 9. La recomendación es que tengas las ultimas versiones de Mozilla Firefox o Google Chrome.