Академический Документы
Профессиональный Документы
Культура Документы
PROGRAMACIÓN ORIENTADA A
OBJETOS
ACADEMIA DE INGENIERÍA
BIOMÉDICA
1. PRESENTACIÓN
Estimado Estudiante:
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.
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.
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.
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
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):
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.
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
>>>
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.
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
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()+"-->"))
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
>>> 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.
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()
>>> 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 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):
self.dato = dato
self.prox = prox
def __str__(self):
return str(self.dato)
>>> v3=Nodo("Bananas")
>>> print v1
Manzanas
>>> print v2
Peras
>>> print v3
Bananas
while nodo:
# muestra el dato
print nodo
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
Manzanas
>>> v3=Nodo("Bananas")
>>> lista=v1
>>> verLista(lista)
Manzanas
Peras
Bananas
>>> 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.
class ListaEnlazada(object):
def __init__(self):
self.prim = None
self.len = 0
Si no se indica posición, i toma la última posición de la lista. Esto se resuelve con este
fragmento de código:
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:
if i == 0:
dato = self.prim.dato
self.prim = self.prim.prox
n_ant = elf.prim
n_act = n_ant.prox
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:
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:
self.len -= 1
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.
if i is None:
i = self.len - 1
if i == 0:
dato = self.prim.dato
self.prim = self.prim.prox
else:
n_ant = self.prim
n_act = n_ant.prox
n_ant = n_act
n_act = n_ant.prox
dato = n_act.dato
n_ant.prox = n_act.prox
self.len -= 1
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:
elif self.prim.dato == x:
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.
n_ant = self.prim
n_act = n_ant.prox
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.
if n_act == None:
else:
n_ant.prox = n_act.prox
if self.len == 0:
elif self.prim.dato == x:
else:
n_ant = self.prim
n_act = n_ant.prox
n_ant = n_act
n_act = n_ant.prox
if n_act == None:
else:
n_ant.prox = n_act.prox
self.len -= 1
# error
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)
if i == 0:
nuevo.prox = self.prim
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.
else:
n_ant = self.prim
n_ant = n_ant.prox
nuevo.prox = n_ant.prox
n_ant.prox = nuevo
# self.len += 1
En el Código se incluye el código resultante del método insert.
0 # error
nuevo = _Nodo(x)
if i == 0:
nuevo.prox = self.prim
self.prim = nuevo
else:
n_ant = self.prim
n_ant = n_ant.prox
nuevo.prox = n_ant.prox
n_ant.prox = nuevo
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.
iterador = iter(secuencia)
while True:
try:
elemento = iterador.next()
except StopIteration:
break
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):
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:
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
class _IteradorListaEnlazada(object):
self.actual = prim
def next(self):
if self.actual == None:
# 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):
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)
...
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:
def __init__(self):
self.items=[]
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):
try:
return self.items.pop()
except IndexError:
def es_vacia(self):
""" Devuelve True si la lista está vacía, False si no. """
return self.items == []
>>> 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
>>> q=Pila()
>>> q.desapilar()
>>> calculadora_polaca.main()
DEBUG: 5
5.0
>>> calculadora_polaca.main()
DEBUG: 4
DEBUG: 5
print calculadora_polaca(elementos)
>>> calculadora_polaca.main()
DEBUG: 4
DEBUG: %
print calculadora_polaca(elementos)
#!/usr/bin/env python
#encoding: latin1
p = Pila()
try:
numero = float(elemento)
p.apilar(numero)
except ValueError:
try:
a1 = p.desapilar()
a2 = p.desapilar()
except ValueError:
resultado = a2 + a1
resultado = a2 - a1
resultado = a2 * a1
resultado = a2 / a1
resultado = a2 % a1
p.apilar(resultado)
res = p.desapilar()
if p.esPilaVacia():
return res
else:
def main():
elementos = expresion.split()
print calculadora_polaca(elementos)
>>> calculadora_polaca.main()
DEBUG: 4
DEBUG: apila 4.0
DEBUG: 5
DEBUG: &
print calculadora_polaca(elementos)
El caso de 4 % 5:
>>> calculadora_polaca.main()
DEBUG: 4
DEBUG: 5
DEBUG: %
4.0
El caso de (4 + 5) * 6:
>>> calculadora_polaca.main()
DEBUG: 4
DEBUG: +
DEBUG: 6
DEBUG: *
54.0
El caso de 4 * (5 + 6):
>>> calculadora_polaca.main()
DEBUG: 4
DEBUG: 5
DEBUG: 6
DEBUG: +
DEBUG: *
DEBUG: desapila 11.0
44.0
El caso de (4 + 5) * (3 + 8):
>>> calculadora_polaca.main()
DEBUG: 4
DEBUG: 5
DEBUG: +
DEBUG: 3
DEBUG: 8
DEBUG: +
DEBUG: *
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.
class Cola:
"""
def __init__(self):
self.items=[]
El método encolar se implementará agregando el nuevo elemento al final de la lista:
self.items.append(x)
def desencolar(self):
try:
return self.items.pop(0)
except:
def es_vacia(self):
return self.items == []
>>> 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()
class Cola:
def __init__(self):
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):
self.primero = None
self.ultimo = None
nuevo = Nodo(x)
if self.ultimo:
self.ultimo.prox = nuevo
self.ultimo = nuevo
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):
if self.primero:
valor = self.primero.dato
self.primero = self.primero.prox
if not self.primero:
self.ultimo = None
return valor
else:
def es_vacia(self):
return self.items == []
Una vez implementada toda la interfaz de la cola, podemos probar el TAD resultante:
>>> 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()
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
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)
print "in-orden:"
ejecutarInOrden(arbol, imprimir)
print "\npre-orden"
ejecutarPreOrden(arbol, imprimir)
print "\npost-orden"
ejecutarPostOrden(arbol, imprimir)
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]
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)
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)
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.
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 buscarNodoMenorValor(nodo):
return nodo if nodo.izquierda
== None else buscarNodoMenorValor(nodo.izquierda)
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
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
Figura 14: Finalización para completar el proceso de partición para encontrar el punto de
división para 54
I. Objetivo
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.
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.
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.
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.
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.
Analysis – Contiene un submenú en el que se puede elegir entre una amplia gama de funciones
matemáticas de análisis.
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.
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 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.
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.
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.
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:
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
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
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.
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
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
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.
I. Objetivo
III. Procedimiento
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.
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.
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.
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.
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.
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).
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).
III. Materiales y
Equipo
Guía de Laboratorio Nº 3
Computadora con software LabVIEW 7 o superior instalado
IV. Procedimiento
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).
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).
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
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.
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.
2
IV. Procedimiento
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
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.
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‖.
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).
V. Análisis de Resultados
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.
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.
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.
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.
4. BIBLIOGRAFÍA
Barcons Gloria T (1991): Cardivillo Carlos J y Ramírez Jesús Alberto, Computación II,
Universidad Nacional Abierta, Caracas.
10