You are on page 1of 93

MANUAL DE PRÁCTICAS

PROGRAMACIÓN ORIENTADA A
OBJETOS

MTI MARIO SALVADOR


CASTRO ZENIL

ACADEMIA DE INGENIERÍA
BIOMÉDICA
1. PRESENTACIÓN
Estimado Estudiante:

El aprendizaje y la generación de conocimiento de las ciencias de la computación siempre han


estado ligados a la experimentación y la repetición. La programación que debe de conocer un
ingeniero biomédico está ligada a la interacción con hardware y lenguajes enfocados al
trabajo en ingeniería.

Las prácticas de este manual proceden de diferentes fuentes adaptados a los objetivos que se
propone alcanzar dentro de la asignatura de Programación Orientada a Objetos y a los
recursos didácticos disponibles en el laboratorio del ITSPA.

Las instrucciones para la realización de cada práctica están bien detalladas, las ideas
conceptuales de cada experiencia vienen acompañadas de una breve introducción y se agregan
los retos que deberán de cumplirse.

Al estudiante se le recomienda que antes de comenzar una práctica lea las instrucciones
generales del manual, así también la exposición teórica para que alcance una comprensión
clara de lo que va a hacer. Se le recomienda, además que conserve un registro de la
experiencia y de las medidas para la elaboración de su respectivo reporte.

2. OBJETIVO GENERAL
El objetivo del Laboratorio de Programación Orientada a Objetos es familiarizarse con el
paradigma de objetos en programación empleando para esto la codificación en lenguaje de
programación Python y LabView.

Este manual tiene la intención de servir como una guía para el desarrollo de prácticas y para la
escritura de reportes de laboratorio.

Esperamos que la experiencia en este curso despierte tu curiosidad y te permita orientar tu


entusiasmo hacia la resolución de problemas relacionados con la biomédica.
3. PRÁCTICAS

3.1 Unidad 1

PRÁCTICA Clases
#1.1
De acuerdo a lo aprendido en cátedras cree una clase en Python que imprima los datos básicos
de un equipo medico de uso hospitalario en un inventario llamado equipoMedico.

3.2 Unidad 2
Una clase Fraccion

Un ejemplo muy común para mostrar los detalles de la implementación de una clase definida
por el usuario es construir una clase para implementar el tipo abstracto de datos Fraccion. Ya
hemos visto que Python proporciona una serie de clases numéricas para nuestro uso. Hay
ocasiones en las que, sin embargo, sería más apropiado ser capaz de crear objetos de datos que
―luzcan‖ como fracciones.

Una fracción como 3/5 consta de dos partes. El valor de arriba, conocido como el numerador,
puede ser cualquier entero. El valor de abajo, llamado el denominador, puede ser cualquier
entero mayor que 0 (las fracciones negativas tienen un numerador negativo). Aunque es
posible crear una aproximación de punto flotante para cualquier fracción, en este caso nos
gustaría representar la fracción como un valor exacto.

Las operaciones para el tipo Fraccion permitirán que un objeto de datos Fraccion se comporte
como cualquier otro valor numérico. Necesitamos ser capaces de sumar, restar, multiplicar y
dividir fracciones. También queremos ser capaces de mostrar fracciones usando la forma
estándar de ―barra‖, por ejemplo 3/5. Además, todos los métodos de fracciones deben
devolver resultados en sus términos menores de modo que, sin importar el cálculo que se
realice, siempre terminemos con la forma más simplificada.

En Python, definimos una nueva clase proporcionando un nombre y un conjunto de


definiciones de métodos que son sintácticamente similares a las definiciones de funciones.
Para este ejemplo,

class Fraccion:
#los métodos van aquí

Proporciona el esqueleto para definir los métodos. El primer método que todas las clases
deben proporcionar es el constructor. El constructor define la forma en que se crean los
objetos de datos. Para crear un objeto Fraccion, tendremos que proporcionar dos piezas de
datos, el numerador y el denominador. En Python, el método constructor siempre se llama
__init__ (dos subrayados antes y después de init) y se muestra en el Programa 2.

Programa 2

class Fraccion:
def __init__(self,arriba,abajo):
self.num = arriba
self.den = abajo

Observe que la lista de parámetros formales contiene tres elementos (self, arriba, abajo). self
es un parámetro especial que siempre se utilizará como una referencia al objeto mismo. Debe
ser siempre el primer parámetro formal; no obstante, nunca se le dará un valor de parámetro
real en la invocación.

Como se describió anteriormente, las fracciones requieren dos piezas de datos de estado, el
numerador y el denominador. La notación self.num en el constructor define que el objeto
fraccion tenga un objeto de datos interno llamado num como parte de su estado. Del mismo
modo, self.den crea el denominador. Los valores de los dos parámetros formales se asignan
inicialmente al estado, permitiendo que el nuevo objeto fraccion conozca su valor inicial.

Observe que la lista de parámetros formales contiene tres elementos (self, arriba, abajo). self
es un parámetro especial que siempre se utilizará como una referencia al objeto mismo. Debe
ser siempre el primer parámetro formal; no obstante, nunca se le dará un valor de parámetro
real en la invocación. Como se describió anteriormente, las fracciones requieren dos piezas de
datos de estado, el numerador y el denominador. La notación self.num en el constructor define
que el objeto fraccion tenga un objeto de datos interno llamado num como parte de su estado.
Del mismo modo, self.den crea el denominador. Los valores de los dos parámetros formales
se asignan inicialmente al estado, permitiendo que el nuevo objeto fraccion conozca su valor
inicial.

Para crear una instancia de la clase Fraccion, debemos invocar al constructor. Esto ocurre
usando el nombre de la clase y pasando los valores reales para el estado necesario (note que
nunca invocamos directamente a __init__). Por ejemplo,

miFraccion = Fraccion(3,5)

Crea un objeto llamado miFraccion que representa la fracción 3/5 (tres quintos). La Figura
muestra este objeto tal como está implementado ahora.
Lo siguiente que debemos hacer es implementar el comportamiento que requiere el tipo
abstracto de datos. Para comenzar, considere lo que sucede cuando tratamos de imprimir un
objeto Fraccion.

>>> miF = Fraccion(3,5)


>>> print(miF)
<__main__.Fraction instance at 0x409b1acc>

El objeto fraccion, miF, no sabe cómo responder a esta solicitud de impresión. La función
print requiere que el objeto sea convertido en una cadena para que se pueda escribir en la
salida. La única opción que miF tiene es mostrar la referencia real que se almacena en la
variable (la dirección en sí misma). Esto no es lo que queremos.

Hay dos maneras de resolver este problema. Una de ellas es definir un método llamado
mostrar que permitirá que el objeto Fraccion se imprima como una cadena. Podemos
implementar este método como se muestra en el Programa 3. Si como antes creamos un objeto
Fraccion, podemos pedirle que se muestre, en otras palabras, que se imprima en el formato
apropiado. Desafortunadamente, esto no funciona en general. Para que la impresión funcione
correctamente, necesitamos decirle a la clase Fraccion cómo puede convertirse en una cadena.
Esto es lo que necesita la función print para hacer su trabajo.

Programa 3

def mostrar(self):
print(self.num,"/",self.den)
>>> miF = Fraccion(3,5)
>>> miF.mostrar()
3/5
>>> print(miF)
<__main__.Fraction instance at 0x40bce9ac>
>>>

En Python, todas las clases tienen un conjunto de métodos estándar que se proporcionan pero
que podrían no funcionar correctamente. Uno de ellos, __str__, es el método para convertir un
objeto en una cadena. La implementación predeterminada para este método es devolver la
cadena de la dirección de la instancia como ya hemos visto. Lo que necesitamos hacer es
proporcionar una ―mejor‖ implementación para este método. Diremos que esta
implementación reescribe a la anterior, o que redefine el comportamiento del método.

Para ello, simplemente definimos un método con el nombre __str__ y le damos una nueva
implementación como se muestra en el Programa 4. Esta definición no necesita ninguna otra
información excepto el parámetro especial self. A su vez, el método construirá una
representación de cadena convirtiendo cada pieza de datos de estado internos en una cadena y
luego colocando un caracter / entre las cadenas usando la concatenación de cadenas. La
cadena resultante se devolverá cada vez que se solicite a un objeto Fraccion que se convierta
en una cadena. Observe las diversas formas en que se utiliza esta función.

Programa 4

def __str__(self):
return str(self.num)+"/"+str(self.den)
>>> miF = Fraccion(3,5)
>>> print(miF)
3/5
>>> print("Comí", miF, "de la pizza")
Comí 3/5 de la pizza
>>> miF.__str__()
'3/5'
>>> str(miF)
'3/5'
>>>

Podemos redefinir muchos otros métodos para nuestra nueva clase Fraccion. Algunas de los
más importantes son las operaciones aritméticas básicas. Nos gustaría poder crear dos objetos
Fraccion y luego sumarlos usando la notación estándar ―+‖. En este punto, si intentamos
sumar dos fracciones, obtendremos lo siguiente:

>>> f1 = Fraccion(1,4)
>>> f2 = Fraccion(1,2)
>>> f1+f2

Traceback (most recent call last):


File "<pyshell#173>", line 1, in -toplevel-
f1+f2
TypeError: unsupported operand type(s) for +:
'instance' and 'instance'
>>>

Si nos fijamos atentamente en el error, veremos que el problema es que el operador ―+‖ no
entiende los operandos para Fraccion.

Podemos corregir este error agregándole a la clase Fraccion un método que redefina el método
asociado a la adición. En Python, este método se llama __add__ y requiere dos parámetros. El
primero, self, siempre es necesario, y el segundo representa el otro operando en la expresión.
Por ejemplo,

f1.__add__(f2)
pedirá al objeto Fraccion f1 que sume el objeto Fraccion f2 a sí mismo. Esto se puede escribir
en la notación estándar, f1 + f2.
Dos fracciones deben tener el mismo denominador para poder ser sumadas. La forma más
fácil de asegurarse de que tienen el mismo denominador es simplemente utilizar el producto
de los dos denominadores como un denominador común de modo que
a/b+c/d=ad/bd+cb/bd=ad+cb/bd. La implementación se muestra en el Programa 5. La función
de adición devuelve un nuevo objeto Fraccion con el numerador y el denominador de la suma.
Podemos usar este método escribiendo una expresión aritmética estándar que involucre
fracciones, asignando el resultado de la adición e imprimiendo nuestro resultado.

Programa 5

def __add__(self,otraFraccion):

nuevoNum = self.num*otraFraccion.den + self.den*otraFraccion.num


nuevoDen = self.den * otraFraccion.den

return Fraccion(nuevoNum,nuevoDen)
>>> f1=Fraccion(1,4)
>>> f2=Fraccion(1,2)
>>> f3=f1+f2
>>> print(f3)
6/8
>>>

El método de adición ya funciona como queremos, pero una cosa podría ser mejor. Note que
6/8 es el resultado correcto (14+12) pero no está en la representación de ―términos menores‖.
La mejor representación sería 3/4. Con el fin de estar seguros de que nuestros resultados estén
siempre en los términos menores, necesitamos una función auxiliar que sepa cómo simplificar
las fracciones. Esta función tendrá que buscar el máximo común divisor, o MCD. Podemos
entonces dividir el numerador y el denominador por el MCD y el resultado se simplificará a
los términos menores.

El algoritmo más conocido para encontrar un máximo común divisor es el Algoritmo de


Euclides. El Algoritmo de Euclides establece que el máximo común divisor de dos enteros m
y n es n si n divide de forma exacta a m. No obstante, si n no divide exactamente a m,
entonces la respuesta es el máximo común divisor de n y el residuo de m dividido entre n.
Aquí simplemente proporcionaremos una implementación iterativa. Tenga en cuenta que esta
implementación del algoritmo del MCD sólo funciona cuando el denominador es positivo.
Esto es aceptable para nuestra clase Fraccion porque hemos dicho que una fracción negativa
estará representada por un numerador negativo.

def mcd(m,n):
while m%n != 0:
mViejo = m
nViejo = n

m = nViejo
n = mViejo%nViejo
return n

print(mcd(20,10))
Ahora podemos utilizar esta función para ayudar a simplificar cualquier fracción. Para poner
una fracción en los términos menores, dividiremos el numerador y el denominador por su
máximo común divisor. Por lo tanto, para la fracción 6/8, el máximo común divisor es 2.
Dividiendo arriba y abajo por 2 se crea una nueva fracción, ¾ (ver el Programa 6).

Programa 6

def __add__(self,otraFraccion):
nuevoNum = self.num*otraFraccion.den + self.den*otraFraccion.num
nuevoDen = self.den * otraFraccion.den
comun = mcd(nuevoNum,nuevoDen)
return Fraccion(nuevoNum//comun,nuevoDen//comun)
>>> f1=Fraccion(1,4)
>>> f2=Fraccion(1,2)
>>> f3=f1+f2
>>> print(f3)
3/4
>>>

PRÁCTICA Clase Fracción


#2.1
Tomando en cuenta los ejercicios anteriores, cree los métodos necesarios para realizar las
operaciones de resta, multiplicación y división de fracciones.

PRÁCTICA Clase Equipo Médico


#2.2
De acuerdo a lo planteado en las cátedras construye una clase llamada equipoMedico, dentro
de esta clase deberás de generar las funciones propias para dar de alta y de baja equipo
Médico , así como planear sus mantenimiento preventivos y poder generar un mantenimiento
correctivo, y la salida de almacén para su uso, o en su caso la cita para estudio, validando si el
quipo esta funcional o en reparación.
3.3 Unidad 3
Herencia: Compuertas lógicas y circuitos
Nuestra sección final presentará otro aspecto importante de la programación orientada a
objetos. La herencia es la habilidad para que una clase esté relacionada con otra clase de la
misma manera que las personas pueden estar relacionadas entre sí. Los hijos heredan
características de sus padres. Del mismo modo, las clases hija en Python pueden heredar datos
y comportamientos característicos de una clase madre. Estas clases se denominan a menudo
subclases y superclases, respectivamente.
La Figura muestra las colecciones incorporadas de Python y sus relaciones entre sí. Llamamos
a una estructura de relación como ésta una jerarquía de herencias. Por ejemplo, la lista es un
hija de la colección secuencial. En este caso, llamamos hija a la lista y madre a la secuencia (o
la subclase lista y la superclase secuencia). Esto a menudo se denomina Relación ES-UNA
(la lista ES-UNA colección secuencial). Esto implica que las listas heredan características
importantes de las secuencias, a saber, el ordenamiento de los datos y operaciones, tales como
la concatenación, la repetición y la indización.

Las listas, las tuplas y las cadenas son todas tipos de colecciones secuenciales. Todas heredan
organización de datos y operaciones comunes. Sin embargo, cada una de ellas es distinta
según los datos sean o no homogéneos y si la colección es inmutable. Los hijos se parecen a
sus padres pero se distinguen agregando características adicionales.
Al organizar las clases de esta manera jerárquica, los lenguajes de programación orientados a
objetos permiten que el código previamente escrito se extienda para satisfacer las necesidades
de una nueva situación. Además, al organizar los datos de esta manera jerárquica, podemos
comprender mejor las relaciones que existen entre ellos. Podemos ser más eficientes en la
construcción de nuestras representaciones abstractas.
Para explorar esta idea más a fondo, construiremos una simulación, una aplicación para
simular circuitos digitales. El bloque constructivo básico para esta simulación será la
compuerta lógica. Estos conmutadores electrónicos representan relaciones de álgebra
booleana entre su entrada y su salida. En general, las compuertas tienen una sola línea de
salida. El valor de la salida depende de los valores dados en las líneas de entrada.
Las compuertas AND tienen dos líneas de entrada, cada una de las cuales puede ser 0 ó 1
(representando False o True, repectivamente). Si ambas líneas de entrada tienen valor 1, la
salida resultante es 1. Sin embargo, si una o ambas líneas de entrada son 0, el resultado es 0.
Las compuertas OR también tienen dos líneas de entrada y producen un 1 si uno o ambos
valores de entrada son 1. En el caso en que ambas líneas de entrada sean 0, el resultado es 0.
Las compuertas NOT se diferencian de las otras dos compuertas porque sólo tienen una única
línea de entrada. El valor de salida es simplemente el opuesto al valor de entrada. Si aparece 0
en la entrada, se produce 1 en la salida. Similarmente, un 1 produce un 0. La Figura muestra
cómo se representa típicamente cada una de estas compuertas. Cada compuerta tiene también
una tabla de verdad de valores que muestran el mapeo de entrada a salida que es llevado a
cabo por la compuerta.

Podemos construir circuitos que tengan funciones lógicas al combinar estas compuertas en
varios patrones y luego aplicarles un conjunto de valores de entrada. La Figura muestra un
circuito que consta de dos compuertas AND, una compuerta OR y una única compuerta NOT.
Las líneas de salida de las dos compuertas AND se conectan directamente en la compuerta
OR y la salida resultante de la compuerta OR es suministrada a la compuerta NOT. Si
aplicamos un conjunto de valores de entrada a las cuatro líneas de entrada (dos por cada
puerta AND), los valores se procesan y aparece un resultado en la salida de la compuerta
NOT. La Figura 10 también muestra un ejemplo con valores.

Para implementar un circuito, primero construiremos una representación para compuertas


lógicas. Las compuertas lógicas se organizan fácilmente en una jerarquía de herencias de clase
como se muestra en la Figura. En la parte superior de la jerarquía, la clase CompuertaLogica
representa las características más generales de las compuertas lógicas: a saber, una etiqueta
para la compuerta y una línea de salida. El siguiente nivel de subclases divide las compuertas
lógicas en dos familias, las que tienen una línea de entrada y las que tienen dos. Debajo de
ellas, aparecen las funciones lógicas específicas de cada una.
Ahora podemos comenzar a implementar las clases empezando con la más general,
CompuertaLogica. Como se ha indicado anteriormente, cada compuerta tiene una etiqueta
para la identificación y una sola línea de salida. Además, necesitamos métodos para permitir
que un usuario de una compuerta le pida la etiqueta a la compuerta.
El otro comportamiento que necesita toda compuerta lógica es la capacidad de conocer su
valor de salida. Esto requerirá que la compuerta lleve a cabo la lógica apropiada con base en
la entrada actual. Con el fin de producir la salida, la compuerta tiene que saber
específicamente cuál es esa lógica. Esto implica invocar a un método para realizar el cálculo
lógico. La clase completa se muestra en el Programa 8.

Programa 8
class CompuertaLogica:
def __init__(self,n):
self.etiqueta = n
self.salida = None
def obtenerEtiqueta(self):
return self.etiqueta
def obtenerSalida(self):
self.salida = self.ejecutarLogicaDeCompuerta()
return self.salida

En este punto, no implementaremos la función ejecutarLogicaDeCompuerta. La razón de esto


es que no sabemos cómo llevará a cabo cada compuerta su propia operación lógica. Estos
detalles serán incluidos por cada compuerta individual que se añada a la jerarquía. Esta es una
idea muy poderosa en la programación orientada a objetos.
Estamos escribiendo un método que usará código que aún no existe. El parámetro self es una
referencia al verdadero objeto compuerta que invoca el método. Cualquier compuerta lógica
nueva que se agregue a la jerarquía simplemente tendrá que implementar la función
ejecutarLogicaDeCompuerta y se utilizará en el momento apropiado. Una vez se haya usado,
la compuerta puede proporcionar su valor de salida. Esta capacidad de extender una jerarquía
que existe actualmente y proporcionar las funciones específicas que la jerarquía necesita para
usar la nueva clase es extremadamente importante para reutilizar el código ya existente.
Categorizamos las compuertas lógicas en función del número de líneas de entrada. La
compuerta AND tiene dos líneas de entrada. La compuerta OR también tiene dos líneas de
entrada. Las compuertas NOT tienen una línea de entrada. La clase CompuertaBinaria será
una subclase de CompuertaLogica y agregará dos líneas de entrada. La clase
CompuertaUnaria también será subclase de CompuertaLogica pero sólo contará con una única
línea de entrada. En el diseño de circuitos asistido por computador, estas líneas a veces se
llaman ―pines‖ por lo que vamos a utilizar esa terminología en nuestra implementación.
Programa 9
class CompuertaBinaria(CompuertaLogica):
def __init__(self,n):
CompuertaLogica.__init__(self,n)
self.pinA = None
self.pinB = None
def obtenerPinA(self):
return int(input("Ingrese la entrada del Pin A para la compuerta "+
self.obtenerEtiqueta()+"-->"))
def obtenerPinB(self):
return int(input("Ingrese la entrada del Pin B para la compuerta "+
self.obtenerEtiqueta()+"-->"))

Programa 10
class CompuertaUnaria(CompuertaLogica):
def __init__(self,n):
CompuertaLogica.__init__(self,n)
self.pin = None
def obtenerPin(self):
return int(input("Ingrese la entrada del Pin para la compuerta "+
self.obtenerEtiqueta()+"-->"))

El Programa 9 y el Programa 10 implementan estas dos clases. Los constructores en ambas


clases comienzan con una llamada explícita al constructor de la clase madre utilizando el
método __init__ de la madre.
Al crear una instancia de la clase CompuertaBinaria, primero queremos inicializar
cualesquiera ítems de datos heredados de CompuertaLogica. En este caso, eso significa la
etiqueta para la compuerta. A continuación, el constructor agrega las dos líneas de entrada
(pinA y pinB). Éste es un patrón muy común que debe usarse siempre al crear jerarquías de
clases. Los constructores de las clases hija deben llamar a los constructores de las clases
madre y luego ocuparse de sus propios datos distintivos.
Python también tiene una función llamada super que se puede usar en lugar de nombrar
explícitamente la clase madre. Éste es un mecanismo más general, y es ampliamente utilizado
especialmente cuando una clase tiene más de una clase madre. Sin embargo, esa opción no se
discutirá en esta introducción.
Por ejemplo, en nuestro ejemplo anterior,
CompuertaLogica.__init__(self,n)
podría reemplazarse por
super(CompuertaUnaria,self).__init__(n).
El único comportamiento que añade la clase CompuertaBinaria es la capacidad de obtener los
valores de las dos líneas de entrada. Dado que estos valores vienen de algún lugar externo,
simplemente le pediremos al usuario a través de una instrucción input que los proporcione. La
misma implementación se usa para la clase CompuertaUnaria excepto que sólo hay una línea
de entrada.
Ahora que tenemos una clase general para las compuertas dependiendo del número de líneas
de entrada, podemos construir compuertas específicas que tengan un comportamiento único.
Por ejemplo, la clase CompuertaAND será una subclase de CompuertaBinaria, ya que las
compuertas AND tienen dos líneas de entrada. Como antes, la primera línea del constructor
invoca al constructor de la clase madre (CompuertaBinaria), que a su vez llama al constructor
de su clase madre (CompuertaLogica). Note que la clase CompuertaAND no proporciona
ningún dato nuevo, ya que hereda dos líneas de entrada, una línea de salida y una etiqueta.

Programa 11
class CompuertaAND(CompuertaBinaria):
def __init__(self,n):
CompuertaBinaria.__init__(self,n)
def ejecutarLogicaDeCompuerta(self):
a = self.obtenerPinA()
b = self.obtenerPinB()
if a==1 and b==1:
return 1
else:
return 0

Lo único que CompuertaAND necesita agregar es el comportamiento específico que realiza la


operación booleana que se describió anteriormente. Éste es el lugar donde podemos
proporcionar el método ejecutarLogicaDeCompuerta. Para una compuerta AND, este método
debe obtener primero los dos valores de entrada y luego devuelve 1 sólo si ambos valores de
entrada son 1. La clase completa se muestra en el Programa 11.
Podemos mostrar la clase CompuertaAND en acción creando una instancia y pidiéndole que
calcule su salida. La sesión siguiente muestra un objeto CompuertaAND, c1, que tiene una
etiqueta interna "C1". Cuando invocamos el método obtenerSalida, el objeto debe llamar
primero a su método ejecutarLogicaDeCompuerta que a su vez consulta las dos líneas de
entrada. Una vez que se proporcionan los valores, se muestra la salida correcta.

>>> c1 = CompuertaAND("C1")
>>> c1.obtenerSalida()
Ingrese la entrada del Pin A para la compuerta C1-->1
Ingrese la entrada del Pin B para la compuerta C1-->0
0

El mismo desarrollo se puede hacer para las compuertas OR y las compuertas NOT. La clase
CompuertaOR también será una subclase de CompuertaBinaria y la clase CompuertaNOT
extenderá la clase CompuertaUnaria. Ambas clases tendrán que proporcionar sus propias
funciones ejecutarLogicaDeCompuerta, ya que ése será su comportamiento específico.
PRÁCTICA Compuertas OR y NOT
#3.1
De acuerdo a lo visto en los códigos anteriores, se solicita se creen las clases CompuertaOR y
CompurtaNOT, a través del proceso de herencia que se ha establecido en el transcurso de este
capito del manual de prácticas.

Ahora que tenemos las compuertas básicas funcionando, podemos centrar nuestra atención en
la construcción de circuitos. Para crear un circuito, necesitamos conectar las compuertas
juntas, la salida de una fluirá hacia la entrada de otra. Para ello, implementaremos una nueva
clase llamada Conector.
La clase Conector no residirá en la jerarquía de las compuertas. Sin embargo, sí usará la
jerarquía de ellas por el hecho que cada conector tendrá dos compuertas, una en cada extremo
(ver la Figura ).
Esta relación es muy importante en la programación orientada a objetos. Se llama la Relación
TIENE-UN(A). Recuerde que antes usamos la frase ―Relación ES-UN(A)‖ para decir que
una clase hija está relacionada con una clase madre, por ejemplo CompuertaUnaria ES-UNA
CompuertaLogica.

Ahora, con la clase Conector, decimos que un Conector TIENE-UNA CompuertaLogica lo


cual significa que los conectores tendrán instancias de la clase CompuertaLogica dentro de
ellos, pero no forman parte de la jerarquía. Al diseñar clases, es muy importante distinguir
entre aquéllas que tienen la relación ES-UN(A) (lo cual requiere herencia) y aquéllas que
tienen relaciones TIENE-UN(A) (sin herencia).
El Programa 12 muestra la clase Conector. Las dos instancias de compuertas dentro de cada
objeto conector se referirán como deCompuerta y aCompuerta, reconociendo que los valores
de los datos ―fluirán‖ desde la salida de una compuerta a una línea de entrada de la siguiente.
El llamado a asignarProximoPin es muy importante para realizar conexiones (ver el Programa
13). Necesitamos agregar este método a nuestras clases de compuertas para que cada
aCompuerta pueda elegir la línea de entrada adecuada para la conexión.

Programa 12
class Conector:
def __init__(self, deComp, aComp):
self.deCompuerta = deComp
self.aCompuerta = aComp
aComp.asignarProximoPin(self)
def obtenerFuente(self):
return self.deCompuerta
def obtenerDestino(self):
return self.aCompuerta
En la clase CompuertaBinaria, para compuertas con dos posibles líneas de entrada, el conector
debe conectarse a una sola línea. Si ambas están disponibles, elegiremos pinA de forma
predeterminada. Si pinA ya está conectado, entonces elegiremos pinB. No es posible
conectarse a una compuerta sin líneas de entrada disponibles.

Programa 13
def asignarProximoPin(self,fuente):
if self.pinA == None:
self.pinA = fuente
else:
if self.pinB == None:
self.pinB = fuente
else:
raise RuntimeError("Error: NO HAY PINES DISPONIBLES")

Ahora es posible obtener entradas desde dos lugares: externamente, como antes, y desde la
salida de una compuerta que está conectada a esa línea de entrada. Esto requiere un cambio en
los métodos obtenerPinA y obtenerPinB (ver el Programa 14). Si la línea de entrada no está
conectada a nada (None), entonces se pide al usuario que ingrese el valor externamente como
antes. Sin embargo, si hay una conexión, se accede a ella y se consulta el valor de salida de
deCompuerta. Esto, a su vez, hace que esa compuerta procese su lógica. Se continúa este
proceso hasta que todas las entradas estén disponibles y el valor de salida final se convierta en
la entrada requerida para la compuerta en cuestión. En cierto sentido, el circuito opera hacia
atrás para encontrar la entrada necesaria para finalmente producir la salida.

Programa 14
def obtenerPinA(self):
if self.pinA == None:
return input("Ingrese la entrada del Pin A para la compuerta " + self.obtenerNombre()+"-
->")
else:
return self.pinA.obtenerFuente().obtenerSalida()

El siguiente fragmento construye el circuito mostrado anteriormente en esta sección:

>>> c1 = CompuertaAND("C1")
>>> c2 = CompuertaAND("C2")
>>> c3 = CompuertaOR("C3")
>>> c4 = CompuertaNOT("C4")
>>> c1 = Conector(c1,c3)
>>> c2 = Conector(c2,c3)
>>> c3 = Conector(c3,c4)

Las salidas de las dos compuertas AND (c1 y c2) están conectadas a la compuerta OR (c3) y
la salida de esta última está conectada a la compuerta NOT (c4). La salida de la compuerta
NOT es la salida de todo el circuito. Por ejemplo:
>>> c4.obtenerSalida()
Ingrese la entrada del Pin A para la compuerta C1-->0
Ingrese la entrada del Pin B para la compuerta C1-->1
Ingrese la entrada del Pin A para la compuerta C2-->1
Ingrese la entrada del Pin B para la compuerta C2-->1
0

PRÁCTICA Compuertas NAND, NOR, XOR y


#3.2 XNOR
Generar las clases necesarias para poder trabajar con todas las compuertas lógicas binarias y
unarias, deberán de funcionar de acuerdo a la lógica establecida a través del desarrollo de este
manual.

PRÁCTICA Compuertas cuaternarias


#3.2
Existen otro tipo de familia de compuertas además de las unarias y las binarias, son conocidas
como compuertas cuaternarias, es decir de cuatro entradas, y estas son construidas en su
interior por compuertas binarias, cree las clases y herencias necesarias para poder usar este
tipo de compuertas en la construcción de circuitos.

PRÁCTICA Circuito
#3.3
Genera con lo aprendido en el curso da la solución para el siguiente circuito, deberás de
construir la representación física del circuito creado para cumplir la tabla de verdad de la
función propuesta y el código Python.
Tabla de Verdad
A B C D F
0 0 0 0 1
0 0 0 1 0
0 0 1 0 0
0 0 1 1 1
0 1 0 0 0
0 1 0 1 1
0 1 1 0 1
0 1 1 1 0
1 0 0 0 0
1 0 0 1 1
1 0 1 0 0
1 0 1 1 1
1 1 0 0 1
1 1 0 1 1
1 1 1 0 1
1 1 1 1 1
F=a´b´c´ + ab +ad +bc´d + b´cd + bcd´
3.4 Unidad 4
Listas Enlazadas
En esta unidad, nos dedicaremos a construir nuestras propias listas, que consistirán de cadenas
de objetos enlazadas mediante referencias.
Si bien Python ya cuenta con sus propias listas, las listas enlazadas que implementaremos en
esta unidad nos resultarán también útiles.
En primer lugar, definiremos una clase muy simple, Nodo, que se comportará como un vagón:
tendrá sólo dos atributos: dato, que servirá para almacenar cualquier información, y prox, que
servirá para poner una referencia al siguiente vagón.
Además, como siempre, implementaremos el constructor y el método __str__ para poder
imprimir el contenido del nodo.

class Nodo(object):

def __init__(self, dato=None, prox = None):

self.dato = dato

self.prox = prox

def __str__(self):

return str(self.dato)

Ejecutamos este código:

>>> v3=Nodo("Bananas")

>>> v2=Nodo("Peras", v3)

>>> v1=Nodo("Manzanas", v2)

>>> print v1

Manzanas

>>> print v2

Peras

>>> print v3

Bananas

Con esto hemos generado la estructura de la Figura 4.1.


El atributo prox de v3 tiene una referencia nula, lo que indica que v3 es el último vagón de
nuestra estructura.
Hemos creado una lista en forma manual. Si nos interesa recorrerla, podemos hacer lo
siguiente:
def verLista(nodo):

""" Recorre todos los nodos a través de sus enlaces,

mostrando sus contenidos. """

# cicla mientras nodo no es None

while nodo:

# muestra el dato

print nodo

# ahora nodo apunta a nodo.prox

nodo = nodo.prox

>>> verLista(v1)

Manzanas

Peras

Bananas
Es interesante notar que la estructura del recorrido de la lista es el siguiente:
Se le pasa a la función sólo la referencia al primer nodo.
El resto del recorrido se consigue siguiendo las cadena de referencias dentro de los nodos.
Si se desea desenganchar un vagón del medio de la lista, alcanza con cambiar el
enganche:

>>> v1.prox=v3

>>> verLista(v1)

Manzanas

Bananas

>>> v1.prox = None


>>> verLista(v1)

Manzanas

De esta manera también se pueden generar estructuras impensables.


¿Qué sucede si escribimos v1.prox = v1? La representación es finita y sin embargo en este
caso verLista(v1) no termina más.
Hemos creado una lista infinita, también llamada lista circular.
Caminos
En una lista cualquiera, como las vistas antes, si seguimos las flechas dadas por las
referencias, obtenemos un camino en la lista.
Los caminos cerrados se denominan ciclos. Son ciclos, por ejemplo, la autorreferencia
de v1 a v1, como así también una flecha de v1 a v2 seguida de una flecha de v2 a v1.
ADVERTENCIA Las listas circulares no tienen nada de malo en sí mismas, mientras su
representación sea finita. El problema, en cambio, es que debemos tener mucho cuidado al
escribir programas para recorrerlas, ya que el recorrido debe ser acotado (por ejemplo no
habría problema en ejecutar un programa que liste los 20 primeros nodos de una lista
circular).
Cuando una función recibe una lista y el recorrido no está acotado por programa, se debe
aclarar en su precondición que la ejecución de la misma terminará sólo si la lista no contiene
ciclos. Ése es el caso de la función verLista(v1).
Referenciando el principio de la lista
Una cuestión no contemplada hasta el momento es la de mantener una referencia a la lista
completa. Por ahora para nosotros la lista es la colección de nodos que se enlazan a partir
de v1. Sin embargo puede suceder que querramos borrar a v1 y continuar con el resto de la
lista como la colección de nodos a tratar (en las listas de Python, del lista[0] no nos hace
perder la referencia a lista).
Para ello lo que haremos es asociar una referencia al principio de la lista, que
llamaremos lista, y que mantendremos independientemente de cuál sea el nodo que está al
principio de la lista:

>>> v3=Nodo("Bananas")

>>> v2=Nodo("Peras", v3)

>>> v1=Nodo("Manzanas", v2)

>>> lista=v1

>>> verLista(lista)

Manzanas

Peras

Bananas

Ahora sí estamos en condiciones de borrar el primer elemento de la lista sin perder la


identidad de la misma:

>>> lista=lista.prox

>>> verLista(lista)
Peras

Bananas

Vamos a ver ahora una nueva manera de definir datos: por las operaciones que tienen y por lo
que tienen que hacer esas operaciones (cuál es el resultado esperado de esas operaciones). Esa
manera de definir datos se conoce como tipos abstractos de datos o TADs.
Lo novedoso de este enfoque respecto del anterior es que en general se puede encontrar más
de una representación mediante tipos concretos para representar el mismo TAD, y que se
puede elegir la representación más conveniente en cada caso, según el contexto de uso.
Los programas que los usan hacen referencia a las operaciones que tienen, no a la
representación, y por lo tanto ese programa sigue funcionando si se cambia la representación.
Dentro del ciclo de vida de un TAD hay dos fases: la programación del TAD y la
construcción de los programas que lo usan.
Durante la fase de programación del TAD, habrá que elegir una representación, y luego
programar cada uno de los métodos sobre esa representación.
Durante la fase de construcción de los programas, no será relevante para el programador que
utiliza el TAD cómo está implementado, sino únicamente los métodos que posee.

La Clase ListaEnlazada
Basándonos en los nodos implementados anteriormente, pero buscando deslindar al
programador que desea usar la lista de la responsabilidad de manipular las referencias,
definiremos ahora la clase ListaEnlazada, de modo tal que no haya que operar mediante las
referencias internas de los nodos, sino que se lo pueda hacer a través de operaciones de lista.
Más allá de la implementación en particular, se podrá notar que implementaremos los mismos
métodos de las listas de Python, de modo que más allá del funcionamiento interno, ambas
serán listas.
Definimos a continuación las operaciones que inicialmente deberá cumplir la clase
ListaEnlazada.

 __str__, para mostrar la lista.

 __len__, para calcular la longitud de la lista.

 append(x), para agregar un elemento al final de la lista.

 insert(i, x), para agregar el elemento x en la posición i (levanta una excepción si la


posición i es inválida).

 remove(x), para eliminar la primera aparición de x en la lista (levanta una excepción


si x no está).

 pop([i]), para borrar el elemento que está en la posición i y devolver su valor. Si no se


especifica el valor de i, pop() elimina y devuelve el elemento que está en el último lugar
de la lista (levanta una excepción si se hace referencia a una posición no válida de la
lista).

 index(x), devuelve la posición de la primera aparición de x en la lista (levanta una


excepción si xno está).
Más adelante podrán agregarse a la lista otros métodos que también están implementados por
las listas de Python.
Valen ahora algunas consideraciones más antes de empezar a implementar la clase:
Por lo dicho anteriormente, es claro que la lista deberá tener como atributo la referencia al
primer nodo que la compone.
Como vamos a incluir un método __len__, consideramos que no tiene sentido recorrer la lista
cada vez que se lo llame, para contar cuántos elementos tiene, alcanza con agregar un atributo
más (la longitud de la lista), que se inicializa en 0 cuando se crea la lista vacía, se incrementa
en 1 cada vez que se agrega un elemento y se decrementa en 1 cada vez que se borra un
elemento.
Por otro lado, como vamos a incluir todas las operaciones de listas que sean necesarias para
operar con ellas, no es necesario que la clase Nodo esté disponible para que otros
programadores puedan modificar (y romper) las listas a voluntad usando operaciones de
nodos. Para eso incluiremos la clase Nodo de manera privada (es decir oculta), de modo que
la podamos usar nosotros como dueños (fabricantes) de la clase, pero no cualquier
programador que utilice la lista.
Python tiene una convención para hacer que atributos, métodos o clases dentro de una clase
dada no puedan ser usados por los usuarios, y sólo tengan acceso a ellos quienes programan la
clase: su nombre tiene que empezar con un guión bajo y terminar sin guión bajo. Así que para
hacer que los nodos sean privados, nombraremos a esa clase como _Nodo, y la dejaremos tal
como hasta ahora.
Empezaremos escribiendo la clase con su constructor

class ListaEnlazada(object):

" Modela una lista enlazada, compuesta de Nodos. "

def __init__(self):

""" Crea una lista enlazada vacía. """

# prim: apuntará al primer nodo -None con la lista vacía

self.prim = None

# len: longitud de la lista - 0 con la lista vacía

self.len = 0

Nuestra estructura ahora será como la representada por la Figura


PRÁCTICA Lista enlazada
#4.1
Deberás de realizar el código necesario para los métodos __str__ y __len__ para la lista.

Eliminar un elemento de una posición


Analizaremos a continuación pop([i]), que borra el elemento que está en la posición i y
devuelve su valor. Si no se especifica el valor de i, pop() elimina y devuelve el elemento que
está en el último lugar de la lista. Por otro lado, levanta una excepción si se hace referencia a
una posición no válida de la lista.
Dado que se trata de una función con cierta complejidad, separaremos el código en las
diversas consideraciones a tener en cuenta.
Si la posición es inválida (i menor que 0 o mayor o igual a la longitud de la lista), se considera
error y se levanta la excepción ValueError.
Esto se resuelve con este fragmento de código:

# Verificación de los límites

if (i < 0) or (i >= self.len):

raise IndexError("Índice fuera de rango")

Si no se indica posición, i toma la última posición de la lista. Esto se resuelve con este
fragmento de código:

# Si no se recibió i, se devuelve el último.

if i == None:

i = self.len - 1

Cuando la posición es 0 se trata de un caso particular, ya que en ese caso, además de borrar el
nodo, hay que cambiar la referencia de self.prim para que apunte al nodo siguiente. Es decir,
pasar de
self.prim → nodo0 → nodo1 a self.prim → nodo1.
Esto se resuelve con este fragmento de código:

# Caso particular, si es el primero,

# hay que saltear la cabecera de la lista

if i == 0:

dato = self.prim.dato

self.prim = self.prim.prox

Vemos ahora el caso general:


Mediante un ciclo, se deben ubicar los nodos npi - 1 y npi que están en las posiciones i − 1 e i
de la lista, respectivamente, de modo de poder ubicar no sólo el nodo que se borrará, sino
también estar en condiciones de saltear el nodo borrado en los enlaces de la lista. La lista debe
pasar de contener el camino npi - 1 → npi → npi.prox a contener el camino npi-1 → npi.prox.
Nos basaremos un esquema muy simple (y útil) que se denomina máquina de parejas:
Si nuestra secuencia tiene la forma ABCDE, se itera sobre ella de modo de tener las parejas
AB, BC, CD, DE. En la pareja XY, llamaremos a X el elemento anterior y a Y el elemento
actual. En general estos ciclos terminan o bien cuando no hay más parejas que formar, o bien
cuando el elemento actual cumple con una determinada condición.
En nuestro problema, tenemos la siguiente situación:
 Las parejas son parejas de nodos.
 Para avanzar en la secuencia se usa la referencia al próximo nodo de la lista.
 La condición de terminación es siempre que la posición del nodo en la lista sea igual
al valor buscado. En este caso particular no debemos preocuparnos por la
terminación de la lista porque la validez del índice buscado ya fue verificada más
arriba.

Esta es la porción de código correspondiente a la búsqueda:

n_ant = elf.prim

n_act = n_ant.prox

for pos in xrange(1, i):

n_ant = n_act

n_act = n_ant.prox

Al finalizar el ciclo, n_ant será una referencia al nodo i − 1 y n_act una referencia al nodo i.
Una vez obtenidas las referencias, se obtiene el dato y se cambia el camino según era
necesario:

# Guarda el dato y elimina el nodo a borrar

dato = n_act.dato

n_ant.prox = n_act.prox

Finalmente, en todos los casos de éxito, se debe devolver el dato que contenía el nodo
eliminado y decrementar la longitud en 1:

# hay que restar 1 de len

self.len -= 1

# y devolver el valor borrado

return dato
Finalmente, en el código 16.1 se incluye el código completo del método pop.
Eliminar un elemento por su valor
Análogamente se resuelve remove(self,x), que debe eliminar la primera aparición de x en la
lista, o bien levantar una excepción si x no se encuentra en la lista.
Nuevamente, dado que se trata de un método de cierta complejidad, lo resolveremos por
partes, teniendo en cuenta los casos particulares y el caso general.

def pop(self, i = None):

""" Elimina el nodo de la posición i, y devuelve el dato contenido.

Si i está fuera de rango, se levanta la excepción IndexError.

Si no se recibe la posición, devuelve el último elemento. """

# Si no se recibió i, se devuelve el último.

if i is None:

i = self.len - 1

# Verificación de los límites

if not (0 <= i < self.len):

raise IndexError("Índice fuera de rango")

# Caso particular, si es el primero,

# hay que saltear la cabecera de la lista

if i == 0:

dato = self.prim.dato

self.prim = self.prim.prox

# Para todos los demás elementos, busca la posición

else:

n_ant = self.prim

n_act = n_ant.prox

for pos in xrange(1, i):

n_ant = n_act
n_act = n_ant.prox

# Guarda el dato y elimina el nodo a borrar

dato = n_act.dato

n_ant.prox = n_act.prox

# hay que restar 1 de len

self.len -= 1

# y devolver el valor borrado

return dato

Los casos particulares son: la lista vacía, que es un error y hay que levantar una excepción; y
el caso en el que x está en el primer nodo, en este caso hay que saltear el primer nodo desde la
cabecera de la lista.
El fragmento de código que resuelve estos casos es:

if self.len == 0:

# Si la lista está vacía, no hay nada que borrar.

raise ValueError("Lista vacía")

# Caso particular, x esta en el primer nodo

elif self.prim.dato == x:

# Se descarta la cabecera de la lista

self.prim = self.prim.prox

El caso general también implica un recorrido con máquina de parejas, sólo que esta vez la
condición de terminación es: o bien la lista se terminó o bien encontramos un nodo con el
valor (x) buscado.

# Obtiene el nodo anterior al que contiene a x (n_ant)

n_ant = self.prim

n_act = n_ant.prox

while n_act != None and n_act.dato != x:


n_ant = n_act

n_act = n_ant.prox

En este caso, al terminarse el ciclo será necesario corroborar si se terminó porque llegó al
final de la lista, y de ser así levantar una excepción; o si se terminó porque encontró el dato, y
de ser así eliminarlo.

# Si no se encontró a x en la lista, levanta la excepción

if n_act == None:

raise ValueError("El valor no ester en la lista.")

# Si encontró a x, debe pasar de n_ant -> n_x -> n_x.prox

# a n_ant -> n_x.prox

else:

n_ant.prox = n_act.prox

Finalmente, en todos los casos de éxito debemos decrementar en 1 el valor de self.len. En


el código 16.2 se incluye el código completo del método remove.
Insertar nodos
Debemos programar ahora insert(i, x), que debe agregar el elemento x en la
posición i (y levantar una excepción si la posición i es inválida).
Veamos qué debemos tener en cuenta para programar esta función.
Si se intenta insertar en una posición menor que cero o mayor que la longitud de la lista debe
levantarse una excepción.

def remove(self, x):

""" Borra la primera aparición del valor x en la lista.

Si x no está en la lista, levanta ValueError """

if self.len == 0:

# Si la lista está vacía, no hay nada que borrar.

raise ValueError("Lista vacía")

# Caso particular, x esta en el primer nodo

elif self.prim.dato == x:

# Se descarta la cabecera de la lista


self.prim = self.prim.prox

# En cualquier otro caso, hay que buscar a x

else:

# Obtiene el nodo anterior al que contiene a x (n_ant)

n_ant = self.prim

n_act = n_ant.prox

while n_act != None and n_act.dato != x:

n_ant = n_act

n_act = n_ant.prox

# Si no se encontró a x en la lista, levanta la excepción

if n_act == None:

raise ValueError("El valor no ester en la lista.")

# Si encontró a x, debe pasar de n_ant -> n_x -> n_x.prox

# a n_ant -> n_x.prox

else:

n_ant.prox = n_act.prox

# Si no levantó excepción, hay que restar 1 del largo

self.len -= 1

if (i > self.len) or (i < 0):

# error

raise IndexError("Posición inválida")

Para los demás casos, hay que crear un nodo, que será el que se insertará en la posición que
corresponda. Construimos un nodo nuevo cuyo dato sea x.
# Crea nuevo nodo, con x como dato:

nuevo = _Nodo(x)

Si se quiere insertar en la posición 0, hay que cambiar la referencia de self.prim.

# Insertar al principio (caso particular)

if i == 0:

# el siguiente del nuevo pasa a ser el que era primero

nuevo.prox = self.prim

# el nuevo pasa a ser el primero de la lista

self.prim = nuevo

Para los demás casos, nuevamente será necesaria la máquina de parejas. Obtenemos el nodo
anterior a la posición en la que queremos insertar.

# Insertar en cualquier lugar > 0

else:

# Recorre la lista hasta llegar a la posición deseada

n_ant = self.prim

for pos in xrange(1,i):

n_ant = n_ant.prox

# Intercala nuevo y obtiene n_ant -> nuevo -> n_ant.prox

nuevo.prox = n_ant.prox

n_ant.prox = nuevo

En todos los casos de éxito se debe incrementar en 1 la longitud de la lista.

# En cualquier caso, incrementar en 1 la longitud

# self.len += 1
En el Código se incluye el código resultante del método insert.

# Código 16.3: insert: Método insert de la lista enlazada

def insert(self, i, x):

""" Inserta el elemento x en la posición i.


Si la posición es inválida, levanta IndexError """

if (i > self.len) or (i < 0):

0 # error

raise IndexError("Posición inválida")

# Crea nuevo nodo, con x como dato:

nuevo = _Nodo(x)

# Insertar al principio (caso particular)

if i == 0:

# el siguiente del nuevo pasa a ser el que era primero

nuevo.prox = self.prim

# el nuevo pasa a ser el primero de la lista

self.prim = nuevo

# Insertar en cualquier lugar > 0

else:

# Recorre la lista hasta llegar a la posición deseada

n_ant = self.prim

for pos in xrange(1,i):

n_ant = n_ant.prox

# Intercala nuevo y obtiene n_ant -> nuevo -> n_ant.prox

nuevo.prox = n_ant.prox

n_ant.prox = nuevo

# En cualquier caso, incrementar en 1 la longitud

self.len += 1
PRÁCTICA Lista enlazada: append e index
#4.2
Completar la clase ListaEnlazada con los métodos que faltan: append e index.

Otras listas enlazadas


Las listas presentadas hasta aquí son las listas simplemente enlazadas, que son
sencillas y útiles cuando se quiere poder insertar o eliminar nodos de una lista en tiempo
constante. Existen otros tipos de listas enlazadas, cada uno con sus ventajas y desventajas.
Listas doblemente enlazadas
Las listas doblemente enlazadas son aquellas en que los nodos cuentan no sólo con una
referencia al siguiente, sino también con una referencia al anterior. Esto permite que la lista
pueda ser recorrida en ambas direcciones.
En una lista doblemente enlazada, es posible, por ejemplo, eliminar un nodo, teniendo
únicamente ese nodo, sin necesidad de saber también cuál es el anterior.
Entre las desventajas podemos mencionar que al tener que mantener dos referencias el código
se vuelve más complejo, y también que ocupa más espacio en memoria.
16.5.2. Listas circulares
Las listas circulares, que ya fueron mencionadas al comienzo de esta unidad, son aquellas en
las que el último nodo contiene una referencia al primero. Pueden ser tanto simplemente como
doblemente enlazadas.
Se las utiliza para modelar situaciones en las cuales los elementos no tienen un primero o un
último, sino que forman una cadena infinita, que se recorre una y otra vez.
NOTA: Un ejemplo de uso de las listas circulares es dentro del kernel Linux. La mayoría de
las listas utilizadas por este kernel son circulares, ya que la mayoría de los datos a los que se
quiere acceder son datos que no tienen un orden en particular.
Por ejemplo, la lista de tareas que se están ejecutando es una lista circular. El scheduler del
kernel permite que cada tarea utilice el procesador durante una porción de tiempo y luego
pasa a la siguiente; y al llegar a la última vuelve a la primera, ya que la ejecución de tareas
no se termina.
Iteradores
En la unidad anterior se hizo referencia a que todas las secuencias pueden ser recorridas
mediante una misma estructura (for variable in secuencia), ya que todas implementan el
método especial __iter__. Este método debe devolver un iterador capaz de recorrer la
secuencia como corresponda.
NOTA: Un iterador es un objeto que permite recorrer uno a uno los elementos almacenados
en una estructura de datos, y operar con ellos.
En particular, en Python, los iteradores tienen que implementar un método next que debe
devolver los elementos, de a uno por vez, comenzando por el primero. Y al llegar al final de la
estructura, debe levantar una excepción de tipo StopIteration.
Es decir que las siguientes estructuras son equivalentes
for elemento in secuencia:

# hacer algo con elemento

iterador = iter(secuencia)

while True:

try:

elemento = iterador.next()

except StopIteration:

break

# hacer algo con elemento

En particular, si queremos implementar un iterador para la lista enlazada, la mejor solución


implica crear una nueva clase, _IteradorListaEnlazada, que implemente el
método next() de la forma apropiada.

ADVERTENCIA: Utilizamos la notación de clase privada, utilizada también para la clase _Nodo, ya que si
bien se devolverá el iterador cuando sea necesario, un programador externo no debería construir el iterador sin
pasar a través de la lista enlazada.

Para inicializar la clase, lo único que se necesita es una referencia al primer elemento de la lista.

class _IteradorListaEnlazada(object):

" Iterador para la clase ListaEnlazada "

def __init__(self, prim):

""" Constructor del iterador.

prim es el primer elemento de la lista. """

self.actual = prim

A partir de allí, el iterador irá avanzando a través de los elementos de la lista mediante el
método next. Para verificar que no se haya llegado al final de la lista, se corroborará que la
referencia self.actualsea distinta de None.

if self.actual == None:

raise StopIteration("No hay más elementos en la lista")

Una vez que se pasó la verificación, la primera llamada a next debe devolver el primer
elemento, pero también debe avanzar, para que la siguiente llamada devuelva el siguiente
elemento. Por ello, se utiliza la estructura guardar, avanzar, devolver.
# Guarda el dato

dato = self.actual.dato

# Avanza en la lista

self.actual = self.actual.prox

# Devuelve el dato

return dato

# Código 16.4: _IteradorListaEnlazada: Un iterador para la lista enl


azada

class _IteradorListaEnlazada(object):

" Iterador para la clase ListaEnlazada "

def __init__(self, prim):

""" Constructor del iterador.

prim es el primer elemento de la lista. """

self.actual = prim

def next(self):

""" Devuelve uno a uno los elementos de la lista. """

if self.actual == None:

raise StopIteration("No hay más elementos en la lista")

# Guarda el dato

dato = self.actual.dato

# Avanza en la lista

self.actual = self.actual.prox

# Devuelve el dato

return dato
Finalmente, una vez que se tiene el iterador implementado, es necesario modificar la
clase ListaEnlazada para que devuelva el iterador cuando se llama al método __iter__.

def __iter__(self):

" Devuelve el iterador de la lista. "

return _IteradorListaEnlazada(self.prim)

Con todo esto será posible recorrer nuestra lista con la estructura a la que estamos
acostumbrados.

>>> l = ListaEnlazada()

>>> l.append(1)

>>> l.append(3)

>>> l.append(5)

>>> for valor in l:

... print valor

...

Pilas
Una pila es un TAD que tiene las siguientes operaciones (se describe también la acción que
lleva adelante cada operación:
 init__: inicializa una pila nueva, vacía.
 apilar: agrega un nuevo elemento a la pila.
 desapilar: elimina el tope de la pila y lo devuelve. El elemento que se devuelve es
siempre el último que se agrego.
 es_vacia: devuelve True o False según si la pila está vacía o no.
El comportamiento de una pila se puede describir mediante la frase "Lo último que se apiló es
lo primero que se usa", que es exactamente lo que uno hace con una pila (de platos por
ejemplo): en una pila de platos uno sólo puede ver la apariencia completa del plato de arriba,
y sólo puede tomar el plato de arriba (si se intenta tomar un plato del medio de la pila lo más
probable es que alguno de sus vecinos, o él mismo, se arruine).
Como ya se dijo, al crear un tipo abstracto de datos, es importante decidir cuál será la
representación a utilizar. En el caso de la pila, si bien puede haber más de una representación,
por ahora veremos la más sencilla: representaremos una pila mediante una lista de Python.
Sin embargo, para los que construyen programas que usan un TAD vale el siguiente llamado
de atención:
Pilas representadas por listas
Definiremos una clase Pila con un atributo, items, de tipo lista, que contendrá los ele-mentos
de la pila. El tope de la pila se encontrará en la última posición de la lista, y cada vez que se
apile un nuevo elemento, se lo agregará al final.
El método __init__ no recibirá parámetros adicionales, ya que deberá crear una pila vacía
(que representaremos por una lista vacía):

class Pila:

""" Representa una pila con operaciones de apilar, desapilar y

verificar si está vacía. """

def __init__(self):

""" Crea una pila vacía. """

# La pila vacía se representa con una lista vacía

self.items=[]

El método apilar se implementará agregando el nuevo elemento al final de la lista:

def apilar(self, x):

""" Agrega el elemento x a la pila. """

# Apilar es agregar al final de la lista.

self.items.append(x)

Para implementar desapilar, se usará el método pop de lista que hace exactamente lo
requerido: elimina el último elemento de la lista y devuelve el valor del elemento eliminado.
Si la lista está vacía levanta una excepción, haremos lo mismo, pero cambiaremos el tipo de
excepción, para no revelar la implementación.

def desapilar(self):

""" Devuelve el elemento tope y lo elimina de la pila.

Si la pila está vacía levanta una excepción. """

try:

return self.items.pop()

except IndexError:

raise ValueError("La pila está vacía")

Finalmente, el método para indicar si se trata de una pila vacía.

def es_vacia(self):
""" Devuelve True si la lista está vacía, False si no. """

return self.items == []

Construimos algunas pilas y operamos con ellas:

>>> from clasePila import Pila

>>> p = Pila()

>>> p.es_vacia()

True

>>> p.apilar(1)

>>> p.es_vacia()

False

>>> p.apilar(5)

>>> p.apilar("+")

>>> p.apilar(22)

>>> p.desapilar()

22

>>> p

<clasePila.Pila instance at 0xb7523f4c>

>>> q=Pila()

>>> q.desapilar()

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

File "clasePila.py", line 24, in desapilar

raise ValueError("La pila está vacía")

ValueError: La pila está vacía

Uso de pila: calculadora científica


La famosa calculadora portátil HP-35 (de 1972) popularizó la notación polaca inversa (o
notación prefijo) para hacer cálculos sin necesidad de usar paréntesis. Esa notación, inventada
por el lógico polaco Jan Lukasiewicz en 1920, se basa en el principio de que un operador
siempre se escribe a continuación de sus operandos. La operación (5 − 3) + 8 se escribirá
como ``5 3 - 8 +, que se interpretará como: "restar 3 de 5, y al resultado sumarle 8".
Es posible implementar esta notación de manera sencilla usando una pila de la siguiente
manera, a partir de una cadena de entrada de valores separados por blancos:
Mientras se lean números, se apilan.
En el momento en el que se detecta una operación binaria +, -, *, / o % se desapilan los dos
últimos números apilados, se ejecuta la operación indicada, y el resultado de esa operación se
apila.
Si la expresión está bien formada, tiene que quedar al final un único número en la pila (el
resultado).
Los posibles errores son:
 Queda más de un número al final (por ejemplo si la cadena de entrada fue "5 3"),
 Ingresa algún caracter que no se puede interpretar ni como número ni como una de las
cinco operaciones válidas (por ejemplo si la cadena de entrada fue "5 3 &")
 No hay suficientes operandos para realizar la operación (por ejemplo si la cadena de
entrada fue "5 3 - +").
La siguiente es la estrategia de resolución:
Dada una cadena con la expresión a evaluar, podemos separar sus componentes utilizando el
método split(). Recorreremos luego la lista de componentes realizando las acciones
indicadas en el párrafo anterior, utilizando una pila auxiliar para operar. Si la expresión está
bien formada devolveremos el resultado, de lo contrario levantaremos una excepción
(devolveremos None).
Veamos algunos casos de prueba:
El caso de una expresión que es sólo un número (es correcta):

>>> calculadora_polaca.main()

Ingrese la expresion a evaluar: 5

DEBUG: 5

DEBUG: apila 5.0

5.0

El caso en el que sobran operandos:

>>> calculadora_polaca.main()

Ingrese la expresion a evaluar: 4 5

DEBUG: 4

DEBUG: apila 4.0

DEBUG: 5

DEBUG: apila 5.0

DEBUG: error pila sobran operandos

Traceback (most recent call last):

File "<stdin>", line 1, in <module>


File "calculadora_polaca.py", line 64, in main

print calculadora_polaca(elementos)

File "calculadora_polaca.py", line 59, in calculadora_polaca

raise ValueError("Sobran operandos")

ValueError: Sobran operandos

El caso en el que faltan operandos:

>>> calculadora_polaca.main()

Ingrese la expresion a evaluar: 4 %

DEBUG: 4

DEBUG: apila 4.0

DEBUG: %

DEBUG: desapila 4.0

DEBUG: error pila faltan operandos

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

File "calculadora_polaca.py", line 64, in main

print calculadora_polaca(elementos)

File "calculadora_polaca.py", line 37, in calculadora_polaca

raise ValueError("Faltan operandos")

ValueError: Faltan operandos

# calculadora_polaca.py: Una calculadora polaca inversa

#!/usr/bin/env python

#encoding: latin1

from clasePila import Pila


def calculadora_polaca(elementos):

""" Dada una lista de elementos que representan las componentes de

una expresión en notacion polaca inversa, evalúa dicha expresión.

Si la expresion está mal formada, levanta ValueError. """

p = Pila()

for elemento in elementos:

print "DEBUG:", elemento

# Intenta convertirlo a número

try:

numero = float(elemento)

p.apilar(numero)

print "DEBUG: apila ", numero

# Si no se puede convertir a número, debería ser un operando

except ValueError:

# Si no es un operando válido, levanta ValueError

if elemento not in "+-*/ %" or len(elemento) != 1:

raise ValueError("Operando inválido")

# Si es un operando válido, intenta desapilar y operar

try:

a1 = p.desapilar()

print "DEBUG: desapila ",a1

a2 = p.desapilar()

print "DEBUG: desapila ",a2

# Si hubo problemas al desapilar

except ValueError:

print "DEBUG: error pila faltan operandos"

raise ValueError("Faltan operandos")


if elemento == "+":

resultado = a2 + a1

elif elemento == "-":

resultado = a2 - a1

elif elemento == "*":

resultado = a2 * a1

elif elemento == "/":

resultado = a2 / a1

elif elemento == " %":

resultado = a2 % a1

print "DEBUG: apila ", resultado

p.apilar(resultado)

# Al final, el resultado debe ser lo único en la Pila

res = p.desapilar()

if p.esPilaVacia():

return res

else:

print "DEBUG: error pila sobran operandos"

raise ValueError("Sobran operandos")

def main():

expresion = raw_input("Ingrese la expresion a evaluar: ")

elementos = expresion.split()

print calculadora_polaca(elementos)

El caso de un operador inválido:

>>> calculadora_polaca.main()

Ingrese la expresion a evaluar: 4 5 &

DEBUG: 4
DEBUG: apila 4.0

DEBUG: 5

DEBUG: apila 5.0

DEBUG: &

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

File "calculadora_polaca.py", line 64, in main

print calculadora_polaca(elementos)

File "calculadora_polaca.py", line 26, in calculadora_polaca

raise ValueError("Operando inválido")

ValueError: Operando inválido

El caso de 4 % 5:

>>> calculadora_polaca.main()

Ingrese la expresion a evaluar: 4 5 %

DEBUG: 4

DEBUG: apila 4.0

DEBUG: 5

DEBUG: apila 5.0

DEBUG: %

DEBUG: desapila 5.0

DEBUG: desapila 4.0

DEBUG: apila 4.0

4.0

El caso de (4 + 5) * 6:

>>> calculadora_polaca.main()

Ingrese la expresion a evaluar: 4 5 + 6 *

DEBUG: 4

DEBUG: apila 4.0


DEBUG: 5

DEBUG: apila 5.0

DEBUG: +

DEBUG: desapila 5.0

DEBUG: desapila 4.0

DEBUG: apila 9.0

DEBUG: 6

DEBUG: apila 6.0

DEBUG: *

DEBUG: desapila 6.0

DEBUG: desapila 9.0

DEBUG: apila 54.0

54.0

El caso de 4 * (5 + 6):

>>> calculadora_polaca.main()

Ingrese la expresion a evaluar: 4 5 6 + *

DEBUG: 4

DEBUG: apila 4.0

DEBUG: 5

DEBUG: apila 5.0

DEBUG: 6

DEBUG: apila 6.0

DEBUG: +

DEBUG: desapila 6.0

DEBUG: desapila 5.0

DEBUG: apila 11.0

DEBUG: *
DEBUG: desapila 11.0

DEBUG: desapila 4.0

DEBUG: apila 44.0

44.0

El caso de (4 + 5) * (3 + 8):

>>> calculadora_polaca.main()

Ingrese la expresion a evaluar: 4 5 + 3 8 + *

DEBUG: 4

DEBUG: apila 4.0

DEBUG: 5

DEBUG: apila 5.0

DEBUG: +

DEBUG: desapila 5.0

DEBUG: desapila 4.0

DEBUG: apila 9.0

DEBUG: 3

DEBUG: apila 3.0

DEBUG: 8

DEBUG: apila 8.0

DEBUG: +

DEBUG: desapila 8.0

DEBUG: desapila 3.0

DEBUG: apila 11.0

DEBUG: *

DEBUG: desapila 11.0

DEBUG: desapila 9.0

DEBUG: apila 99.0

99.0
PRÁCTICA Pilas
#4.3
Realizar un programa que de una expresión regular la transforme a una expresión de tipo
polaca para introducirla al programa trabajo en clases.

Colas
Todos sabemos lo que es una cola. Más aún, ¡estamos hartos de hacer colas!
El TAD cola modela precisamente ese comportamiento: el primero que llega es el primero en
ser atendido, los demás se van encolando hasta que les toque su turno.
Sus operaciones son:
 __init__: inicializa una cola nueva, vacía.
 encolar: agrega un nuevo elemento al final de la cola.
 desencolar: elimina el primero de la cola y lo devuelve.
 es_vacia: devuelve True o False según si la cola está vacía o no.

Colas implementadas sobre listas


Al momento de realizar una implementación de una Cola, deberemos preguntarnos ¿C6mo
representamos a las colas? Veamos, en primer lugar, si podemos implementar colas usando
listas de Python, como hicimos con la Pila.
Definiremos una clase Cola con un atributo, items, de tipo lista, que contendrá los
elementos de la cola. El primero de la cola se encontrará en la primera posición de la lista, y
cada vez que encole un nuevo elemento, se lo agregará al final.
El método __init__ no recibirá parámetros adicionales, ya que deberá crear una cola vacía
(que representaremos por una lista vacía):

class Cola:

""" Representa a una cola, con operaciones de encolar y desencolar.

El primero en ser encolado es también el primero en ser desencolado.

"""

def __init__(self):

""" Crea una cola vacía. """

# La cola vacía se representa por una lista vacía

self.items=[]
El método encolar se implementará agregando el nuevo elemento al final de la lista:

def encolar(self, x):

""" Agrega el elemento x como último de la cola. """

self.items.append(x)

Para implementar desencolar, se eliminará el primer elemento de la lista y se devolverá el


valor del elemento eliminado, utilizaremos nuevamente el método pop, pero en este caso le
pasaremos la posición 0, para que elimine el primer elemento, no el último. Si la cola está
vacía se levantará una excepción.

def desencolar(self):

""" Elimina el primer elemento de la cola y devuelve su

valor. Si la cola está vacía, levanta ValueError. """

try:

return self.items.pop(0)

except:

raise ValueError("La cola está vacía")

Por último, el método es_vacia, que indicará si la cola está o no vacía.

def es_vacia(self):

""" Devuelve True si la cola esta vacía, False si no."""

return self.items == []

Veamos una ejecución de este código:

>>> from claseCola import Cola

>>> q = Cola()

>>> q.es_vacia()

True

>>> q.encolar(1)

>>> q.encolar(2)

>>> q.encolar(5)

>>> q.es_vacia()

False

>>> q.desencolar()

>>> q.desencolar()

>>> q.encolar(8)
>>> q.desencolar()

>>> q.desencolar()

>>> q.es_vacia()

True

>>> q.desencolar()

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

File "claseCola.py", line 24, in desencolar

raise ValueError("La cola está vacía")

ValueError: La cola está vacía

Colas y listas enlazadas


En la unidad anterior vimos la clase ListaEnlazada. La clase presentada ejecutaba la
inserción en la primera posición en tiempo constante, pero el append se había convertido en
líneal.
Sin embargo, como ejercicio, se propuso mejorar el append, agregando un nuevo atributo
que apunte al último nodo, de modo de poder agregar elementos en tiempo constante.
Si esas mejoras estuvieran hechas, cambiar nuestra clase Cola para que utilice
la ListaEnlazada sería tan simple como cambiar el constructor, para que en lugar de
construir una lista de Python construyera una lista enlazada.

class Cola:

""" Cola implementada sobre lista enlazada"""

def __init__(self):

""" Crea una cola vacía. """

# La cola se representa por una lista enlazada vacía.

self.items = claseListaEnlazadaConUlt.ListaEnlazada()

Sin embargo, una Cola es bastante más sencilla que una ListaEnlazadaConUlt, por lo
que también podemos implementar una clase Cola utilizando las técnicas de referencias, que
se vieron en las listas enlazadas.
Planteamos otra solución posible para obtener una cola que sea eficiente tanto al encolar como
al desencolar, utilizando los nodos de las listas enlazadas, y solamente implementaremos
insertar al final y remover al principio.
Para ello, la cola deberá tener dos atributos, self.primero y self.ultimo, que en todo
momento deberán apuntar al primer y último nodo de la cola, es decir que serán los
invariantes de esta cola.
En primer lugar los crearemos vacíos, ambos referenciando a None.

def __init__(self):

""" Crea una cola vacía. """

# En el primer momento, tanto el primero como el último son None

self.primero = None

self.ultimo = None

Al momento de encolar, hay dos situaciones a tener en cuenta.


 Si la cola está vacía (es decir, self.ultimo es None),
tanto self.primero como self.ultimo deben pasar a referenciar al nuevo nodo, ya que
este nodo será a la vez el primero y el último.
 Si ya había nodos en la cola, simplemente hay que agregar el nuevo a continuación del
último y actualizar la referencia de self.ultimo.
El código resultante es el siguiente.

def encolar(self, x):

""" Agrega el elemento x como último de la cola. """

nuevo = Nodo(x)

# Si ya hay un último, agrega el nuevo y cambia la referencia.

if self.ultimo:

self.ultimo.prox = nuevo

self.ultimo = nuevo

# Si la cola estaba vacía, el primero es también el último.

else:

self.primero = nuevo

self.ultimo = nuevo

Al momento de desencolar, será necesario verificar que la cola no esté vacía, y de ser así
levantar una excepción. Si la cola no está vacía, se almacena el valor del primer nodo de la
cola y luego se avanza la referencia self.primero al siguiente elemento.
Nuevamente hay un caso particular a tener en cuenta y es el que sucede cuando luego de
eliminar el primer nodo de la cola, la cola queda vacía. En este caso, además de actualizar la
referencia de self.primero, también hay que actualizar la referencia de self.ultimo.
def desencolar(self):

""" Elimina el primer elemento de la cola y devuelve su

valor. Si la cola está vacía, levanta ValueError. """

# Si hay un nodo para desencolar

if self.primero:

valor = self.primero.dato

self.primero = self.primero.prox

# Si después de avanzar no quedó nada, también hay que

# eliminar la referencia del último.

if not self.primero:

self.ultimo = None

return valor

else:

raise ValueError("La cola está vacía")

Finalmente, para saber si la cola está vacía, es posible verificar tanto


si self.primero o self.ultimoreferencian a None.

def es_vacia(self):

""" Devuelve True si la cola esta vacía, False si no."""

return self.items == []

Una vez implementada toda la interfaz de la cola, podemos probar el TAD resultante:

>>> from claseColaEnlazada import Cola

>>> q = Cola()

>>> q.es_vacia()

True

>>> q.encolar("Manzanas")

>>> q.encolar("Peras")

>>> q.encolar("Bananas")

>>> q.es_vacia()
False

>>> q.desencolar()

'Manzanas'

>>> q.desencolar()

'Peras'

>>> q.encolar("Guaraná")

>>> q.desencolar()

'Bananas'

>>> q.desencolar()

'Guaraná'

>>> q.desencolar()

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

File "claseColaEnlazada.py", line 42, in desencolar

raise ValueError("La cola está vacía")

ValueError: La cola está vacía

PRÁCTICA Colas
#4.4
Hace un montón de años había una viejísma sucursal del correo en la vereda impar de Av. de
Mayo al 800 que tenía un cartel que decía "No se recibirán más de 5 cartas por
persona". O sea que la gente entregaba sus cartas (hasta la cantidad permitida) y luego tenía
que volver a hacer la cola si tenía más cartas para despachar.
Modelar una cola de correo generalizada, donde en la inicialización se indica la cantidad (no
necesariamente 5) de cartas que se reciben por persona.
Arboles
Árboles Binarios
Los árboles binarios tienen sólo dos ramas por cada nodo. Son particularmente interesantes
porque se pueden utilizar para mantener un orden entre los elementos: Cualquier elemento
menor está en la rama izquierda, mientras que cualquier elemento mayor está en la rama
derecha

Este tipo de estructura presenta una ventaja con respecto a las listas enlazadas: En el peor
de los casos, un elemento en el árbol se encuentra en una cantidad de pasos igual a la
profundidad del mismo, mientras que en una lista ordenada el peor de los casos se encuentra
en una cantidad de pasos igual a la cantidad de elementos.
Árbol binario de búsqueda en python
Para generar un árbol binario, además del elemento vamos a utilizar una función que permita
determinar el orden de los elementos,

class ArbolBinario:
def __init__( self, elemento, esMenorFunction = lambda x,y: x < y ):
self.derecha = None
self.izquierda = None
self.elemento = elemento
self.esMenor = esMenorFunction

Agregar Elemento
Para agregar un elemento, ya no se le indica el padre, el mismo árbol sabe dónde ubicarlo
usando la función de comparación

def agregarElemento(arbol, elemento):


if (arbol.esMenor(elemento, arbol.elemento)):
agregarIzquierda(arbol, elemento)
else:
agregarDerecha(arbol, elemento)

def agregarIzquierda(arbol, elemento):


if arbol.izquierda == None:
arbol.izquierda = ArbolBinario(elemento, arbol.esMenor)
else:
agregarElemento(arbol.izquierda, elemento)

def agregarDerecha(arbol, elemento):


if arbol.derecha == None:
arbol.derecha = ArbolBinario(elemento, arbol.esMenor)
else:
agregarElemento(arbol.derecha, elemento)

Ejecutemos la siguiente sentencia:


import datetime

arbol = ArbolBinario(Persona("Elisa", datetime.date(1981,12,13)), lambda


x,y: x.fechaNacimiento > y.fechaNacimiento)
agregarElemento(arbol, Persona("Yesi", datetime.date(1984,12,1)))
agregarElemento(arbol, Persona("Leo", datetime.date(1980,11,1)))
agregarElemento(arbol, Persona("Pablo", datetime.date(1978,8,13)))
agregarElemento(arbol, Persona("Javier", datetime.date(1982,4,29)))
agregarElemento(arbol, Persona("Azul", datetime.date(2011,11,2)))
agregarElemento(arbol, Persona("Zoe", datetime.date(2007,6,4)))

Aquí la definición de la clase Persona que usamos para los ejemplos


import datetime

class Persona:
def __init__(self, nombre, fechaNacimiento):
self.nombre = nombre
self.fechaNacimiento = fechaNacimiento
def __str__(self):
return self.nombre + "[" + self.fechaNacimiento.strftime("%d-%m-
%Y") + "]"
Recorridos
Existen tres variantes del algorirmo de profundidad primero para árboles binarios: pre-orden,
in-orden, post-orden. Lo que varía entre cada uno es el orden en el que se ejecuta la función
sobre el nodo:
 pre-orden: nodo actual -> rama izquierda -> rama derecha
 in-orden: rama izquierda -> nodo actual -> rama derecha
 post-orden: rama izquierda -> rama derecha -> nodo actual
Teniendo en cuenta que hay una relación entre la rama izquierda y la derecha (en uno están
los menores y en el otro los mayores), esto significa que:
 pre-orden: primero se ejecuta sobre la raiz, despues sobre todos los menores y despues
sobre todos los mayores
 in-orden: se ejecuta primero todos los menores, luego la raiz, y luego los mayores,
siendo éste el órden normal entre todos los elementos
 post-orden: primero se ejecuta sobre todos los menores, luego sobre todos los mayores
y finalmente la raiz
Estas son las funciones de orden superior que recorren los árboles:
def ejecutarPreOrden(arbol, funcion):
if (arbol != None):
funcion(arbol.elemento)
ejecutarPreOrden(arbol.izquierda, funcion)
ejecutarPreOrden(arbol.derecha, funcion)

def ejecutarInOrden(arbol, funcion):


if (arbol != None):
ejecutarInOrden(arbol.izquierda, funcion)
funcion(arbol.elemento)
ejecutarInOrden(arbol.derecha, funcion)

def ejecutarPostOrden(arbol, funcion):


if (arbol != None):
ejecutarPostOrden(arbol.izquierda, funcion)
ejecutarPostOrden(arbol.derecha, funcion)
funcion(arbol.elemento)

Código que lo usa:


def imprimir(elemento):
print elemento

print "in-orden:"
ejecutarInOrden(arbol, imprimir)
print "\npre-orden"
ejecutarPreOrden(arbol, imprimir)
print "\npost-orden"
ejecutarPostOrden(arbol, imprimir)

Y la salida por consola:

in-orden:
Azul[02-11-2011]
Zoe[04-06-2007]
Yesi[01-12-1984]
Javier[29-04-1982]
Elisa[13-12-1981]
Leo[01-11-1980]
Pablo[13-08-1978]

pre-orden
Elisa[13-12-1981]
Yesi[01-12-1984]
Azul[02-11-2011]
Zoe[04-06-2007]
Javier[29-04-1982]
Leo[01-11-1980]
Pablo[13-08-1978]

post-orden
Zoe[04-06-2007]
Azul[02-11-2011]
Javier[29-04-1982]
Yesi[01-12-1984]
Pablo[13-08-1978]
Leo[01-11-1980]
Elisa[13-12-1981]

Eliminación de elementos en Árboles Binarios


Hasta el momento trabajamos con árboles que agregan elementos pero nunca los
remueven. La remoción de un elemento se vuelve dificultosa cuando se trata de la raiz, ya que
al igual que nos sucede en las listas enlazadas, los usuarios de nuestro árbol tienen una
referencia a la raiz que no se puede cambiar. Por eso todas las implementaciones de árboles
binarios que soportan la eliminación de elementos utilizan dos clases: una que simplemente se
encarga de tener un elemento a la raiz (Que generalmente se llama árbol) y otra que contiene
al elemento y las ramas (que generalmente se llama nodo). Hay que tener cuidado con la
nomenclatura porque a nivel conceptual un nodo puede ser visto como un árbol, pero en estas
implementaciones un nodo es otra cosa de la estructura denominada árbol.
Por lo tanto, todo lo que habíamos desarrollado anteriormente y considerábamos un "árbol"
será considerado un "nodo" en estas implementaciones. ahora cuando se ejecuta una función
sobre un árbol, ésta trabaja sobre la raiz:
class Arbol:
def __init__( self, esMenorFunction = lambda x,y: x < y ):
self.raiz = None
self.esMenor = esMenorFunction

class NodoBinario:
def __init__( self, elemento):
self.elemento = elemento
self.izquierda = None
self.derecha = None

Agregar un elemento:
No hay conceptos nuevos aquí, simplemente que hay que tener en cuenta el caso de un árbol
vacío. Si el árbol no es vacío se resuelve de manera recursiva sobre los nodos:
def agregarElemento(arbol, elemento):
if (arbol.raiz == None):
arbol.raiz = NodoBinario(elemento, None)
else:
subAgregarElementoEnNodo(arbol.raiz, elemento, arbol.esMenor)

def subAgregarElementoEnNodo(nodo, elemento, funcionEsMenor):


if (funcionEsMenor(elemento, nodo.elemento)):
agregarIzquierda(nodo, elemento, funcionEsMenor)
else:
agregarDerecha(nodo, elemento, funcionEsMenor)

def agregarIzquierda(nodo, elemento, funcionEsMenor):


if nodo.izquierda == None:
nodo.izquierda = NodoBinario(elemento, nodo)
else:
subAgregarElementoEnNodo(nodo.izquierda, elemento,funcionEsMenor)

def agregarDerecha(nodo, elemento, funcionEsMenor):


if nodo.derecha == None:
nodo.derecha = NodoBinario(elemento, nodo)
else:
subAgregarElementoEnNodo(nodo.derecha, elemento,funcionEsMenor)

Recorridos:
Tampoco hay conceptos nuevos en el recorrido, la funcion que recorre recibe un árbol y
delega en la función recursiva sobre los nodos
def ejecutarInOrden(arbol, funcion):
subEjecutarInOrden(arbol.raiz, funcion)

def subEjecutarInOrden(nodo, funcion):


if (nodo != None):
subEjecutarInOrden(nodo.izquierda, funcion)
funcion(nodo.elemento)
subEjecutarInOrden(nodo.derecha, funcion)

Eliminar un elemento
La eliminación de un elemento presenta ciertas dificultades debido a que hay que enganchar
los hijos del nodo eliminado con el padre, pero respetando la condición de que un nodo no
puede tener más de dos hijos:
Analicemos los casos particulares:
 Eliminar un nodo hoja (sin hijos)
 Eliminar un nodo con solo un hijo
 Eliminar un nodo con ambos hijos
Eliminar una hoja
Este es el caso más sencillo, ya que no hay que enganchar ningún nodo.
Eliminar un nodo con un hijo
En este caso se procede a remover el nodo, y la única rama hija del nodo pasa a ocupar el
lugar del nodo eliminado en el padre. No importa de si se trata de una rama derecha o
izquierda, el árbol mantiene el orden al realizar esta operación sin preocuparnos de realizar
operaciones adicionales.

Eliminar un nodo con dos hijos


Este es el caso más complejo debido a que en el padre tengo un lugar solo para dos ramas.
La estrategia para removerlo es eliminar el nodo inmediatamente mayor al que se quería
eliminar previamente. Este nodo se encuentra navegando siempre por la izquierda del
hijo derecho. Luego, se reemplaza el elemento del nodo que se quería eliminar por el
elemento del nodo recientemente eliminado. Esto genera un árbol que mantiene el orden sin
realizar una operación adicional, ya que al reemplazar el elemento por su siguiente, se sigue
respetando que a izquierda están los menores y a derecha los mayores. Este procedimiento es
recursivo y tiene como casos base los dos procedimientos explicados anteriormente
(eventualmente llegará a un nodo con un hijo solo o una hoja)
Ejemplo en python

class Arbol:
def __init__( self, esMenorFunction = lambda x,y: x < y ):
self.raiz = None
self.esMenor = esMenorFunction

class NodoBinario:
def __init__( self, elemento, padre ):
self.elemento = elemento
self.izquierda = None
self.derecha = None
self.padre = padre

def eliminar(arbol, element):


eliminarNodo(arbol, buscarNodo(arbol.raiz, element, arbol.esMenor))

def eliminarNodo(arbol, nodo):


if (not tieneHijos(nodo)):
eliminarSinHijos(arbol, nodo)
elif (tieneAmbosHijos(nodo)):
eliminarConAmbosHijos(arbol, nodo)
elif (tieneHijoDerecho(nodo)):
eliminarCon1Hijo(arbol, nodo, nodo.derecha)
else:
eliminarCon1Hijo(arbol, nodo, nodo.izquierda)

def tieneHijoDerecho(nodo): return nodo.derecha != None


def tieneHijoIzquierdo(nodo): return nodo.izquierda != None
def tieneHijos(nodo): return tieneHijoDerecho(nodo) or tieneHijoIzquierdo(n
odo)
def tieneAmbosHijos(nodo): return tieneHijoDerecho(nodo) and tieneHijoIzqui
erdo(nodo)
def esHijoIzquierdo(nodo): return nodo.padre.izquierda == nodo
def esRaiz(nodo): return nodo.padre == None
def eliminarSinHijos(arbol, nodo):
if (esRaiz(nodo)):
arbol.raiz = None
elif esHijoIzquierdo(nodo):
nodo.padre.izquierda = None
else:
nodo.padre.derecha = None

def eliminarCon1Hijo(arbol, nodo, hijo):


if (esRaiz(nodo)):
arbol.raiz = hijo
arbol.raiz.padre = None
elif esHijoIzquierdo(nodo):
nodo.padre.izquierda = hijo
hijo.padre = nodo.padre
else:
nodo.padre.derecha = hijo
hijo.padre = nodo.padre

def eliminarConAmbosHijos(arbol, nodo):


menorHijoRamaDerecha = buscarNodoMenorValor(nodo.derecha)
eliminarNodo(arbol, menorHijoRamaDerecha)
nodo.elemento = menorHijoRamaDerecha.elemento

def buscarNodoMenorValor(nodo):
return nodo if nodo.izquierda
== None else buscarNodoMenorValor(nodo.izquierda)

PRÁCTICA Recorridos en Arboles de


#4.6 Busqueda
Realizar los programas para poder lograr los recorridos en preorden, postorden e inorden en
un árbol binario

3.5 UNIDAD 5
Ordenamiento de Burbuja
El ordenamiento burbuja hace múltiples pasadas a lo largo de una lista. Compara los ítems
adyacentes e intercambia los que no están en orden. Cada pasada a lo largo de la lista ubica el
siguiente valor más grande en su lugar apropiado. En esencia, cada ítem ―burbujea‖ hasta el
lugar al que pertenece.
La Figura 1 muestra la primera pasada de un ordenamiento burbuja. Los ítems sombreados se
comparan para ver si no están en orden. Si hay n ítems en la lista, entonces
hay n−1n−1 parejas de ítems que deben compararse en la primera pasada. Es importante tener
en cuenta que, una vez que el valor más grande de la lista es parte de una pareja, éste avanzará
continuamente hasta que la pasada se complete.
Figura 1: La primera pasada de ordenamientoBurbuja
Al comienzo de la segunda pasada, el valor más grande ya está en su lugar.
Quedan n−1n−1 ítems por ordenar, lo que significa que habrá n−2n−2 parejas. Puesto que
cada pasada ubica al siguiente valor mayor en su lugar, el número total de pasadas necesarias
será n−1n−1. Después de completar la pasada n−1n−1, el ítem más pequeño debe estar en la
posición correcta sin requerir procesamiento adicional. El ActiveCode 1 muestra la
función ordenamientoBurbuja completa. La función recibe la lista como un parámetro, y lo
modifica intercambiando ítems según sea necesario.
La operación de intercambio es ligeramente diferente en Python que en la mayoría de los
otros lenguajes de programación. Normalmente, el intercambio de dos ítems en una lista
requiere una ubicación de almacenamiento temporal (una ubicación de memoria adicional).
Un fragmento de código como intercambiará los ítems ii-ésimo y jj-ésimo de la lista. Sin el
almacenamiento temporal, uno de los valores sería sobrescrito.

temp = unaLista[i]

unaLista[i] = unaLista[j]

unaLista[j] = temp

En Python es posible realizar la asignación simultánea. La instrucción a,b=b,a dará lugar a


que se realicen dos instrucciones de asignación al mismo tiempo (véase la Figura 2). Usando
la asignación simultánea, la operación de intercambio se puede hacer en una sola instrucción.
Las líneas 5-7 en el ActiveCode 1 realizan el intercambio de los ítems ii-ésimo e (i+1)(i+1)-
ésimo utilizando el procedimiento de tres pasos descrito anteriormente. Note que también
podríamos haber utilizado la asignación simultánea para intercambiar los ítems.
Figura 2: Intercambio de dos valores en Python

PRÁCTICA Ordenamiento Burbuja


#5.1
Realice un programa en Python que ordene una lista de números en forma ascendente y
descendente.

El ordenamiento rápido

El ordenamiento rápido usa dividir y conquistar para obtener las mismas ventajas que el
ordenamiento por mezcla, pero sin utilizar almacenamiento adicional. Sin embargo, es posible
que la lista no se divida por la mitad. Cuando esto sucede, veremos que el desempeño
disminuye.
Un ordenamiento rápido primero selecciona un valor, que se denomina el valor pivote.
Aunque hay muchas formas diferentes de elegir el valor pivote, simplemente usaremos el
primer ítem de la lista. El papel del valor pivote es ayudar a dividir la lista. La posición real a
la que pertenece el valor pivote en la lista final ordenada, comúnmente denominado punto de
división, se utilizará para dividir la lista para las llamadas posteriores a la función de
ordenamiento rápido.
La Figura 12 muestra que 54 servirá como nuestro primer valor pivote. Como ya hemos visto
este ejemplo unas cuantas veces, sabemos que 54 eventualmente terminará en la posición que
actualmente contiene a 31. El proceso de partición sucederá a continuación. Encontrará el
punto de división y al mismo tiempo moverá otros ítems al lado apropiado de la lista, según
sean menores o mayores que el valor pivote.
Figura 12: El primer valor pivote para un ordenamiento rápido

El particionamiento comienza localizando dos marcadores de posición -


llamémoslos marcaIzq y marcaDer- al principio y al final de los ítems restantes de la lista
(posiciones 1 y 8 en la Figura 13). El objetivo del proceso de partición es mover ítems que
están en el lado equivocado con respecto al valor pivote mientras que también se converge en
el punto de división. La Figura 13 muestra este proceso a medida que localizamos la posición
del 54.

Figura 13: Encontrar el punto de división para el 54


Comenzamos incrementando marcaIzq hasta que localicemos un valor que sea mayor que el
valor pivote. Luego decrementamos marcaDer hasta que encontremos un valor que sea menor
que el valor pivote. En tal punto habremos descubierto dos ítems que están fuera de lugar con
respecto al eventual punto de división. Para nuestro ejemplo, esto ocurre en 93 y 20. Ahora
podemos intercambiar estos dos ítems y luego repetir el proceso de nuevo.
Nos detendremos en el punto donde marcaDer se vuelva menor que marcaIzq. La posición
de marcaDeres ahora el punto de división. El valor pivote se puede intercambiar con el
contenido del punto de división y el valor pivote está ahora en su lugar (Figura 14). Además,
todos los ítems a la izquierda del punto de división son menores que el valor pivote y todos
los ítems a la derecha del punto de división son mayores que el valor pivote. La lista ahora se
puede dividir en el punto de división y el ordenamiento rápido se puede invocar
recursivamente para las dos mitades.

Figura 14: Finalización para completar el proceso de partición para encontrar el punto de
división para 54

PRÁCTICA Ordenamiento QuickSort


#5.2
Realice un programa en Python que ordene una lista de números en forma ascendente y
descendente.

PRÁCTICA Introducción a LabView I


#5.3

I. Objetivo

Familiarizarse con el ambiente de programación de LabVIEW.


Elaborar un VI (panel frontal y diagrama de bloque) que proporcione los
resultados de las cuatro operaciones básicas empleando operadores aritméticos
y booleanos con los datos introducidos.

II. Introducción Teórica

La programación G (gráfica) de Labview consta de un panel frontal y un panel de


código; en el panel frontal es donde se diseña la interface de usuario y se ubican los
controles e indicadores. En el panel de código se encuentran las funciones. Cada control
que se utiliza en la interfaz tiene una representación en el panel de código, igualmente los
indicadores necesarios para entregar la información procesada al usuario tienen un icono
que los identifica en el panel de código o de programación. Los controles pueden ser
booleanos, numéricos, strings, un arreglo matricial de estos o una combinación de los
anteriores; y los indicadores pueden ser como para el caso de controles pero pudiéndolos
visualizar como tablas, gráficos en 2D o 3D, browser, entre otros.

Las funciones pueden ser VIs prediseñados y que pueden ser reutilizados en cualquier
aplicación, estos bloques funcionales constan de entradas y salidas, igual que en un
lenguaje de programación estándar las funciones procesan las entradas y entregan una o
varias salidas, estos VI pueden también estar conformados de otros subVIs y así
sucesivamente, de esta forma se pueden representar como un árbol genealógico donde un
VI se relaciona o depende de varios SubVIs.

Labview tiene VIs de adquisición de datos e imágenes, de comunicaciones, de


procesamiento digital de señales, de funciones matemáticas simples, hasta funciones que
utilizan otros programas como Matlab o HiQ para resolver problemas, otras más complejas
como "nodos de formula" que se utilizan para la resolución de ecuaciones editando
directamente estas como en lenguajes de programación tradicionales y definiendo las
entradas y las salidas. Labview también se puede utilizar para graficar en tres dimensiones,
en coordenadas polares y cartesianas, tiene disponibles herramientas para análisis de
circuitos RF como la Carta de Smith, tiene aplicaciones en manejo de audio y se puede
comunicar con la tarjeta de sonido del computador para trabajar conjuntamente. Entre sus
muchas funciones especiales se encuentran las de procesamiento de imágenes, como
capturar una imagen a través de una tarjeta de adquisición, analizarla y entregar respuestas
que difícilmente otros sistemas realizarían. Un VI contiene los siguientes tres
componentes:

1. Panel Frontal (Front panel) —Sirve como la interfaz de usuario que es


donde los datos son manipulados controlados y monitoreados.
2. Diagrama de bloque (Block diagram) —contiene el código fuente grafico que
define la funcionalidad del VI. En este se aprecia la estructura del programa, su
función y algoritmo, de una forma gráfica en lenguaje G, donde los datos fluyen a
través de líneas
3. Icono y Bloque de Conectores — Identifica al VI si se usa en otro VI. Un VI que
se utiliza en otro es llamado subVI. Un subVI corresponde a una subrutina.El Panel
es la interfaz de usuario del VI. El panel frontal se construye con controles e
indicadores, los cuales son las terminales de entradas y salidas de un VI,
respectivamente. Entre los controles tenemos perillas, pushbuttons, y otros
dispositivos de entrada. Los indicadores pueden ser gráficos, LEDs, y otros
displays. Los controles simulan instrumentos de entradas y entregan los datos en
el diagrama de bloques del VI. Los indicadores simulan los instrumentos de salida
y muestran los datos que el diagrama de bloques genera o adquiere.
Panel Frontal de una aplicación
en LabVIEW

Una vez construido el panel frontal, en el diagrama de bloques se agrega el código gráfico,
representando las funciones de control de los objetos del panel frontal. En el diagrama de
bloques esta contenido el código fuente del programa. Los objetos del panel frontal
(controles e indicadores) aparecen como terminales en el diagrama de bloques.

Diagrama de bloques en una


aplicación en LabVIEW

Adicionalmente, el diagrama de bloques contiene las librerías de LabVIEW como son las
funciones y estructuras para construir nuestro programa. En el diagrama de bloques se
alambran cada nodo incluidos las terminales de los controles e indicadores, funciones y
estructuras.
PALETAS DE LABVIEW

Paleta de Herramientas
(Tools Palette)
La paleta de Herramientas está habilitada tanto en el Front Panel
como en el block diagram. Una herramienta es un modo especial del
cursor del Mouse. Cuando se selecciona una herramienta, el cursor cambia
de icono al icono de la herramienta. Utilice las herramientas para operar o
modificar los objetos del front panel y block diagram. Para mostrar la
paleta de herramientas debes seleccionar de la barra de menús
Window»Show Tools Palette. Se puede colocar la paleta de herramientas
en cualquier parte de la pantalla.

Si está habilitada la selección automática de herramientas al mover el


cursor sobre los objetos del front panel o block diagram, y LabVIEW
automáticamente selecciona la correspondiente herramienta de la paleta.

Las opciones que presenta esta paleta son


las siguientes:

Operating tool – Cambia el valor


de los controles.
Positioning tool – Desplaza, cambia de tamaño y selecciona los objetos.
Labeling tool – Edita texto y crea etiquetas.
Wiring tool – Une los objetos en el
diagrama de bloques.
Object Pop-up Menu tool – Abre el menú
desplegable de un objeto.
Scroll tool – Desplaza la pantalla sin necesidad de emplear las barras de
desplazamiento.
Breakpoint tool – Fija puntos de interrupción de la ejecución del programa
en VIs, funciones y
estructuras.
Probe tool – Crea puntos de prueba en los cables, en los que se puede visualizar el
valor del dato que fluya por dicho cable en cada instante.
Color Copy tool – Copia el color para después establecerlo mediante la
siguiente herramienta.

Color tool – Establece el color de fondo y el

de los objetos.
Automatic Tool Selection – El puntero cambia de forma automática sin la
necesidad de estar abriendo la paleta de herramientas.
Paleta de controles (Controls palette)
La paleta de controles está habilitada únicamente en el panel frontal. La paleta de
controles contiene los
controles e indicadores que se necesitan para crear el panel frontal. Selecciona de la barra de
menús Window»Show Controls Palette o dar un clic derecho en el área de trabajo del
front panel para mostrar la paleta de controles. Se puede colocar la paleta de control en
cualquier parte de la pantalla.

Num Ctrls – Para la introducción de cantidades numéricas.

Num Inds – Para la visualización de cantidades numéricas.

Buttons – Para la entrada de valores booleanos.

LEDs – Para la visualización de valores booleanos.

Text Ctrls – Para la entrada de texto.

Text Inds – Para visualizar texto.

Graph – Para representar gráficamente los datos.

User Ctrls – Para elegir un control creado por el propio usuario.

All Control – Muestra todos los controles que posee LabVIEW.


Paleta de funciones (Functions palette)

La paleta de funciones (ver figura 6) esta habilitada solo en el block diagram. La paleta de funciones
contiene los VIs y funciones que se necesitan para construir el diagrama de bloques. Selecciona de la
barra de menús Window»Show Functions Palette o haz clic derecho sobres el área de trabajo
del block diagram para mostrar la paleta de funciones. Se puede colocar la paleta de funciones en
cualquier parte de la pantalla.

Exec Ctrl – Muestra las estructuras de control del programa.

Arith/Compare – Muestra funciones aritméticas, constantes numéricas, funciones que sirven


para comparar números, valores booleanos o cadenas de caracteres, y muestra funciones y
constantes lógicas.

Analysis – Contiene un submenú en el que se puede elegir entre una amplia gama de funciones
matemáticas de análisis.

User Libraries – Muestra las librerías definidas por el usuario.

Input– Contiene un submenú donde puede elegirse entre distintas librerías referentes a la
adquisición de datos.

Output– Contiene un submenú donde puede elegirse entre distintas librerías referentes al

manejo de
periféricos; esta carpeta es complemento de Input.

All Functions – Muestra todas las funciones que posee LabVIEW.

Sig Manip – Manipula, selecciona, señales para procesarlas.


Las funciones son los elementos de operación esenciales para programar en LabVIEW. Las
funciones no tienen representación en el Panel Frontal, no se pueden abrir ni editar. La paleta de
funciones también incluye los VI que vienen con LabVIEW. Hay diversos tipos de funciones en
LabVIEW las más ocupadas son:

Funciones Numéricas: Se usan las funciones numéricas para crear y ejecutar operaciones
aritméticas, trigonométricas, logarítmicas y complejas, también para convertir números de un tipo a
otro.

Funciones Booleanas (lógicas): Con ellas se ejecutan operaciones lógicas en valores


boléanos.

Funciones de hileras: Se utilizan para realizar: concatenaciones entre dos o más hileras, extraer un
rango de caracteres, buscar y reemplazar uno o más caracteres, convertir datos numéricos a hileras,
dar formato a una hilera para usarse en un procesador de texto o en una hoja de cálculo.
Funciones de comparación (relación): Se comparan valores boléanos, de hileras, numéricos,
arreglos y clusters.

Funciones de diálogos y tiempo: Se usan para manipular la velocidad a la que se ejecuta una
operación, obtener la fecha y hora de la computadora, crear cajas de diálogo para pedir al usuario
más instrucciones, etc.

Las funciones de los botones del Diagrama de Bloques es el


siguiente:
Para poder adquirir una mayor habilidad en el uso del software mostramos la siguiente ayuda:

Cuando su VI no es ejecutable, se despliega una flecha quebrada en el botón de correr en la


paleta de herramientas.

Encontrando los Errores: Para hacer una lista de los errores, haga clic en la flecha quebrada. Para
localizar el objeto malo, haga clic en el mensaje del error.

Resaltando la Ejecución: Anima el diagrama y traza el flujo de datos, permitiéndole ver


los valores intermedios. Haga clic en el bombillo incandescente (light bulb) en la barra de
herramientas.

Probe: Utilizado para ver los valores en los arrays (arreglos) y clusters. Haga clic en los
cables con la herramienta Probe o haga clic derecho en el cable para ajustar los probes.

Punto de Paro (Breakpoint): Coloca pausas en diferentes lugares del diagrama. Haga clic en los
cables o en los objetos con la herramienta de Punto de Paro para colocar los puntos de paro.

III. Requerimientos de material y equipo


- Una computadora personal con software LabVIEW versión 7 o superior instalado
- Manual de Laboratorio

IV. Procedimiento

1. Inicie el programa LabVIEW, puede encontrarlo desde el Menú Inicio >> Programas >>
National
Instruments >> LabVIEW 7.0 ó la versión que se encuentre instalada.
2. Observe las diferentes categorías que se encuentran en la ventana desplegada las cuales
corresponden a cada uno de los tipos de tareas que puede elegir. También puede seleccionar VI
from Template para ver formas o plantillas prediseñadas que te brindan puntos de partida para
tus aplicaciones. Las opciones de Projects y Other Files son componentes más avanzados. Para
informarte más sobre cualquiera de los componentes listados en New Dialog Box, puedes
obtenerla seleccionando con un clic Help, ubicado en la esquina inferior derecha de esa ventana.
Ojo: esta ventana podrá variar levemente de apariencia según la versión del software, pero las
opciones
se mantendrán.
3. Da un doble clic en Blank VI.

Se abrirán dos ventanas. La ventana con fondo color gris es el Front Panel (Panel Frontal), y el
de fondo blanco es el Block Diagram (Diagrama de Bloques).

PANEL FRONTAL

1. Haremos una aplicación donde se observe el resultado de las 4 operaciones matemáticas básicas
dados dos números enteros sin signo, los cuales serán denominados A y B y al mismo tiempo
dichos números serán comparados entre si indicando si estos son iguales o uno es mayor que el
otro. El panel frontal base, será el siguiente:

2. De la paleta de herramientas seleccione la opción de los controles numérico


3. Ahora insertaremos dos controles numérico que se llamara, A y B

4. Para poder ver los resultados de las operaciones, emplearemos los indicadores numéricos

5. Inserte 4 visualizadores, los cuales representaran los resultados de las 4 operaciones básicas

6. Nuevamente de la paleta de herramientas seleccione la opción

7. Seleccione cualquiera de los tipos de led

DIAGRAMA DE BLOQUES

8. Cambie al Diagrama de Bloques, para realizar las conexiones de las diferentes terminales, de
manera que en los indicadores obtengamos el resultado esperado
9. Seleccione la opción Arith &
Compar

10. Para los operadores matemáticos utilice

11. Y para los operadores de relación utilice

12. Compruebe el funcionamiento del VI, haciendo click en . Esto ejecutará el programa una
sola vez. Si cambiamos los valores de los controles digitales no veremos el resultado hasta que
los presionemos nuevamente.

13. Si presionamos el botón el programa se ejecutará continuamente, por lo que si cambiamos los
valores de los controles el resultado se refrescará instantáneamente. Pulsando sobre los botones de
stop y pausa, respectivamente, podemos detener la ejecución definitiva o temporalmente.

14. Introduzca diferentes valores en A y B y compruebe los resultados.

SUSTITUCIÓN DE CONTROLES E INDICADORES

Sustituiremos o reemplazaremos los controles existentes por otros diferentes, por lo que
cambiaremos la parte correspondiente a la interfaz de usuario, no a la funcionalidad.

15. Posiciónese sobre los controles numéricos y haga click derecho, aparece un pequeño menú,
del cual elegiremos la opción Replace, seleccione Num Ctrls

16. Ahora emplee slide, knob o Dial.

17. Para los resultados, hacemos un procedimiento similar al anterior, click derecho sobre el
indicador y seleccionamos Replace, siempre utilizaremos Num Inds

18. Aquí podrá seleccionar Bar, Tank, Gauge o Meter. Para cambiar la escala, basta con
sobreescribir el nuevo valor sobre el máximo establecido por default con la herramienta de
escritura.
V. Ejercicios a Evaluar

Resuelva los problemas que se le presentan a continuación:


a) Realice un Instrumento Virtual capaz de calcular el valor de M, de la siguiente
ecuación: M2=(X2+Y2)/4, y que al mismo tiempo posea un Led que indique cuando la
respuesta (M) sea mayor que 100.

b) Realice un Instrumento Virtual que sea capaz de determinar la corriente (I) a través de un
resistor (R) dependiendo del voltaje (V) con que se está alimentando el Resistor.

PRÁCTICA Introducción a LabView II


#5.4

I. Objetivo

Familiarizarse con el ambiente de programación de LabVIEW.


Cambiar las propiedades de cada uno de los controles e indicadores
utilizados. Elaborar un VI que permita convertir entre diferentes unidades
de temperatura. Realizar una serie de ejercicios aplicando LabVIEW.

II. Requerimientos de material y equipo


- Una computadora personal con software LabVIEW versión 7 o superior instalado
- Guía de laboratorio Nº 2

III. Procedimiento

1. Proceda a ejecutar LabVIEW y a crear un instrumento virtual en blanco (Blank VI).


2. Disponga de las ventanas del panel frontal y el diagrama de bloques para facilitar su
visualización, para ello presione las teclas Ctrl+T.
3. Ahora proceda a realizar un programa que permita realizar conversiones de temperaturas entre las
unidades Kelvin, Fahrenheit y Celsius. Para ello, tome como referencia el diseño del panel frontal
y el diagrama de bloques que se le presenta en la figura 3.1 y 3.2.

Figura 3.1. Panel frontal de la aplicación.


Figura 3.2. Diagrama de bloques de la
aplicación.

4. Para crear la interfaz grafica de usuario (panel frontal) inserte los respectivos controles e
indicadores. Los controles se encuentran en la paleta Controls >> Num Ctrls >> Num Ctrl.

5. Los indicadores a utilizar también se encuentran en la paleta de controles. Se utilizaran


indicadores numéricos y booleanos. La ubicación de los indicadores numéricos es
Controls>>Num Inds>>Thermometer; los indicadores booleanos se encuentran en
Controls>>LEDs.
6. Para cambiar el nombre de la etiqueta de un control o indicador debe dar doble click izquierdo
sobre la etiqueta, luego cambie el nombre de la etiqueta remplazando el nombre de ―Numeric‖ por
―Ingrese temperatura en ºC‖ como se muestra en la figura 3.3 y 3.4. Realice el mismo
procedimiento para todos los controles e indicadores.

Figura 3.3
Figura 3.4

7. Una vez insertados los indicadores Thermometer cambiaremos algunas de sus propiedades. Al
indicador Thermometer le podemos cambiar la escala de representación simplemente
dando click sobre el número del límite inferior y reescribir el número mayor que se espera que
tenga la escala, y repitiendo el proceso con el límite inferior para el menor valor de la escala.

8. Podemos agregar un visualizador del valor de la barra para que sea más fácil la lectura, esto
se hace dando click derecho sobre el indicador y, del menú desplegable, seleccionar la opción
Visible Items>>Digital Display.
9. Además se puede cambiar los colores de la barra indicadora (Thermometer) dando click
derecho sobre ella y seleccionando la opción Properties. Luego se mostrara una ventana como la
que se muestra en la figura 3.5. En la parte inferior de la ventana se ubica una etiqueta designada
como Fill, con esa opción cambie el color que tendrá el indicador Thermometer.

Figura 3.5. Ventana de propiedades para indicadores.

10. Una vez conectado el diagrama de bloques y finalizado el panel frontal, ejecute el
programa y compruebe su funcionamiento. Con la ayuda del programa complete la Tabla 3.1.
Para comprobar sus resultados puede realizar los cálculos manualmente auxiliándose de las
ecuaciones planteadas en la tabla
3.2.

ºC ºF ºK
10 50 283.15
25
78.5
87.3
100
Tabla 3.1. Conversión de grados Celsius a Fahrenheit y Kelvin.

Tipo Ecuación para


de conversión
conversió
De ºC a ºF ºF = (1.8 x ºC) + 32
De ºCn a ºK ºK = ºC + 273.15
De ºF a ºC ºC = (ºF – 32)/1.8
De ºF a ºK ºK = (ºF + 459.67)/1.8
De ºK a ºC ºC = ºK – 273.15
De ºK a ºF ºF = (1.8 x ºK) - 459.67
Tabla 3.2. Ecuaciones para conversiones entre diferentes unidades.

11. Una práctica recomendable en todos los controles e indicadores es añadirles un texto que indique
la función que realizan en la ventana que aparece al seleccionar en el menú contextual Description
and Tip. Este texto se mostrará en la ventana de ayuda contextual. Para agregar esta función a los
controles procedemos a dar click derecho sobre un control o indicador especifico, luego
seleccionamos la opción Description and Tip. Una vez hecho esto aparecerá una ventana como la
que se muestra en la figura 3.6.
Figura 3.6. Ventana de edición Description and Tip.

12. En la parte superior se encuentra la edición para Description, que es donde se escribe la
descripción de la función de dicho control. En la parte inferior se encuentra la edición para Tip,
aquí se escribe un texto breve que aparecerá cada vez que situemos el puntero del ratón sobre
dicho control. Edite por lo menos tres controles y tres indicadores del VI.

IV. Ejercicios a Evaluar

Presente funcionando los programas que se le plantean a continuación, solicite a su instructor la


revisión de dichos programas al concluir el desarrollo de ambos ejercicios.

1. Diseñe una VI que permita calcular la densidad (ρ) de una sustancia si dicho sustancia
posee una masa(m) y un volumen(V), expresar dicha respuesta con las siguientes unidades
(kg/m 3) y (g/cm3) al mismo tiempo que el programa debe de forzar al usuario a introducir los
valores de masa en kg y de volumen en cm3. Recuerde que (ρ.V)/m=1

2. Realice un VI que calcule las magnitudes involucradas en la ley de Ohm. El programa deberá
permitir al usuario calcular cualquier magnitud a partir de las otras dos. Por ejemplo, si el usuario
desea calcular corriente, en el programa se deben introducir los valores de voltaje y resistencia
respectivos.
PRÁCTICA Introducción a LabView I
#5.5

I. Objetivo

Identificar los tipos de datos con los que cuenta LabVIEW, analizar sus características y
aplicarlos en la creación de programas.
Implementar diferentes operadores aritméticos y lógicos, así como también, el uso de
funciones para enlazar cadenas de caracteres con datos numéricos y booleanos.

II. Introducción Teórica

LabVIEW es un lenguaje de programación grafico que ofrece una gran variedad de datos, operadores
y funciones que permitan realizar programas con gran flexibilidad y aplicación. A continuación se
detallan las funciones que se utilizaran en la práctica a realizar.

FUNCIONES ARITMÉTICAS
Number To Boolean
Array
Convierte un numero entero a un arreglo booleano de 8, 16 ò 32 elementos, dependiendo del
numero de bits que se necesiten para representar el numero entero. El último elemento del arreglo
obtenido representa el bit más significativo. (Functions>>All
Functions>>Numeric>>Conversion>>NumberToBooleanArray).

FUNCIONES CON CADENAS


Number To Octal
String
Convierte un numero entero a una cadena de caracteres que lo representa en base octal. Number puede
ser un numero escalar (o un arreglo de escalares). Width(-) debe ser un numero el cual indica con
cuantos dígitos se desea representar el numero en base octal. Octal integer string es la cadena de
caracteres en base octal resultante. (Functions>>All
Functions>>String>>String/NumberConversion>>NumberToOctalString).

Number To Hexadecimal
String
Convierte un número entero a una cadena de caracteres que lo representa en base hexadecimal.
Number puede ser un numero escalar (o un arreglo de escalares). Width(-) debe ser un numero el cual
indica con cuantos dígitos se desea representar el numero en base hexadecimal. Octal integer string
es la cadena de caracteres en base hexadecimal resultante.
(Functions>>AllFunctions>>String>>String/NumberConversion>>Number To Hexadecimal
String).
Build
Text
Concatena una cadena de entrada. Si la entrada no es una cadena, este VI Express convierte la
entrada en una cadena basada en la configuración de la VI Express. Al abrir la pantalla de
propiedades se visualiza una ventana como la que se muestra en la figura 1. En dicha ventana se
configuran las variables de entrada que se tendrán (datos de cualquier tipo) y los textos con los cuales
se concatenaran dichas variables. (Functions>>String>>BuildText).

Figura 1. Ventana de propiedades del VI Express Build

Text. FUNCIONES CON ARREGLOS


Reverse 1D
Array
Invierte el orden de los elementos de un arreglo de una dimensión. Array es la entrada para el arreglo
de una dimensión y Reversed array es el arreglo de entrada pero de forma invertida.
(Functions>>AllFunctions>>Array>>Reverse1DArray).

III. Materiales y
Equipo

Guía de Laboratorio Nº 3
Computadora con software LabVIEW 7 o superior instalado

IV. Procedimiento

1. Proceda a ejecutar LabVIEW y a crear un instrumento virtual en blanco (Blank VI).


2. Disponga de las ventanas del panel frontal y el diagrama de bloques para facilitar su
visualización, para ello presione las teclas Ctrl+T.
3. Ahora proceda a realizar un programa que permita convertir un número entero a su
representación octal, hexadecimal y binaria. En la figura 2 se muestra el respectivo panel frontal
del programa a realizar. Ojo: Los elementos marcados serán insertados más adelante.
Figura 2. Panel
Frontal.

4. Ahora procederemos a cambiar la representación del control numérico donde se ingresa el numero
a convertir. Para ello, de click derecho sobre el respectivo control (Numero a Convertir) y
del menú desplegable seleccione la opción Representation>>Unsigned Byte. De la misma forma
configuramos un display que nos indicara en que base esta representado el dato de entrada, para
ello de click derecho sobre el control y seleccione la opción Visible Items>>Radix.
5. El diagrama de bloques se presenta en la figura 3. Los elementos marcados serán
agregados más adelante.

Figura 3. Diagrama de
bloques.
6. Una vez insertada y conectada la función Reverse 1D Array, situaremos el cursor del ratón en la
parte central derecha del icono de la función antes mencionada (figura 4). Una vez situado el
cursor del ratón damos click derecho y se desplegara una ventana de opciones de la cual
seleccionaremos Create>>Indicator. Con lo anterior aparecerá el terminal de un arreglo booleano
tanto en el panel frontal (figura 5) como en el diagrama de bloques (figura 6).

Figura 4. Creación de Indicador para arreglo. Figura 5. Indicador de arreglo en


panel frontal

Figura 6. Icono de indicador de arreglo en el diagrama de bloques.

7. Seleccione el terminal del indicador booleanos en el panel frontal y ajuste el tamaño para
visualizar ocho elementos, esto lo logra arrastrando el recuadro que aparece alrededor del primer
Led mostrado en (figura 7).

Figura 7. Cambiando el número de elementos del


arreglo.

8. Para configurar el VI Express Build Text tome como referencia la figura 1, coloque las opciones y
textos mostrados en dicha figura.
9. Compruebe el funcionamiento del programa y muestre el funcionamiento a su docente.
10. Cambie la representación del control numérico ―Numero a Convertir‖ a Extended Precision y
conteste,
¿Qué cambios ocurrieron en el funcionamiento del arreglo booleano en el panel
frontal? Explique_

11. Cambie la representación a Unsigned Byte para el control numérico nuevamente. Ahora
procederemos a cambiar el rango del control donde ingresamos el numero a convertir, para
ello damos click derecho sobre el control en el panel frontal y seleccionamos la opción
Properties. Luego se mostrara una ventana de la cual seleccionara la pestaña Data Range y
colocara las configuraciones mostradas en la figura 8.
Figura 8. Configuración del rango e incremento de un control numérico.

12. Ejecute nuevamente el programa con las modificaciones realizadas y explique la función
para las opciones Maximum e Increment.

V. Ejercicio a
Evaluar
Modifique el programa anterior de tal forma que el mínimo valor que se pueda ingresar sea de
10 y el máximo valor sea de 255, con incrementos de 5. Además, el programa debe indicar al usuario
el número de bits necesarios para representar el número que ha ingresado. Por ejemplo: si el usuario
ingresa el número 8, se deberá mostrar un mensaje que diga: ―El número 8 se puede representar con 4
bits‖.
PRÁCTICA Introducción a LabView I
#5.6

I. Objetivos

Identificar las características y representaciones de cada uno de los tipos de variables


aplicados en
LabVIEW.
Definir y aplicar variables LOCALES y GLOBALES, y a partir de sus características,
identificar cuáles son los ámbitos en las que se pueden aplicar.
Diseñar un VI en el cuál se apliquen los tipos de variables estudiados, y poder así, determinar
cuál es su función dentro de un VI.

II. Introducción Teórica

Las variables son imprescindibles en cualquier tipo de problemas, ya que permiten


almacenar la información necesaria para su resolución. En LabVIEW todos los controles
introducidos en el panel frontal que generan un terminal en la ventana Block Diagram van a ser
variables, identificables por el nombre asignado en la etiqueta. Pero puede ocurrir que queramos
utilizar el valor de cierta variable en otro subdiagrama o en otro VI o, simplemente, que
queramos guardar un resultado intermedio. La forma más sencilla de hacerlo es generando variables
locales y/o globales dependiendo de la aplicación.

VARIABLES
LOCALES
En las variables locales los datos se almacenan en algunos de los controles o indicadores existentes
en el panel frontal del VI creado; es por eso que estas variables no sirven para intercambiar datos
entre VI’s. La principal utilidad de estas variables radica en el hecho de que una vez creada la
variable local no importa que proceda de un indicador o de un control, ya que se podrá utilizar en un
mismo diagrama, tanto de entrada como de salida.

Las variables locales están disponibles en el menú All Functions/Structures de la paleta Functions
en el diagrama de bloques y disponen del siguiente menú Pop-up (figura 1):
Visible Items: oculta o visualiza la etiqueta de identificación de la estructura y, si no existe,
permite ponerla.
Find: permite encontrar el control y terminal del cual procede la variable local, así como otras
variables locales de ese mismo control.
Change to Read/Change to Write: permite entre leer o escribir en el
control.
Select Item: visualiza una lista con el nombre de todos los controles existentes en el panel
frontal y de ella escogeremos el control al cual queremos que haga referencia nuestra variable.
Es por esto que para poder crear la variable local será imprescindible que el control tenga
asignado un nombre de identificación. Una vez creada la variable local, si en algún momento se
cambia el nombre del control de
1
origen, no será necesario cambiar también el nombre de la variable local, ya que LabVIEW
actualiza los cambios.
Description and Tip: permite añadir comentarios.
Set Breakpoint: pone un punto de ruptura para depuración.
Create: crea un control, indicador o constante conectados a esa variable local.
Replace: sustituye la variable local por cualquier otra función.

Figura 1. Menú pop-up de las variables locales.

VARIABLES
GLOBALES
Las variables locales son un tipo especial de VI, que únicamente dispone de panel frontal, en el
cual se define el tipo de dato de la variable y el nombre de identificación imprescindible para
podernos referir a ella.

Cuando escogemos la función Global del menú All Function/Structures creamos un nuevo
terminal en el diagrama; este terminal corresponde a un VI que inicialmente no contiene ninguna
variable. Para poderlas añadir haremos doble click en el terminal y se abrirá el panel frontal. Una vez
abierto, las variables se definirán igual que cualquier control o indicador de un VI normal. Podemos
crear un VI para cada variable global o definirlas todas en el mismo, que es la opción más indicada
para cualquier aplicación. Cuando terminemos de colocar todas las variables guardaremos el VI con
un nombre específico y lo cerraremos. Si una vez cerrado queremos añadir nuevas variables, bastará
con volverlo abrir e introducir los cambios necesarios.

Para agregar la variable global en otro VI, ejecutamos el comando Select a VI…del menú
Functions/ All Functions (en el Block Diagram) y buscamos la variable en el lugar donde ha sido
guardada, y finalmente la agregamos a la aplicación. El menú asociado a una variable global es
prácticamente similar al de una local.

III. Requerimientos de material y


equipo
Una computadora con software LabVIEW versión 7.1 o superior instalado
Guía de Laboratorio 4

2
IV. Procedimiento

PARTE I. Aplicación de variables locales


1. Crear una carpeta en el escritorio con el nombre ―Variables‖. En esta carpeta se guardaran todos los
VI’s
desarrollados durante esta practica de laboratorio.
2. Ahora proceda a diseñar un VI para poder visualizar el estado de llenado de tres tanques de agua
(A, B y C), además, el VI debe tener un indicador para visualizar el estado de llenado de los
tanques y debe contar con un menú que permita seleccionar al usuario entre las opciones: 1.
Estado Tanque A, 2. Estado Tanque B, 3. Estado Tanque C. Además, en un tablero se deben
indicar con LED’s, cuando un tanque a alcanzado su nivel máximo y mostrar en un indicador el
promedio de llenado alcanzado por los tres tanques. A continuación, se muestra como debería de
quedar el FRONT PANEL (figura 2):

Figura 2. Panel Frontal de la aplicación de variables


locales.

3. A continuación se le presenta la ubicación algunos de los controles que utilizara durante la practica:

1
Tank: Controls/NumInds/Tank Fill Slide: Controls/NumCtrls/Fill Slide Menu Ring:
Controls/TextCtrls/MenuRing

Configuración del control Menu Ring


Una vez insertado en control Menu Ring, damos click derecho sobre dicho control. Luego se
desplegara un menú del cual elegiremos la opción Properties. A continuación aparecerá una ventana
como la que se muestra en la figura 3. Seleccionamos la pestaña Edit Items y editamos los textos que
queremos que se desplieguen.

Figura 3. Ventana Properties de un control Menu Ring.

4. No olvide cambiar la representación numérica a I32 (enteros de 32 bits) todos los


indicadores y controles.

5. Ahora realice las siguientes conexiones en el Block Diagram (ver figura 4). A continuación se
explica algunas funciones que utilizara en el alambrado.

Compound Arithmetic: es una función aritmética que permite realizar entre las siguientes
operaciones: Add, Multiply, AND, OR, y XOR. Para cambiar la operación a realizar, solo basta
con hacer click derecho sobre el icono, y seleccionar la opción ―Change Mode‖.

2
Figura 4. Diagrama de bloques de la aplicación de variables
locales.

6. Para poder insertar las variables locales denominadas en el Block Diagrama como Tanque A,
Tanque B y Tanque C, debe hacerlo desde la paleta de Functions/All Functions/Structures/Local
Variable. El icono es de la siguiente forma:

7. Al insertar una variable local, podemos apreciar que el icono tiene un signo de interrogación,
eso se debe a que no tiene ningún control o indicador asignado. Para ello, de un click izquierdo
sobre el icono, luego se desplegara un menú con los nombres de todos los controles e indicadores
que están en su respectivo Front Panel. Seleccione los indicados en el numeral 6.

3
8. Para cambiar el modo de operación de las variables (Read ó Write) es necesario que de click
derecho sobre el icono y luego seleccione Change to Write ó Change to Read, como mejor
convenga. Para nuestro caso las variables locales deben estar configuradas como Change to Read.

9. Muestre el funcionamiento de dicho programa a su instructor, luego guárdelo con el


nombre
―Monitor1.VI‖ en la carpeta creada al inicio de la práctica.

PARTE II. Aplicación de Variables Globales

10. Inserte una variable global en el Block Diagram y realice su respectiva configuración, para ello de
doble clic sobre el icono de la variable global, luego se mostrara un Front Panel donde se
insertaran cuatro Num Inds. NOTA: cambiar su representación para que sean enteros de 32 bits
(I32).

11. Este VI (o variable global) llevara por nombre ―GlobalLEDS.VI‖, y debe guardarlo en la carpeta
creada en el escritorio. Después de guardar las configuraciones en ―GlobalLEDS.VI‖ cierre el
Front Panel de dicha variable.

12. Ahora, diseñe un nuevo VI con la siguiente interfaz en el Front Panel y el siguiente Block Diagram
(figura
5). Este llevara por nombre ―Monitor2.VI‖.

Figura 5. Panel Frontal y Diagrama de bloques para Monitor2.VI.


4
13. Para insertar la variable global creada anteriormente debe de seguir los siguientes pasos:
a) En la paleta de Functions (del Block Diagram) seleccione la opción All Functions.
b) Luego seleccione la opción Select a VI (figura 6).

Figura 6. Insertar variable global existente en


LabVIEW.

14. Busque la variable global por su correspondiente nombre (recuerde que es la que denomino
con ―GlobalLEDS.VI‖). Después de haber insertado la variable global, puede cambiar, al igual que
en las variables locales, el Num Ind al cual se desea que haga referencia esa variable.
15. Luego, realice las siguientes modificaciones en el Block Diagram del ―Monitor1.VI‖ (figura 7).

Figura 7. Modificaciones en el diagrama de bloques de


Monitor1.VI
5
16. Antes de poner a correr ambos programas, llame a su instructor para pedir revisión en las
aplicaciones realizadas.
17. Verifique el funcionamiento de dichos programa.
18. Luego de la respectiva revisión, elimine la carpeta donde guardo los programas realizados durante
esta práctica, luego apague su equipo y deje su lugar de trabajo en orden.

V. Análisis de Resultados

1. Resuelva el problema que se le presenta a continuación:


Realice un VI que simule un control de nivel mediante LED’s. El nivel máximo alcanzado debe ser de 10
y el valor mínimo debe ser de 0. Además los LED deben de permanecer encendidos, aún cuando se haya
superado el nivel que representan. Por ejemplo, si el ―Control de Nivel‖ se encuentra en 4, deben de estar
encendidos los LED que representan al nivel 1, 2, 3 y 4 respectivamente. A continuación se
presenta el Front Panel de dicho programa

6
PRÁCTICA Introducción a LabView I
#5.7

I. Objetivos
Implementar Nodo de Formulas y Nodo de Expresión para dar soluciones a ecuaciones
matemáticas complejas. Conocer la sintaxis del Nodo de Formulas, y analizar las diferencias
de este con el Nodo de Expresión.

II. Requerimientos de material y equipo


Una computadora con software LabVIEW versión 7.1 o superior instalado
Guía de Laboratorio Nº 5

III. Procedimiento
PARTE I. Nodo de Formula.
1. Crear un nuevo VI y guárdelo en el escritorio de su PC con el nombre ―practica5.1‖.
2. Ahora procederá a diseñar un VI que permita calcular la longitud de la hipotenusa de un
triangulo rectángulo en función de sus dos catetos A y B. Para ello, considere el panel frontal
que se le presenta en la figura 1.

Figura 1. Panel
Frontal.

7
3. El diagrama de bloques de la aplicación se presenta en la figura 2.

Figura 2. Diagrama de
Bloques.

4. La estructura Nodo de Formula (Formula Node) se


encuentra en
Functions>>AllFunctions>>Structures>>FormulaNode.

5. Recuerde configurar la representación de los controles ―Cateto A‖ y ―Cateto B‖ como U32


(Unsigned Long) y los indicadores como DBL (Double Precision). Complete la tabla Nº 1 con
los datos obtenidos.
6. Realice el mismo programa utilizando las funciones comunes de LabVIEW, compruebe su
funcionamiento y no olvide mostrar sus resultados a su docente.

Cateto A Cateto B Hipotenus


a

Tabla 1. Catetos de un triangulo rectángulo.

8
PARTE II. Aplicación de Nodo de Formula y Nodo de Expresión.
7. Crear un nuevo VI con el nombre ―practica5.2‖.
8. Este VI permitirá realizar un análisis de un circuito resistivo mixto, para lo cual, dicho VI
permitirá al usuario ingresar valores de resistencia y voltaje, y a partir de dichos parámetros,
mostrara la corriente total del circuito e indicara si se ha superado el nivel máximo de voltaje de
entrada. Para el planteamiento anterior tome como ejemplo el panel frontal de la figura 3.

Figura 3. Panel Frontal VI para análisis de


circuitos.

9. Armar el respectivo diagrama de bloques basándose en el de la figura 4. Los controles e


indicadores utilizados deberán estar configurados para una representación DBL(Double
Precision).

Figura 4. Diagrama de Bloques del VI para análisis


de circuitos.

9
10. El Nodo de Expresión representado de la siguiente manera puede ser encontrado en la
paleta de herramientas en la siguiente ubicación:
Functions>>AllFunctions>>Numeric>>Expression Node.

11. Haciendo uso de la estructura Nodo de Formula y Nodo de Expresión, modificar el VI


anterior de tal forma que el usuario permita conocer la corriente que circula por R3 (IR3) y
R4 (IR4), y la resistencia total del circuito (RT). Muestre el funcionamiento del VI a su
respectivo docente.
12. Apague su equipo y deje su lugar de trabajo ordenado.

IV. Análisis de Resultados


Muestre a su docente el funcionamiento de todos los VI’s realizados durante el desarrollo de
la práctica con sus respectivas modificaciones

4. BIBLIOGRAFÍA

Barcons Gloria T (1991): Cardivillo Carlos J y Ramírez Jesús Alberto, Computación II,
Universidad Nacional Abierta, Caracas.

Brassard G. y Bratley P(2000): Fundamentos de Algoritmia, Prentice may.

Joyanes Aguilar, L (2003): Fundamentos de programación, Algoritmos y Estructuras de


datos y Objetos, Madrid, McGraw-Hill.

Torrealba Javier (2004): Computación I, Universidad Nacional Abierta, Caracas

10