Estructura repetitiva i programació estructurada

La programació estructurada és un paradigma per a l’escriptura de codi. Segons la programació estructurada, qualsevol programa es pot escriure utilitzant les estructures de control bàsiques: seqüència, selecció i iteració. L’ús de sentències com són el goto, break, continue o exit és prescindible, i la majoria d’elles s’utilitzen per sortir, o per alterar el flux, en una estructura repetitiva.

Com escollir el bucle adequat segons les premisses de la programació estructurada

Escollir el bucle adient entre el for in i el while i trobar la condició de testatge adequada (en el cas del while) evita l’ús d’aquestes sentències “prohibides”.

En aquesta lliçó es proporcionaran tècniques i es donaran consells per a saber quin bucle utilitzar quan us trobeu amb una estructura repetitiva que recorri dades de tipus enter, per aconseguir programes que segueixin el paradigma de la programació estructurada.

Començarem amb una petita explicació sobre la programació estructurada i un recordatori de les estructures iteratives per acabar amb consells i exemples que us han d’ajudar a escollir l’adequada per a cada programa.

Programació estructurada

La programació estructurada és una teoria orientada a millorar la qualitat, la claredat i el temps de desenvolupament del software. Utilitza subrutines, estructures de bloc i estructures de control.

El principal objectiu de la programació estructurada era eliminar la sentència goto. L’ús indiscriminat d’aquesta funció convertia els programes en autèntics laberints impossibles de desxifrar.

La sentència goto (o “anar a” en anglès) serveix per transferir el flux del programa a una etiqueta o número de línia concret. L’ús del goto genera programes molt poc llegibles i molt difícils de mantenir.

Breu història de la programació estructurada

Històricament, hi ha dos moments destacats en la implantació de la programació estructurada:

  • El 1966 Böhm i Jacopini proposen el teorema del programa estructurat, amb el que demostren que tot programa pot ser escrit utilitzant només 3 estructures de control: sentència, selecció i iteració.
  • El 1968 Edsger Dijkstra recomana la verificació formal de programes i l’eliminació del goto.
  • Al mateix 1968 es va modificar el llenguatge de programació ALGOL 60, basat en blocs de codi, per convertir-lo en el primer llenguatge de programació estructurada.

Elements de la programació estructurada

Estructures de control bàsiques:

  • Estructura de seqüència. És l’execució d’una instrucció després d’una altra.
  • Estructura de selecció. Permet escollir un camí o un altre. Depenent d’una condició, unes instruccions no es faran. És la instrucció if.
  • Estructura de repetició. Permet repetir unes instruccions diverses vegades. Són el bucle while i el bucle for in.

Les subrutines són programes o “fragments de programa”. El programa que s’està executant pot passar el control a altres elements perquè facin la seva feina. L’exemple més clar és quan crideu a una funció del sistema: si en un programa utilitzeu la funció math.sqrt(), el programa que s’està executant passa el flux a la funció perquè faci els seus càlculs. En el moment que retorna el resultant, el programa recupera el control del flux. Altes subrutines poden ser mètodes, procediments o subprogrames.

Un bloc seria el conjunt d’instruccions que componen un if, un for in o un while (entre altres coses). O unes quantes instruccions seqüencials. Un bloc permet tractar un grup com una única instrucció. En la programació estructurada s’ha de sortir d’un bloc de manera ordenada, sense interrompre la seva execució completa.

En Python els blocs es defineixen amb la indentació. En ALGOL, per exemple, s’utilitzava if..fi i en C les claus {…}.

Sentències i funcions que interrompen la programació estructurada

  • goto: és la “bèstia negra” de la programació estructurada i s’ha d’evitar a tot preu. Molts dels llenguatges moderns ja no compten amb aquesta seqüència.
  • break: permet sortir d’un bloc “a la valenta”. És molt utilitzat per sortir d’un bucle for in, ja que a molts programadors els és més senzill crear un for in i trencar-lo amb el break que gestionar la condició adient del bucle while. Segur que en la vostra vida de programadors l’utilitzareu, però en aquest curs és una sentència prohibida.
  • continue: sentència que interromp un bucle fins a la següent iteració. És a dir, es salta el que queda del cos del bucle. Es pot evitar dissenyant de manera estructurada el cos del bucle.
  • exit: s’utilitza per finalitzar un programa. Permet múltiples sortides i, per tant, no és programació estructurada.
  • Ús d’excepcions. Molts llenguatges moderns, com el Python i el Java, gestionen els errors amb l’ajuda de les Exception. Es considera que la programació estructurada és indispensable en el programa principal, però hi ha moments que el codi resultant pot ser molt extens i complicat, per exemple en els casos que s’han de descartar dades com en la gestió dels errors. En aquestes ocasions s’entén que pot aportar confusió i es prima la llegibilitat per a escalar el problema a un altre nivell. Les Exception són uns objectes que faciliten el control dels errors.

En aquest curs de programació s’exigirà que el programa principal sigui estructurat. En canvi, i seguint la política utilitzada en l’ús de les Exception, es permetrà que en el control d’errors del programa hi hagi clàusules que no siguin de programació estructurada (bàsicament el exit). Així s’evita “embrutar” el programa i es facilita la seva reutilització. S’entendrà el control d’errors com un mòdul que es pot afegir, treure o modificar, al marge del programa principal.

La programació estructurada millora la qualitat del codi. Permet molta més flexibilitat a causa de la seva modularitat, el que facilita el manteniment del programa, la llegibilitat i la possibilitat de dividir el codi entre un equip de programadors.

Com a administradors, gran part de la vostra feina consistirà en el manteniment de programes que han fet altres persones. O bé, si elaboreu nou codi, serà necessari que sigui fàcil de mantenir per altres professionals. És per això que us cal tenir molt presents els principis de la programació estructurada.

Bucle while

Recordeu l’estructura del bucle while:

#inicialització variables
while condició:
  # Cos del bucle

Es repetirà el cos del bucle mentre la condició sigui certa. Perquè el bucle funcioni cal que:

  • Alguna o algunes de les variables de les quals depèn la condició s’han de modificar dins del cos. En cas contrari, el resultat de la condició no canviaria, però també podria ser que no s’entrés al bucle o no en sortís mai.
  • Alguna o algunes de les variables s’han d’inicialitzar fora del bucle. Si s’inicialitzen totes a dins, cada execució es farà amb els mateixos valors i no se’n sortirà mai.

Quan detecteu una estructura repetitiva en un programa, cal escollir el bucle while quan s’ha de comprovar una condició per sortir. Quan poden passar diverses coses que portin a finalitzar-lo i quan no sabeu d’entrada les repeticions que s’han de donar.

Per a fer programes amb repeticions sobre dades numèriques es pot utilitzar sempre el while. El desavantatge és que la gestió de la condició i les variables de les quals depèn és més complicada que amb un for in.

Heu de pensar a escollir el bucle while quan d’entrada no està clara la quantitat de repeticions que s’hauran de fer.

Programa amb bucle while

El programa per comprovar si un nombre és o no primer és un clar exemple de bucle while. S’ha de repetir el cos del bucle mentre no hi hagi divisors i mentre no s’arribi al mateix nombre. En alguns casos (el 200, per exemple), s’ha d’efectuar només una repetició, i, en canvi, en el nombre 7 s’ha d’arribar fins al final. Aquí teniu la versió sense bandera.

#llegir num
n = int(input())

div = 2
# mentre no trobem divisor i encara n'hi pugui haver
while div < n and not n % div == 0:
	div = div + 1
	
es_primer = (div == n)
print(es_primer)

Bucle for in

L’estructura del bucle for in, quan l’utilitzem per recórrer una llista d’enters, és més senzilla:

for element in llista:
  # Cos del bucle

Quan sabeu la quantitat de vegades que s’ha de repetir el bucle (tipus comptadors o semblants) o podeu descriure per quins valors s’ha de fer, l’estructura adequada és el for in. Per exemple, tots els programes en què necessiteu un comptador fins a arribar a un límit seran més senzills amb el bucle for in que amb el bucle while.

S’ha de tenir en compte una cosa molt important: només es pot sortir d’un bucle for in quan s’arriba a l’últim element de la llista (o de la dada que s’estigui iterant).

Programa amb bucle for

Qualsevol exemple d’un programa que hagi de repetir una acció un nombre determinat de vegades és algorísmicament apropiat per al bucle for in. En aquest exemple es mostra el programa del compte enrere.

n = int(input())

for i in range(n, -1, -1):
	print(i)
	
print('BUUUUM')

Utilitzar el for in en comptes del while fa que el programa sigui més senzill i més llegible.

Programa que no es pot fer amb el bucle for

El programa per indicar si un nombre és o no és primer no es pot fer seguint les premisses de la programació estructurada i utilitzant el bucle for in. Fixeu-vos en el codi següent:

#llegir num
n = int(input())

es_primer = True

# mentre no trobem divisor i encara n'hi pugui haver
for div in range(2, n):
	if n % div == 0:
		es_primer = False
	
print(es_primer)

El programa funciona, tot i que sempre busca divisors fins al final. És per això que la sentència /break és molt temptadora: el següent programa surt del for in en el moment que es troba el primer divisor. És a dir, en el cas del 200 només fa una volta.

#llegir num
n = int(input())

es_primer = True
# mentre no trobem divisor i encara n'hi pugui haver
for div in range(2, n):
	if n % div == 0:
		es_primer = False
	  break
	
print(es_primer)

Però la solució en programació estructurada passa per refer el programa i muntar-lo amb un bucle while. S’han de fer repeticions mentre no s’arriba a n i mentre no es troba un divisor.

L’estructura for in és molt més potent del que s’ha mostrat fins ara. A més de recórrer llistes d’enters, es pot utilitzar per recórrer qualsevol mena de seqüència (per exemple una cadena), o altres tipus de dades que no són seqüencials (com els diccionaris).

Clàusula else per a estructures repetitives

Python té una particularitat que no tenen altres llenguatges de programació, com el Java, Javascript, el C o el C++. Es tracta de la possibilitat d’afegir la clàusula else en un bucle, tant en el while com en el for in. L’ else s’executa una vegada se surt del bucle.

Bucle for amb else

Fixeu-vos en el codi del programa 1:

for i in range(0, 3):
	print(i)
else:
	print('sortint del bucle')
print('final')

El resultat de la seva execució és el següent:

videoioc@debian-xtec:~/Documents/programacio$ python3 for_else.py 
0
1
2
sortint del bucle
final

Quina és la diferència amb el programa 2?

for i in range(0, 3):
	print(i)

print('sortint del bucle')
print('final')

Cap ni una. A més, el programa 2 és més entenedor i més llegible i utilitza estructures comunes a tots els llenguatges de programació. Per l’únic que serveix afegir l’ else en un bucle és per saber si s’ha sortit a través d’un break o no.

Aquest és el programa 3:

n = int(input())
for i in range(0, n):
	print(i)
	if i == 2:
		break
else:
	print('sortint del bucle sense break')
print('final')

Aquestes sónles execucions per n = 2:

videoioc@debian-xtec:~/Documents/programacio$ python3 for_else.py 
2
0
1
sortint del bucle sense break
final

I aquestes per n = 3:

videoioc@debian-xtec:~/Documents/programacio$ python3 for_else.py 
3
0
1
2
final

En Python es pot afegir un else a una estructura repetitiva (sigui un for in o un while). Però el seu ús no aporta res: és millor no utilitzar-lo i afegir el codi que s’ha de fer en sortir del bucle immediatament després de sortir-ne, sense la clàusula else.

Com escollir entre un while i un for

Quan feu el refinament i detecteu la necessitat d’una estructura repetitiva, cal decidir si és millor utilitzar un bucle while o un bucle for in.

En general:

  • Cal escollir el bucle for in quan es poden determinar la quantitat de repeticions necessàries a priori.
  • També serà un for in si sabeu exactament quantes vegades i els valors concrets que ha de prendre la variable en cada iteració.
  • El bucle serà un while si la sortida depèn d’una o diverses condicions que s’han de comprovar.

Si en fer el refinament decidiu el tipus de bucle, estalviareu problemes en el moment d’escriure el codi.

D’un bucle for in no es pot sortir fins que no s’han acabat tots els elements de la llista o del que s’estigui iterant. Si us adoneu que necessiteu sortir d’un for in és que no heu escollit l’estructura adequada.

Evolució d'un programa amb nous coneixements

Aquesta és l’última lliçó en què es treballa només amb dades numèriques. També s’utilitzen dades booleanes perquè algunes de les estructures de control requereixen condicions, i una condició no és més que una dada booleana.

Segons la programació estructurada, ja teniu les eines suficients per a fer qualsevol tipus de programa que treballi amb nombres enters (només falten les subrutines). En el que queda de bloc aprendreu a treballar amb diferents tipus de dades com cadenes i llistes. Ara es farà un repàs de què heu anat aprenent i de les millores que es van afegint a un programa a mesura que assoliu nous conceptes.

El programa que s’utilitza com a exemple és el de llegir 4 nombres i verificar si corresponen als camps d’una IP correcta o no. Per estudiar diferents casos es tindran en compte dos jocs de proves diferents. En les versions compatibles amb el joc de proves de la taula, es llegiran els 4 camps sempre, independentment que siguin o no correctes.

Les especificacions d’entrada són: 4 nombres enters.

Entrada Sortida
120,5, 6, 0 True
300,2, 7, 23 False
3,-3, 0, 1 False
-2,-3, -2, -4 False
255,255, 2, 1 True
0,256, 0, 0 False

En canvi, en les versions compatibles amb el joc de proves de la taula, només es llegiran camps mentre puguin correspondre a una IP vàlida.

Especificacions d’entrada : n nombres enters. n >= 1 and n ⇐ 4.

Entrada Sortida
120,5, 6, 0 True
300 False
3,-2 False
-2 False
255,255, 2, 1 True
0,256 False
127,0, 0, -1 False

Estructura seqüencial

Amb l’estructura seqüencial només es pot resoldre el programa corresponent al joc de proves de la taula. S’han d’utilitzar variables booleanes per a emmagatzemar la validesa de cada camp i escriure l’expressió booleana adequada.

Versió 0 Segurament la primera versió del programa de les IPs que vau intentar va ser amb aquest codi. En ell s’utilitzen operadors relacionals i operadors lògics per comprovar si cada camp és o no correcte.

# llegir els 4 camps
ip1 = int(input())
ip2 = int(input())
ip3 = int(input())
ip4 = int(input())


#validem cada camp per separat
if ip1 >=0 and ip1 <= 255:
	print(True)
	
if ip2 >=0 and ip2 <= 255:
	print(True)
	
if ip3 >=0 and ip3 <= 255:
	print(True)

if ip4 >=0 and ip4 <= 255:
	print(True)	

Aquest programa no és vàlid, ja que la seva sortida no coincideix amb el joc de proves de la taula.

Versió 1

En treballar amb variables booleanes vau aprendre a escriure condicions més descriptives. El següent programa és un programa realitzat amb una estructura seqüencial que indica si una IP és vàlida o no, i que s’adapta al joc de proves de la taula:

# llegir els 4 camps
ip1 = int(input())
ip2 = int(input())
ip3 = int(input())
ip4 = int(input())

#si son correctes (expressió booleana)

es_ip_valida =(ip1 >= 0 and ip1 <= 255) and (ip2 >= 0 and ip2 <= 255) and (ip3 >= 0 
	and ip3 <= 255) and (ip4 >= 0 and ip4 <= 255)
print(es_ip_valida)

És important que us acostumeu a desar la informació en una dada booleana, i que l’anomeneu es_alguna_cosa. Això us facilitarà recuperar la informació si la necessiteu més endavant, i ajuda molt a la llegibilitat dels programes.

Estructura alternativa

En començar sembla més natural utilitzar una estructura alternativa per a resoldre un programa d’aquest tipus. Però si compareu aquest codi amb el de la versió 1 comprovareu que l’altre és més senzill i elegant.

Versió 2

Aquest codi correspon també al joc de proves de la taula (es fan les 4 lectures sempre). Utilitza quatre vegades una estructura condicional simple per avaluar cada camp. Si una d’elles no és correcte, el valor final tampoc ho serà. Fixeu-vos que en aquest cas, la condició que surt de manera natural és “si un camp no és correcte”.

ip_valida = True

# llegim cada camp
ip1 = int(input())

#validem cada camp per separat
if not (ip1 >=0 and ip1 <= 255):
	ip_valida = False

ip2 = int(input())
if not (ip2 >=0 and ip2 <= 255):
	ip_valida = False
	
ip3 = int(input())	
if not (ip3 >=0 and ip3 <= 255):
	ip_valida = False

ip4 = int(input())
if not (ip4 >=0 and ip4 <= 255):
	ip_valida = False

print(ip_valida)

Per a negar una condició cal escriure la condició amb un not davant, i posar parèntesis per respectar les prioritats. Mai heu d’intentar escriure la condició contrària, que pot ser molt complicada de girar. Tampoc escriure codi del tipus:

if (ip1 >=0 and ip1 <= 255):
  a = a  #si teniu temptacions d'escriure una línia que no fa res, és 
         #que necessiteu posar el not a la condició  i suprimir el else
else:
  ip_valida = False

Versió 3

Programa corresponent al joc de proves de la taula. En aquest cas, s’utilitza una estructura condicional niada, és a dir, en alguna de les branques del if hi ha altres estructures condicionals.

# llegir els 4 camps
ip1 = int(input())
ip2 = int(input())
ip3 = int(input())
ip4 = int(input())

#validem cada camp per separat
if ip1 >= 0 and ip1 <= 255:	
	if ip2 >= 0 and ip2 <= 255:
		if ip3 >=0  and ip3 <= 255:
			if ip4 >= 0 and ip4 <= 255:
				print('True')
			else:
				print('False')
		else:
			print('False')
	else:
		print('False')

Segurament és la primera versió que fa un programador novell. Però és un mal programa (tot i que funciona). Si necessiteu validar una IP per fer una altra operació, no us serveix. Tampoc és bona política tenir diversos print() quan n’hi pot haver només un.

Versió 4

Només amb crear una variable booleana es_ip_valida i mostrar-la al final, s’aconsegueix una versió millor (també amb el joc de proves de la taula).

# llegir els 4 camps
ip1 = int(input())
ip2 = int(input())
ip3 = int(input())
ip4 = int(input())

es_ip_valida = False
#validem cada camp per separat
if ip1 >= 0 and ip1 <= 255:	
	if ip2 >= 0 and ip2 <= 255:
		if ip3 >=0  and ip3 <= 255:
			if ip4 >= 0 and ip4 <= 255:
				es_ip_valida = True

print(es_ip_valida)

Versió 5

El plantejament anterior permet fer una primera versió per a un programa que acompleixi el joc de proves de la taula (continuar efectuant lectures només mentre els camps són correctes). Si en comptes de fer les 4 lectures de cop les feu només si la IP compleix la bona condició, sortireu del programa en el cas que un camp no sigui correcte.

# llegir els 4 camps
ip1 = int(input())

es_ip_valida = False
#validem cada camp per separat
if ip1 >= 0 and ip1 <= 255:	
	ip2 = int(input())
	if ip2 >= 0 and ip2 <= 255:
		ip3 = int(input())
		if ip3 >=0  and ip3 <= 255:
			ip4 = int(input())
			if ip4 >= 0 and ip4 <= 255:
				es_ip_valida = True

print(es_ip_valida)

Estructura iterativa

Amb la introducció dels bucles ja teniu eines per millorar aquest tipus de programes. En concret, el de les IPs es pot fer sense l’estructura repetitiva perquè només hi ha quatre repeticions. Però si n’hi hagués més, seria inassumible programar només amb condicionals.

Per a recodificar el programa afegint l’estructura de bucle, cal tornar a la versió 2 del programa:

Versió 2

ip_valida = True

# llegim cada camp
ip1 = int(input())

#validem cada camp per separat
if not (ip1 >=0 and ip1 <= 255):
	ip_valida = False

ip2 = int(input())
if not (ip2 >=0 and ip2 <= 255):
	ip_valida = False
	
ip3 = int(input())	
if not (ip3 >=0 and ip3 <= 255):
	ip_valida = False

ip4 = int(input())
if not (ip4 >=0 and ip4 <= 255):
	ip_valida = False

print(ip_valida)

Versió 6

D’entrada es farà un programa compatible amb el joc de proves de la taula.

És fàcil veure que hi ha instruccions repetides per cada camp. I que si en comtes de fer les 4 lectures de cop, es fa cadascuna just abans de validar el camp, hi ha un bloc d’instruccions que es repeteix 4 vegades.

Sabeu exactament la quantitat de repeticions que cal fer. Això indica que l’estructura adequada és el bucle for in. En aquest cas, també es necessita la variable booleana es_ip_valida per emmagatzemar la validesa final de la IP.

es_ip_valida = True

for i in range(0, 4):
	camp = int(input())
	#comprovar si el camp és válid
	if not (camp >=0 and camp <= 255):
		es_ip_valida = False
	
print(es_ip_valida)

Versió 7

Ara es vol fer un programa que respongui al joc de proves de la taula, que no acaba la lectura si no és necessari. En aquesta versió no sabeu amb antelació les vegades que s’haurà d’executar el bucle; depèn dels valors de l’entrada. Això indica que el programa necessita un bucle while. Cal pensar la condició amb deteniment perquè es tracta d’una condició composta.

El flux s’ha de mantenir dins del bucle mentre no hagi arribat a la quarta repetició i mentre el nombre llegit sigui correcte. En ser un while, necessitareu crear una variable per comptar les repeticions.

El codi corresponent serà:

p_valida = True
i = 0

while i < 4 and ip_valida:
	camp = int(input())
	
  #comprovar si el camp és válid
  if not (camp >=0 and camp <= 255):
		ip_valida = False
		
	i = i + 1
  
print(ip_valida)

Aquí s’ha d’utilitzar una variable booleana (com una bandera) perquè el nombre s’ha de llegir dins del bucle i no es pot posar directament a la condició del while.

Exemple: Com modificar el programa si no s'ha escollit el bucle correcte

Suposeu que esteu resolent el programa anterior (amb el joc de proves corresponent a la taula), i us adoneu que heu escollit malament l’estructura repetitiva. Com podeu modificar el programa? El codi del programa amb el bucle for in seria:

es_ip_valida = True
for i in range(0, 4):
	camp = int(input())
	#comprovar si el camp és válid
	if not (camp >=0 and camp <= 255):
		es_ip_valida = False
	  break # VULL FORÇAR LA SORTIDA PERÒ ÉS UN BUCLE FOR!!!!
print(es_ip_valida)

En aquesta situació heu de crear una bandera, fer un bucle while i afegir la bandera a la condició. En muntar el bucle while, cal començar pel cos, que ja teniu definit.

# bucle while
  # cos del bucle
  camp = int(input())
	#comprovar si el camp és válid
	if not (camp >=0 and camp <= 255):
		bandera = False

Ara caldrà escriure la condició, definir la variable del comptador, incrementar-la a dins del bucle i inicialitzar les variables. Si heu estat una mica descriptius amb el nom de la variable bandera i l’heu anomenada es_ip_valida, directament obteniu el mateix codi que en el de la versió 6.

Ara tocaria comprovar si es pot modificar la condició del while de manera que no sigui necessària la bandera. En aquest cas no es pot fer, perquè cal llegir el camp a dins del cos del bucle.

Si no aconseguiu treure la bandera, no passa res més que el programa serà una mica menys llegible. En canvi, l’ús de l’estructura break està prohibit en aquest curs.

Entendre les diferents versions d’aquest programa us ha de donar una visió general del que heu après en aquestes primeres lliçons i ajudar-vos a triar les estructures més adequades per a fer cada programa.

Anar a la pàgina anterior:
Contingut
Anar a la pàgina següent:
Índex general