Aceasta este o versiune mai veche a cărții cunoscute acum sub numele de Think Python. S-ar putea să preferați să citiți o versiune mai recentă.
How to Think Like a Computer Scientist |
Capitolul 15
15.1 Compoziția
Până acum, ați văzut mai multe exemple de compoziție. unul dintre primele exemple a fost folosirea invocării unei metode ca parte a unei expresii. Un alt exemplu este structura imbricata a instrucțiunilor;puteți pune o instrucțiune if în interiorul unei bucle while, în interiorul unei alte instrucțiuni if, și așa mai departe.
Având văzut acest model, și după ce ați învățat despre liste și obiecte,nu ar trebui să fiți surprins să aflați că puteți crea liste de obiecte. De asemenea, puteți crea obiecte care conțin liste (ca atribute); puteți crea liste care conțin liste; puteți crea obiecte care conțin obiecte; și așa mai departe.
În acest capitol și în următorul, vom examina câteva exemple ale acestorcombinații, folosind ca exemplu obiectele Card.
15.2 Obiecte Card
Dacă nu sunteți familiarizați cu cărțile de joc obișnuite, acum ar fi un moment bun să vă procurați un pachet, altfel acest capitol s-ar putea să nu aibă prea mult sens. 52 de cărți se află într-un pachet, fiecare dintre ele aparținând uneia dintre cele patru costume și unuia dintre cele treisprezece ranguri. Suitele sunt Pică, Inimă, Caroiaz și Club (în ordine descrescătoare în bridge). Rangurile sunt As, 2, 3, 4, 5, 6, 7, 8, 9, 10, Valet, Damă și Rege. În funcție de jocul pe care îl jucați, rangul Asului poate fi mai mare decât Regele sau mai mic decât 2.
Dacă dorim să definim un nou obiect care să reprezinte o carte de joc, este evident care ar trebui să fie atributele: rang și costum. Nu este la fel de evident ce tip ar trebui să fie atributele. O posibilitate este de a folosi șiruri de caractere care să conțină cuvinte precum „Pică” pentru costume și „Damă” pentru ranguri. O problemă cu această punere în aplicare este că nu ar fi ușor de comparat cărțile pentru a vedea care are un rang sau o suită mai mare.
O alternativă este de a folosi numere întregi pentru a codifica rangurile și suitele.Prin „codificare” nu ne referim la ceea ce cred unii oameni, adică la criptare sau la transpunerea într-un cod secret. Ceea ce un informatician înțelege prin „a codifica” este „a defini o corespondență între o secvență de numere și elementele pe care vreau să le reprezint”. De exemplu:
Spade | -> | 3 |
Cupe | -> | 2 |
Diamante | . | 1 |
Cluburi | -> | 0 |
O caracteristică evidentă a acestei cartografieri este că suitele se mapează la numere întregi în ordine, astfel încât putem compara suitele prin compararea numerelor întregi. Cartografierea rangurilor este destul de evidentă; fiecare dintre rangurile numerice se corelează cu numărul întreg corespunzător, iar pentru cărțile cu fața:
Jack | -> | 11 |
Regină | -> | 12 |
Rege | – – | 13 |
Motivul pentru care folosim notații matematice pentru aceste corespondențe este că ele nu fac parte din programul Python. Ele fac parte din proiectarea programului, dar nu apar niciodată în mod explicit în cod. Definiția clasei pentru tipul Card arată astfel:
class Card:
def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank
Ca de obicei, furnizăm o metodă de inițializare care ia un parametru opțional pentru fiecare atribut. Valoarea implicită a costumului este0, care reprezintă treflă.
Pentru a crea o carte, invocăm constructorul Card cu costumul și rangul cărții pe care o dorim.
threeOfClubs = Card(3, 1)
În secțiunea următoare ne vom da seama ce carte tocmai am creat.
15.3 Atributele clasei și metoda __str__
Pentru a imprima obiectele Card într-un mod ușor de citit de către oameni, dorim să mapăm codurile întregi pe cuvinte. Un mod natural de a face acest lucru este cu liste de șiruri de caractere. Atribuim aceste liste la atributul clasei în partea de sus a definiției clasei:
clasa Card:
suitList =
rankList =
#init method omitted
def __str__(self):
return (self.rankList + ” of ” +
self.suitList)
Un atribut de clasă este definit în afara oricărei metode și poate fi accesat din oricare dintre metodele clasei.
În interiorul lui __str__, putem utiliza suitList și rankList pentru a cartografia valorile numerice ale lui suit și rank în șiruri de caractere.De exemplu, expresia self.suitList înseamnă „utilizează atributul suit din obiectul self ca index în atributul clasei numit suitList și selectează șirul de caractere corespunzător”.”
Motivul pentru „narf” în primul element din rankList este acela de a acționa ca un păstrător de loc pentru cel de-al zecelea element al listei, care nu ar trebui să fie folosit niciodată. Singurele ranguri valide sunt de la 1 la 13. Acest element irosit nu este în întregime necesar. Am fi putut începe de la 0, ca de obicei, dar este mai puțin confuz să codificăm 2 ca 2, 3 ca 3 și așa mai departe.
Cu metodele pe care le avem până acum, putem crea și tipări cărți:
>>> card1 = Card(1, 11)
>>>> print card1
Jack of Diamonds
Atributele clasei, cum ar fi suitList, sunt împărtășite de toate Cardobjects. Avantajul acestui lucru este că putem folosi orice Cardobject pentru a accesa atributele clasei:
>>> card2 = Card(1, 3)
>>>> print card2
3 of Diamonds
>>>> print card2.suitList
Diamonds
Dezavantajul este că, dacă modificăm un atribut al clasei, acesta afectează fiecare instanță a clasei. De exemplu, dacă decidem că „Valet de caro” ar trebui să se numească de fapt „Valet de balene învârtite”, am putea face așa:
>>> card1.suitList = „Swirly Whales”
>>>> print card1
Jack of Swirly Whales
Problema este că toate Diamantele tocmai au devenitSwirly Whales:
>>> print card2
3 of Swirly Whales
De obicei nu este o idee bună să modificați atributele clasei.
15.4 Compararea cărților
Pentru tipurile primitive, există operatori condiționali(<, >, ==, etc.)care comparăvalorile și determină când una este mai mare decât, mai mică decât sau egală cu alta. Pentru tipurile definite de utilizator, putem suprascrie comportamentul operatorilor încorporați prin furnizarea unei metode numite__cmp__. Prin convenție, __cmp__are doi parametri, self și altcineva, și returnează1 dacă primul obiect este mai mare, -1 dacă al doilea obiect este mai mare și 0 dacă sunt egale între ele.
Câteva tipuri sunt complet ordonate, ceea ce înseamnă că puteți compara oricare două elemente și spune care este mai mare. De exemplu, numerele întregiși numerele în virgulă mobilă sunt complet ordonate. Unele seturi sunt neordonate, ceea ce înseamnă că nu există o modalitate semnificativă de a spune că un element este mai mare decât altul. De exemplu, fructele sunt neordonate, motiv pentru care nu se pot compara merele și portocalele.
Setul de cărți de joc este parțial ordonat, ceea ce înseamnă că uneori se pot compara cărți și alteori nu. De exemplu, știți că 3 de treflă este mai mare decât 2 de treflă, iar 3 de caro este mai mare decât 3 de treflă. Dar care este mai bună, 3 de treflă sau 2 de caro? Unul are un rang mai mare, dar celălalt are o culoare mai mare.
Pentru a face cărțile comparabile, trebuie să decideți ce este mai important, rangul sau culoarea. Ca să fiu sincer, alegerea estearbitrară. De dragul alegerii, vom spune că suita este mai importantă, pentru că un pachet nou de cărți vine sortatcu toate treflele laolaltă, urmate de toate carotele și așa mai departe.
După această decizie, putem scrie __cmp__:
def __cmp__(self, other):
# verifică costumele
if self.suit > other.suit: return 1
if self.suit < other.suit: return -1
# costumele sunt la fel… verifică rangurile
if self.rank > other.rank: return 1
if self.rank < other.rank: return -1
# rangurile sunt identice… este egalitate
return 0
În această ordine, Așii apar mai jos decât Deucii (2s).
Ca exercițiu, modificați __cmp__ astfel încât Așii să fie clasificați mai sus decât Regii.
15.5 Punți
Acum că avem obiecte care să reprezinte Cărțile, următorul pas logic este să definim o clasă care să reprezinte o punte. Bineînțeles, un pachet este alcătuit din cărți, așa că fiecare obiect Deck va conține o listă de cărți ca atribut.
În cele ce urmează este o definiție de clasă pentru clasa Deck. Metoda de inițializare creează atributul cărți și generează un set standard de cincizeci și două de cărți:
class Deck:
def __init__(self):
self.cards =
for suit in range(4):
for rank in range(1, 14):
self.cards.append(Card(suit, rank))
Cel mai simplu mod de a popula pachetul este cu o buclă imbricata. Bucla exterioară enumeră suitele de la 0 la 3. Bucla interioară enumeră rangurile de la 1 la 13. Deoarece bucla exterioară itera de patru ori, iar bucla interioară itera de treisprezece ori, numărul total de ori de câte ori se execută corpul este de cincizeci și două (treisprezece ori patru). Fiecare iterație creează o nouă instanță de Card cu suita și rangul curent și adaugă acea carte la lista de cărți.
Metoda append funcționează pe liste, dar nu, desigur, pe tuple.
15.6 Tipărirea pachetului
Ca de obicei, atunci când definim un nou tip de obiect dorim o metodă care să tipărească conținutul unui obiect.Pentru a imprima un pachet, parcurgem lista și imprimăm fiecare card:
class Deck:
…
def printDeck(self):
for card in self.cards:
print card
Aici, și de acum înainte, elipsele (….) indică faptul că am omis celelalte metode din clasă.
Ca o alternativă la printDeck, am putea scrie o metodă __str__ pentru clasa Deck. Avantajul lui __str__ este că este mai flexibilă. În loc să tipărească pur și simplu conținutul obiectului, ea generează o reprezentare sub formă de șir de caractere pe care alte părți ale programului o pot manipula înainte de imprimare sau o pot stoca pentru utilizare ulterioară.
Iată o versiune a metodei __str__ care returnează o reprezentare sub formă de șir de caractere a unui Deck.Pentru a adăuga un pic de eleganță, aceasta aranjează cărțile într-o cascadăîn care fiecare carte este indentată cu un spațiu mai mult decât cartea anterioară:
class Deck:
…
def __str__(self):
s = „”
for i in range(len(self.cards)):
s = s + „”*i + str(self.cards) + „\n”
return s
Acest exemplu demonstrează mai multe caracteristici. În primul rând, în loc să parcurgem self.cards și să atribuim fiecare carte la o variabilă, folosim i ca o variabilă de buclă și un index în lista de cărți.
În al doilea rând, folosim operatorul de înmulțire a șirurilor de caractere pentru a indenta fiecare carte cu un spațiu în plus față de ultima. Expresia””*i produce un număr de spații egal cu valoarea curentă a lui i.
În al treilea rând, în loc să folosim comanda print pentru a imprima cărțile,folosim funcția str. Transmiterea unui obiect ca argument pentruostr este echivalentă cu invocarea metodei __str__ asupra obiectului.
În cele din urmă, folosim variabila s ca acumulator.inițial, s este șirul gol. De fiecare dată când trece prin buclă, este generat un nou șir de caractere și concatenat cu vechea valoare a lui sto obține noua valoare. Când se termină bucla, s conține reprezentarea completă a șirului Deck, care arată astfel:
>>>> deck = Deck()
>>>> print deck
Ace de treflă
2 de treflă
3 de treflă
4 de treflă
5 de treflă
6 de treflă Cluburi
7 de Cluburi
8 de Cluburi
9 de Cluburi
10 de Cluburi
Jacheta de Cluburi
Dama de Cluburi
Regină de Cluburi
Regină de Cluburi
Omul de Caroți
Și așa mai departe. Chiar dacă rezultatul apare pe 52 de linii, este un singur șir lung care conține linii noi.
15.7 Amestecarea pachetului
Dacă un pachet este perfect amestecat, atunci orice carte are aceeași probabilitate de a apărea oriunde în pachet, iar orice locație din pachet are aceeași probabilitate de a conține orice carte.
Pentru a amesteca pachetul, vom folosi funcția randrange din modulul random. Cu două argumente întregi,a și b, randrange alege un număr întreg aleatoriu în intervalul a <= x < b. Deoarece limita superioară este strict mai mică decât b, putem folosi lungimea unei liste ca al doilea argument și avem garanția că vom obține un indice legal.De exemplu, această expresie alege indicele unei cărți aleatoare dintr-un pachet:
random.randrange(0, len(self.cards))
O modalitate ușoară de a amesteca pachetul de cărți este de a traversa cărțile și de a schimba fiecare carte cu una aleasă la întâmplare. Este posibil ca această carte să fie schimbată cu ea însăși, dar este în regulă. De fapt, dacă am exclude această posibilitate, ordinea cărților ar fi mai puțin decât complet aleatorie:
class Deck:
…
def shuffle(self):
import random
nCards = len(self.cards)
for i in range(nCards):
j = random.randrange(i, nCards)
self.cards, self.cards = self.cards, self.cards
În loc să presupunem că există cincizeci și două de cărți în pachet, obținem lungimea reală a listei și o stocăm în nCards.
Pentru fiecare carte din pachet, alegem o carte aleatorie dintre cărțile care nu au fost încă amestecate. Apoi schimbăm cartea curentă (i) cu cartea aleasă (j). Pentru a schimba cărțile folosim o atribuire de tuplu, ca în secțiunea 9.2:
self.cards, self.cards = self.cards, self.cards
Ca un exercițiu, rescrieți această linie de cod fără a folosi o atribuire de secvență.
15.8 Îndepărtarea și împărțirea cărților
O altă metodă care ar fi utilă pentru clasa Deck este removeCard, care primește o carte ca argument, o îndepărtează și returnează True dacă cartea era în pachet și Falseîn caz contrar:
clasa Deck:
…
def removeCard(self, card):
if card in self.cards:
self.cards.remove(card)
return True
else:
return False
Operatorul in returnează true dacă primul operand se află în thesecond, care trebuie să fie o listă sau o tupla. Dacă primul operand este un obiect, Python folosește metoda __cmp__ a obiectului pentru a determinaegalitatea cu elementele din listă. Deoarece __cmp__ din clasaCard verifică egalitatea în profunzime, metoda removeCard verifică egalitatea în profunzime.
Pentru a împărți cărți, dorim să eliminăm și să returnăm cartea de sus.Metoda pop a listei oferă o modalitate convenabilă de a face acest lucru:
class Deck:
…
def popCard(self):
return self.cards.pop()
De fapt, pop elimină ultima carte din listă, astfel încât, fără efect, împărțim de la baza pachetului.
O altă operație pe care probabil că o vom dori este funcția booleanăisEmpty, care returnează true dacă pachetul nu conține cărți:
class Deck:
…
def isEmpty(self):
return (len(self.cards) == 0)
15.9 Glosar
encode Reprezentarea unui set de valori folosind un alt set de valori prin construirea unei corespondențe între ele. class attribute O variabilă care este definită în interiorul unei definiții de clasă, dar în afara oricărei metode. Atributele clasei sunt accesibile din orice metodă a clasei și sunt partajate de toate instanțele clasei. acumulator O variabilă utilizată într-o buclă pentru a acumula o serie de valori, cum ar fi prin concatenarea lor într-un șir de caractere sau prin adăugarea lor la o sumă curentă.
Aceasta este o versiune mai veche a cărții cunoscute acum sub numele de Think Python. S-ar putea să preferați să citiți o versiune mai recentă.
Cum să gândești ca un informatician |
.