Řetězce II

Seznámili jsme se již s nejzákladnějšími operacemi s řetězci.

  • Vytvoření řetězce explicitním zápisem (literálem) s = 'retezec v apostrofech' a s = "retezec v uvozovkach"

  • Vytvoření řetězce konverzí s = str(math.pi)

  • Vytvoření řetězce obsahujícího znak s daným ASCII/Unicode kódem chr(65)

Také jsme potkali speciality

  • Speciální znaky "a\tb\n1\t2"

  • Formátované řetězce f'Pro n={2**i} je vysledek {spoctiTo(i)}'

  • Víceřádkové řetězce

s = '''\
<html>
 <body>
   <h1>Hola!</h1>
 </body>
</html>
'''

Dále jsme viděli elementární operace s řetězci

  • Spojování 'a'+'b' (dá 'ab')

  • Množení 'a'*5 (dá 'aaaaa')

Řetězce vs. pole znaků

Je přirozené nahlížet na řetězce jako na pole znaků. Můžeme tedy použít složené závorky a z řetězce přečíst znak na dané pozici. První znak řetezce má index 0, druhý 1, atd.

[ ]:
s = 'ABCD'

for i in range(len(s)):
  print(f's[{i}] == "{s[i]}"')
s[0] == "A"
s[1] == "B"
s[2] == "C"
s[3] == "D"

Abychom si procvičili práci s indexy u řetězců, napíšeme si nejprve funkci převádející řetězec na celé číslo. Samozřejmě, činíme tak jen z výukových důvodů, protože takovou službu již poskytuje konverze int(s), kde s je řetězec obsahující dekadický zápis čísla.

[ ]:
def atoi(s):
  'převede řetězec na celé číslo'
  i = 0   # index cifry
  n = 0   # postupně se kumulující výsledek

  for i in range(len(s)):
    assert '0' <= s[i] <= '9', "Neplatná cifra"
    n = n*10 + ord(s[i])-ord('0')

  return n

def atoi2(s):
  'převede řetězec na celé číslo, tato varianta funkce nepoužívá indexy'
  i = 0   # index cifry
  n = 0   # postupně se kumulující výsledek

  for c in s:
    assert '0' <= c <= '9', "Neplatná cifra"
    n = n*10 + ord(c)-ord('0')

  return n


print( atoi('123456') )

123456

Stejně jako u polí můžeme i u řetězců použíl řezy, tedy např.

[ ]:
s = 'ABCD'
print(f'{      s = }')
print(f'{ s[1:3] = }')
print(f'{  s[1:] = }')
print(f'{ s[:-1] = }')
      s = 'ABCD'
 s[1:3] = 'BC'
  s[1:] = 'BCD'
 s[:-1] = 'ABC'

Zajímavou pomůckou pro zapamatování je, že u řetězců (protože operace + znamená spojování) platí s[:n] + s[n:] == s pro libovolnou (i zápornou) hodnotu n.

To vše ale platí, pokud informaci z řetězce čteme. Není ale dovoleno modifikovat konkrétní znak. Řetězce (typ str) představuje totiž v logice jazyka Python immutable typ. Nebude nám to asi vadit, protože většinou budeme po možnosti modifikovat jednotlivé znaky řetězce toužit v situaci, kdy je cíle možno dosáhnout zavoláním některé z metod typu str.

Nejprve si zběžně ujasněme termín metoda. Souvisí s principy tzv. objektového přístupu k programování, my se zatím učíme přístup procedurální a tak lze zkráceně říci, že jde o funkci, jejíž první argument se místo do závorek za název (identifikátor) funkce nebo procedury připojuje před její název tečkou. Zatímco

  • len('111939') je příkladem volání funkce, kde řetězec je její argument a vrací celé číslo 6, tedy počet znaků, který tvoří daný řetězec

  • '111939'.count('9') je příkladem volání metody. První argument sa nachází před tečkou a volání vrátí hodnotu 2, protože devítka je v řetězci \(2\times\).

    Pokud by se nám nelíbilo, že count musíme psát za tečku, můžeme defiovat stejnojmenou funkci dvou argumentů

    def count(x, a):
      return x.count(a)
    

    a poté psát např. print( count('111223', '2') ).

Použití metod přináší i jisté pohodlí. Pokud dokáže editor kódu
uhodnout typ identifikátoru (nebo výrazu v závorkách) před tečkou, může nabídnout po napsání tečky seznam metod. Pokud jsou rozumně pojmenované, můžeme bez hledání v dokumentaci dosáhnout potřebného efektu.

Vybrané metody typu str.

Operací, které jsou takto dostupné jako metody typu str hodně. Podrobně vše najdete v dokumentaci, zde jen stručné shrnutí. Tyto funkce občas uvidíte použité v příkladech, ale je jasné, že jejich znalost nebude u zkoušky potřeba.

  • Mnoho funkcí se zabývá modifikací velkých a malých písmen v řetězci, např. lower, upper, capitalize, swapcase. Příklad: 'xxxxii'.upper() == 'XXXXII'.

  • Další funkce testují, zda řetězec splňuje nějaká kritéria, např. isalpha, isdecimal, islower.

  • Odstranění mezer na začátku a/nebo konci řetězce zajistí metody strip, lstrip, rstrip. Příklad: '  1 2 3  '.strip() == '1 2 3'.

  • Metody find a replace zajistí elementární vyhledávání a nahrazovanání v řetězci.

    print( '1\t2\t3'.replace('\t','...') )
    

    vytiskne

    1...2...3
    

    Většinou však použijete složitější variantu používajíc tzv. *regulární výrazy`, která dovolí pohodlně provét i takovou operaci, jako nalezení letopočtů zapsných v římskými číslicemi v textu a jejich nahrazení dekadickou hodnotou. Jde o rozsáhlé téma, které sice nespadá do "základů programování", nicméně je natolik důležité, že o existenci musíte vědět. Proto jej zmíníme v kapitole "Co jsme nestihli".

  • Podobné operaci hledání je rozdělení řetězce na seznam řetězců. Opět můžeme vzít jako oddělovač jediný znak (např. čárku), pak nám stačí metoda split typu str. Důležitý příklad souvisí s interpretací řádku čísel načtených ze souboru.

Příklad Následující kód nejprve získá data o hranicích souhvězdí z webu a následně je načte. Protože bez podpory dalších knihoven mámě v Pythonu načtená data v podobě řetězce musíme tento převést na číslené údaje.

Klíčová je zde funkce str.split(sep), která rozdělí řetězec na místech určených hodnotou argumentu sep. V našem případě je to jednak mezera a poté dvojtečka (viz formát dat).

Další užitečnou vlastností jazyka, kterou několikrát využijeme je unpacking.Již víme, že ten v nejjednodušší podobě znamená přiřazení prvků pole do tolika proměnných, kolik je délka pole, např. a,b,c = [1,2,3].

Pozn. Pokud program spouštíte na počítači, kde nemáte instalovaný program wget, soubor bound_edges_18.txt můžete do běžného adresáře uložit webovým prohlížečem.

[ ]:
# Hranice souhvězdí ze stránky https://pbarbier.com/constellations/boundaries.html
! wget https://pbarbier.com/constellations/bound_edges_18.txt
--2023-12-11 10:29:31--  https://pbarbier.com/constellations/bound_edges_18.txt
Resolving pbarbier.com (pbarbier.com)... 104.244.120.9
Connecting to pbarbier.com (pbarbier.com)|104.244.120.9|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 89114 (87K) [text/plain]
Saving to: 'bound_edges_18.txt'

bound_edges_18.txt  100%[===================>]  87.03K   487KB/s    in 0.2s

2023-12-11 10:29:32 (487 KB/s) - 'bound_edges_18.txt' saved [89114/89114]

[ ]:
# Nakreslí lomené "čáry" vyznačující dané souhvězdí na obloze
# data v souboru mají formát/význam
#
# 587:586 P+ 11:31:00 +11:00:00 11:52:00 +11:00:00 VIR LEO
# KE1:KE2 ED    RA1      DE1        RA2     DE2    CO1 CO2
#
#  KE1,KE2  ... Vertex keys
#  ED       ... Edge type and direction -- [M]eridian or [P]arallel
#  RA1, DE1 ... Right ascension and declination (B1875) of 1st vertex
#  RA2, DE2 ... Right ascension and declination (B1875) of 2nd vertex
#  CO1, CO2 ... Constellations delimeted by this edge

import matplotlib.pyplot as plt

def hex2dec(xms):
  "Převádí úhel v šedesátkové (hexagesimální) soustavě x:minuty:vteřiny na reálné číslo"
  x, m, s = xms.split(':')
  return int(x) + int(m)/60 + int(s)/3600

def plot_constellation_bdry(name):
  'Nakreslí lomenou "čáru" vyznačující dané souhvězdí na obloze'

  hrs2deg = -360/24   # konverze hodin na stupně u rektascense

  with open('bound_edges_18.txt') as file:
    for line in file:
      ke12, ed, ra1, de1, ra2, de2, co1, co2 = line.split(' ')

      if co1==name or co2==name:
        ra1 = hex2dec(ra1) * hrs2deg
        de1 = hex2dec(de1)
        ra2 = hex2dec(ra2) * hrs2deg
        de2 = hex2dec(de2)

        plt.plot([ra1,ra2],[de1,de2],'k')


plot_constellation_bdry('ORI')
plt.axis("equal")
plt.grid()
plt.show()





../_images/Prednaska_0085_retezce_2_9_0.png

Srovnáním našeho obrázku s tím na wikipedii vidíme nedostaky našeho zobrazení sféry na rovinu (naše poledníky jsou vertikály). Orientace a škálování vodorovné osy na tomto obrázku pak vyjasní, proč jsem museli použít konstantu hrs2deg = -360/24.

Orion.

Regulární výrazy

Jde o jazyk určený k popisu řetězců. Například má-li být identifikátor neprázdná posloupnost písmen, číslic a podtržítka nezačínající číslicí, použijeme regulární výraz

[a-zA-Z_][a-zA-Z0-9_]*

Dnes se běžně používá v textových editorech ke specifikaci hledaného řetězce a pokud v programu potřebujeme upravovat řetězce (například při načítání dat v nepohodlném tvaru) je regulární výraz ve většině případů správným nástrojem jaký použít.

Pravidel pro psaní regulárních výrazů je hodně, ty nejdůležitější jsou - a znamená znak a - [abc] znamená znak a nebo b nebo c - [a-d] znamená znak a nebo b nebo c nebo d - [^xy] znamená libovolný znak kromě x a y - ab* znamená a nebo ab nebo abb nebo abbb atd. - ab+ znamená ab nebo abb nebo abbb atd. - ab? znamená a nebo ab - a(bb)?c znamená ac nebo abbc

Závorky navíc označují skupiny, jejichž hodnotu můžeme dále použít.

Regulární výrazy jsou dnes součástí nástrojů pro vyhledávání v editorech a jsou také vhodným nástrojem, pokud ve vašem programu potřebujete modifikovat řetězce nebo textové soubory. Následující příklad se pokouší řešit převod desetinné čárky v číslech na desetinnou tečku. Funkce re.sub (sub jako substitute) je zde použita se třemi parametry:

  • první představuje vyhledávaný text popsaný regulárním výrazem

  • druhý pak text, který jej má nahradit. Speciální výrazy \1 a \2 se nahradí tím, co se napasovalo na první resp. druhou skupinu uvedenou v závorkách v regulárním výraze.

  • třetí argument představuje prohledávaný text.

[1]:
import re

vstup = """\
1,23, 5,23, 7,02
16,365, 5,96, 11,32
"""

vystup = re.sub(r"([0-9]+),([0-9]+)",r"\1.\2", vstup)

print(vystup)
1.23, 5.23, 7.02
16.365, 5.96, 11.32

Poznámka: Na webu regexr.com si můžete vyzkoušet svoje nebo prohlédnout cizí nápady na tvar regulárního výrazu pro daný problém.

Cvičení: Zkuste vzít prvních pár řádek tabulky https://github.com/dbwebb-se/vlinux/blob/master/example/grep/presidents.txt a s použijtím výrazu ([A-Z])[a-z]+ (([A-Z])[a-z]+ )?([A-Z][a-z]+) upravte program výše, aby převáděl jména na iniciály: George Washington, 1789-1797  --> G. Washington, 1789-1797