%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
T["texto do padrão"]
A["alternancia: lê uma concatenação"]
C["concatenacao: lê uma repetição"]
R["repeticao: lê um átomo"]
AT{"atomo:<br/>o que vem?"}
F["uma folha"]
G["lê uma alternância<br/>e exige o )"]
K["a classe de símbolos<br/>vira alternância de folhas"]
E["erro, com a posição<br/>em que a falta se constata"]
T --> A --> C --> R --> AT
AT -->|"símbolo ou ."| F
AT -->|"("| G
AT -->|"["| K
AT -->|"nada disso"| E
Expressões regulares e linguagens regulares — Atlas
O módulo inteiro em oito peças visuais. Cada uma olha o mesmo conteúdo por um lado: o resumo desenhado, os procedimentos passo a passo, os cartões para treinar de memória, o módulo contado em quadros, o mapa dos tópicos, o painel que junta tudo, a rede de conceitos e a folha para consultar na hora.
Resumo ilustrado
Fluxogramas
Cinco procedimentos do módulo. Siga cada um com o lápis, sobre uma expressão sua.
Ler o padrão e montar a árvore
Quatro funções, uma chamando a outra. A ordem das chamadas é a precedência: quem é chamado por último prende com mais força.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
S["o átomo lido: a árvore x"]
RQ{"vem *, + ou ?"}
RS["fecho(x) para o *<br/>concat(x, fecho(x)) para o +<br/>alt(x, vazio) para o ?"]
CQ{"começa outro átomo<br/>logo em seguida?"}
CJ["lê a repetição seguinte;<br/>concat(esquerda, direita)<br/>vira a nova esquerda"]
AQ{"vem uma barra |?"}
AJ["lê a concatenação seguinte;<br/>alt(esquerda, direita)<br/>vira a nova esquerda"]
FIM["árvore pronta"]
S --> RQ
RQ -->|"sim"| RS --> RQ
RQ -->|"não"| CQ
CQ -->|"sim"| CJ --> CQ
CQ -->|"não"| AQ
AQ -->|"sim"| AJ --> AQ
AQ -->|"não"| FIM
Calcular o conjunto que a expressão denota
%%{init: {"flowchart": {"useMaxWidth": false}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
I["árvore do padrão"]
F{"que folha é?"}
F1["vazio: ∅"]
F2["cadeia vazia: {ε}"]
F3["símbolo a: {a}"]
S{"o nó de cima<br/>é qual operador?"}
U["escolher: une os<br/>conjuntos dos filhos"]
K["colar: cada cadeia do primeiro<br/>seguida de cada cadeia do segundo"]
P["repetir: junta as potências,<br/>a começar pela potência zero, {ε}"]
Q{"chegou à raiz?"}
L["o conjunto da raiz é L(r)"]
I --> F
F --> F1
F --> F2
F --> F3
F1 --> S
F2 --> S
F3 --> S
S --> U
S --> K
S --> P
U --> Q
K --> Q
P --> Q
Q -->|"não, sobe um nível"| S
Q -->|"sim"| L
Reduzir um atalho ao núcleo
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart LR
Q{"qual atalho<br/>a leitura<br/>encontrou?"}
M["x+ vira concat(x, fecho(x))<br/>a subárvore de x aparece duas vezes"]
O["x? vira alt(x, vazio)<br/>é por aqui que ε entra na árvore"]
E["[abc] vira a alternância<br/>das folhas a, b e c"]
R["[a-c] vira [abc],<br/>e daí a alternância"]
W["o coringa . fica como folha própria:<br/>redutível, mas a expansão teria<br/>quase uma centena de folhas"]
Z["x{3,5} fica fora da notação:<br/>redutível, descartado por utilidade"]
N["núcleo: colar,<br/>escolher, repetir;<br/>folhas: símbolo e ε"]
Q --> M
Q --> O
Q --> E
Q --> R
Q --> W
Q --> Z
M --> N
O --> N
E --> N
R --> N
Decidir se duas expressões são equivalentes
%%{init: {"flowchart": {"useMaxWidth": false}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
D["duas expressões, r e s"]
RD["lê e reduz as duas ao núcleo"]
C{"as formas prefixas<br/>são o mesmo texto?"}
S["equivalentes: a mesma árvore<br/>denota a mesma linguagem"]
N["nada decidido ainda:<br/>árvores diferentes podem<br/>denotar a mesma linguagem"]
T{"existe uma cadeia que está<br/>numa linguagem e falta na outra?"}
NE["não equivalentes:<br/>a cadeia é a testemunha"]
P["a decisão plena compara<br/>as máquinas mínimas, no módulo 05"]
D --> RD --> C
C -->|"sim"| S
C -->|"não"| N --> T
T -->|"achei uma"| NE
T -->|"não achei"| P
Saber se um requisito cabe na classe regular
%%{init: {"flowchart": {"useMaxWidth": false}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
R["requisito escrito em prosa"]
P1["(1) que pergunta a máquina<br/>teria de responder<br/>ao chegar ao fim do texto?"]
P2["(2) o que ela precisaria<br/>lembrar para responder aquilo?"]
P3{"(3) o que ela precisa lembrar<br/>tem teto declarado?"}
D["cabe na classe regular:<br/>existe expressão que denota<br/>a linguagem do requisito"]
F["fica fora da classe:<br/>nenhuma expressão denota<br/>a linguagem do requisito"]
R --> P1 --> P2 --> P3
P3 -->|"sim, teto fixo"| D
P3 -->|"não, cresce com o texto"| F
Cartões de memória
Leia a frente, diga a resposta em voz alta ou escreva no papel, e só então vire o cartão.
História em quadros
O módulo contado do começo ao fim, um quadro por passo, na ordem em que o livro avança.
Mapa mental
Os tópicos do módulo, do centro para fora, na ordem do livro.
Painel do conhecimento
Conceitos
- Expressão regular: o molde, feito de folhas (\emptyset, \varepsilon, um símbolo) e três operadores (escolher, colar, repetir).
- Linguagem denotada, L(r): o conjunto de cadeias que o molde descreve.
- Linguagem regular: a que alguma expressão denota.
- Equivalência, r \equiv s: L(r) = L(s).
- Fechamento: unir, colar e repetir linguagens regulares devolve linguagem regular.
Exemplos
a*: dois caracteres, conjunto infinito.a|bcdenota \{a, bc\};(a|b)cdenota \{ac, bc\}.(ab)ceabcdão a mesma árvore.- L(\emptyset^*) = \{\varepsilon\}.
[a-z]: cinco caracteres, 51 nós.
Diagrama
%%{init: {"flowchart": {"useMaxWidth": false}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TB
C(("concatenação")) --> A(("alternância"))
C --> F3(("'c'"))
A --> F1(("'a'"))
A --> F2(("'b'"))
F1 -.->|"denota"| L1["{a}"]
F2 -.->|"denota"| L2["{b}"]
F3 -.->|"denota"| L3["{c}"]
A -.->|"denota"| L4["{a, b}"]
C -.->|"denota"| L5["{ac, bc}"]
Evidências
- Dezembro de 1951: Kleene escreve o memorando RM-704 da RAND, sobre redes de neurônios.
- Junho de 1968: Thompson publica um método que lê o texto uma vez e nunca volta atrás.
- 2 de julho de 2019: na Cloudflare, o fragmento
.*.*=.*, num motor que retrocede, custa um trabalho que cresce com o cubo do comprimento da linha.
Referências
- Kleene, RM-704, RAND, 1951.
- Thompson, Regular Expression Search Algorithm, Communications of the ACM, v. 11, n. 6, p. 419–422, 1968.
- Graham-Cumming, relatório do incidente da Cloudflare, 2019.
- Cox, Regular Expression Matching Can Be Simple And Fast, 2007.
- Aho, Handbook of Theoretical Computer Science, 1990.
Aplicações
- Busca em texto: em 1973, a pedido de McIlroy, o
g/re/psai do editorede vira programa próprio. - Descrever os trechos de uma categoria num programa: o padrão é uma expressão regular.
- Dois motores, dois compromissos: PCRE (1997) retrocede; RE2 (Google, 2010) não.
- Nos módulos 03 a 05, a expressão vira a máquina que reconhece.
Mapa conceitual
Escreva num papel: expressão regular, árvore sintática, operadores, folhas, precedência, linguagem denotada, linguagem regular, atalho, núcleo, equivalência, convergência estrutural, fechamento, memória finita, retrovisão, máquina. Ligue os conceitos com setas, e escreva em cada seta um verbo que diga qual é a relação. Depois compare com o mapa.
Folha de consulta
Escrita e precedência
- repetir, o fecho:
a*, prende primeiro - colar, a concatenação:
ab, prende depois - escolher, a alternância:
a|b, prende por último - iguais em fila agrupam pela esquerda:
abcviraconcat(concat('a', 'b'), 'c') - parênteses mudam a forma da árvore e custam zero nós
O conjunto, caso a caso
- L(\emptyset) = \emptyset, L(\varepsilon) = \{\varepsilon\}, L(a) = \{a\}
- L(r \mid s) = L(r) \cup L(s)
- L(rs) = L(r)\,L(s)
- L(r^*) = L(r)^*, a começar pela potência zero, \{\varepsilon\}
Atalhos e o que viram
| Atalho | Vira |
|---|---|
x+ |
concat(x, fecho(x)) |
x? |
alt(x, vazio) |
[abc] |
a alternância de a, b, c |
[a-c] |
[abc] |
. |
fica como folha própria |
x{3,5} |
fora da notação |
Contas de nós
- faixa de n símbolos: 2n - 1 nós
[0-9]: 19 ·[a-z]: 51 ·[a-z]?: 53 ·[a-z]+: 104- três faixas concatenadas: 155; sob
+: 312, de 18 caracteres a|bc: 5 nós · (a \mid b)^{*}c: 6 nós
Bordas e identidades
- colar com \emptyset dá \emptyset; colar com \varepsilon não muda nada
- L(\emptyset^*) = \{\varepsilon\}
- (a^*)^* \equiv a^* e (a \mid b)^* \equiv (a^*b^*)^*
- a^*b^* \not\equiv (a \mid b)^*:
baestá só na segunda - L(a^*) contém \varepsilon; L(a^+), não
A cerca da classe
- dentro: unir, colar, repetir; também complemento e interseção, que a notação não oferece como operador
- dentro: aninhamento até uma profundidade fixa, um conjunto infinito
- fora: tantos
aquantob; parênteses sem profundidade máxima; a retrovisão
Erros previsíveis
- “a expressão reconhece a cadeia”: ela denota um conjunto; quem reconhece é a máquina
- “árvores diferentes, linguagens diferentes”: (a \mid b)^* e (a^*b^*)^* desmentem
- “o asterisco quer dizer qualquer coisa”: aqui ele junta as potências
- “o coringa não é redutível”: é, e ficou por tamanho
- “a retrovisão é um atalho”: ela sai da classe