<jupyter_start><jupyter_text>Padrões e classes de caracteres<jupyter_code>#lista com notas de um aluno durante um semestre
notas = 'ABCAABAACBCBACBCAAAA'

#para descobrir quantos B esse aluno teve, podemos usar o B como parâmetro
re.findall('B', notas)
#agora apra saber a quantidade de A ou B não podemos colocar AB, pq daí ia buscar um A seguido de um B
#coloca então o AB dentro de colchetes, daí ele busca individualmente quantas vezes aparece

re.findall('[AB]', notas)

#ainda da pra buscar por letras minusculas colcoando [a-z]
#construiindo um regex para analisar todas as vezes que o aluno ganhou um A seguido por B ou C
re.findall("[A][B-C]", notas) #busca ai todos os A que são seguidos por um B ou por um C

#também pode usar o | para representar o OU
re.findall('AB|AC', notas)
#podemos usar o ^ com o operador para negar o resultado, por exemplo, se quisermos analisar apenas as notas que NÂO SÂO A:
re.findall("[^A]", notas) #exclui tudo que é A e manda o resto<jupyter_output><empty_output><jupyter_text>Quantificadores<jupyter_code>#quantificadores são o número de vezes você quer que um padrão seja combinado/correspondido
#O mais básico é expressado por e{m,n} onde: E é a expressão ou caractere que estamos combinando,
#M é o mínimo de vezez que você quer que ele seja combinado e N é o número máximo de vezes a ser combinado

#usando as notas como exemplo, quantas vezes esse estudante tirou notas A em sequencia? ou seja, duas notas A uma após a outra
re.findall('A{2,5}', notas)
#olha, quantas vezes esse aluno tirou notas a em sequência?
#quero que seja no mínimo duas notas e no máximo dez notas A em sequência

re.findall('A{1,1}A{1,1}', notas)
#a diferença aqui é que o primeiro código procura por no mínimo duas letras A e máximo de 5 A's em sequência
#no segundo cógido ele procura um A seguido por outro A, nesse caso, 4 A's são dois A's seguidos por mais dois A's
#ele corta uma sequência de 4 em duas de 2

#essa função não pode ser separada por espaços dentro das {}, o {m,n} tem que ser colado um no outro, não pode ser {m, n}
#e se não colocar um valor para m ou n, o default sera {1,1}
#se tiver só um número A{2} ele será o m e o n, mínimo e máximo


#podemos encontrar um padrão decrescente nas notas do aluno, uma piora
re.findall('A{1,10}B{1,10}C{1,10}', notas)
#tem outros três quantificadores que são usados de formas mais simples. um asterisco * para zero 
#ou um match, uma interrogação ? para 1 ou 2 combinações ou um sinal de + para uma ou mais combinações
with open('ferpa.txt', 'r') as file:
    wiki = file.read()
wiki
#olhando o documento, todos os cabeçalhos começam com [edit] seguidos por um \n
#então se quisermos uma lista de todos os cabeçalhos, podemos usar o findall

re.findall('[a-zA-z]{1,1000}\[edit\]', wiki)
#deu certo mas pegou só a última palavra do cabeçalho, queremos o cabeçalho inteiro
#tem uma opção de usar o \w para dar match em qualquer letra, incluindo dígitos e números
re.findall('[\w]{1,1000}\[edit\]', wiki)
#podemos usar outros três quantificadores para remover as chaves {} do código
#começando pelo asterisco *
re.findall('[\w]*\[edit\]', wiki)
#aqui adicionamos espaço usando um espaço após o W
re.findall('[\w ]*\[edit\]', wiki)
#isso nos deu uma lista de títulos numa página da wiki
#agora vamos criar uma lista de titulos
for title in re.findall('[\w ]*\[edit\]', wiki):
    #agora pegamos esse resultado intermediário e separamos no colchete e pegamos o primeiro resultado
    print(re.split('[\[]', title)[0])<jupyter_output>Overview
Access to public records
Student medical records<jupyter_text>Grupos<jupyter_code>#da pra dar match com padrões diferentes, chamados de grupos
#para agrupar padrões usa-se o parenteses
#é tipo buscar dois padrões de uma vesz só

re.findall('([\w ]*)(\[edit\])', wiki)
#o python quebra o nosso resultado em grupos, mas podemos nos referir aos grupos por números também
#nesse caso usamos o finditer()

for item in re.finditer('([\w ]*)(\[edit\])', wiki):
    print(item.groups())
#vemos que o método groups() retorna uma tupla contendo os grupos. podemos pegar um grupo individual usando group(number)
#onde number = número do grupo e group(0) são todas as correspondências
print('Grupo 1 abaixo')
for item in re.finditer('([\w ]*)(\[edit\])', wiki):
    print(item.group(1))
print('-----')
print('Grupo 2 abaixo')
for item in re.finditer('([\w ]*)(\[edit\])', wiki):
    print(item.group(2)) 
#mais uma opção do grupos regex é dar nome aos grupos.par aisso usamos a syntax (?P<name>)
#onde os parênteses iniciam os grupos, o ?P indica que é uma extensão de regex básica e <name> é a chave do dicionário
#que queremos usar envelopada em <>

for item in re.finditer('(?P<title>[\w ]*)(?P<edit_link>\[edit])', wiki):
    #aqui podemos retornar um dicionário para o grupo usando o .groupdict()
    print(item.groupdict()['title'])
    #também podemos imprimir o dicionário completo, mostrando que o [edit] ainda está lá como um valor da chave "edit_link"
    print(item.groupdict())
    print('----')<jupyter_output>Overview
{'title': 'Overview', 'edit_link': '[edit]'}
----
Access to public records
{'title': 'Access to public records', 'edit_link': '[edit]'}
----
Student medical records
{'title': 'Student medical records', 'edit_link': '[edit]'}
----<jupyter_text>Look-ahead e look-behind matching<jupyter_code>#a gente pega um pedaço do texto mas usa somente o que vem antes ou depois desse trecho em específico,
#como no caso do [edit], a gente usa ele como referência mas não retorna esse [edit],
#daí ele é colocado num grupo diferente e o que nós queremos encontrar vai em outro grupo

for item in re.finditer('(?P<title>[\w ]+)(?=\[edit\])', wiki):
    print(item)

with open('buddhist.txt', 'r') as file:
    wiki = file.read()
wiki
#a descrição segue um padrão: o nome seguido por um - depois "located in" e por fim a cidade e o estado
#usaremos o "verbose". com ele você pode escrever regexes em mais de uma linha e incrementar a leitura. temos que
#indicar explicitamente todos os caracteres de espaço vazio, tanto por colocar um \ antes dele ou usando
#o valor especial /s. daí podemos escrever o regex mais em forma de código

pattern="""
(?P<title>.*)        #the university title
(–\ located\ in\ )   #an indicator of the location
(?P<city>\w*)        #city the university is in
(,\ )                #separator for the state
(?P<state>\w*)       #the state the city is located in"""

#agora quando chamamos a função finditer() passamos o flag re.VERBOSE como último parâmetro,
#isso torna muito mais fácil de entender regexes grandes
for item in re.finditer(pattern, wiki, re.VERBOSE):
    #aqui podemos obter o dicionário para o item com .groupdict()
    print(item.groupdict())
with open('nytimeshealth.txt', 'r') as file:
    health = file.read()
health
#vamos tentar pegar todas as hashtags desse documento
#criando um padrão que pega um # depois qualquer quantidade de caracteres alfanuméricos e acaba quando tem espaço vazio
pattern='#[\w\d]*(?=\s)'
#o final é um look-ahead

re.findall(pattern, health)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10361888606163415, 'fraction_numerical': 0.005654509471303365, 'mean_word_length': 4.471771075019335, 'pattern_counts': {'":': 0, '<': 24, '<?xml version=': 0, '>': 24, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '4', 'dir': 'jupyter-structured-clean-dedup', 'id': '23454', 'license': 'MIT', 'n_tokens_mistral': 2578, 'n_tokens_neox': 2510, 'n_words': 1029, 'path': 'regex.ipynb', 'repo_name': 'onativo/DataScience101', 'size': '7074'}