<jupyter_start><jupyter_text>COMANDOS AUXILIARES  Inserção de imagens<jupyter_code>from IPython.display import Image<jupyter_output><empty_output><jupyter_text>ESTRUTURAS ITERÁVEIS Como vimos nas aulas anteriores, o Python possuí duas grandes estruturas de repetição: `for` e `while`. Dessa forma, todo o bloco de código que fica dentro dessas estruturas de repetiçao é executado e repetido várias vezes, até que uma determinada condção seja satisfeita.Porém, elas dessas estruturas de repetição, no Python existem estruturas que nos permitem iterar sobre elas. Nós chamamos essas estruturas de **Estruturas Iteráveis**. Um exemplo de uma estrutura iterável é a **lista**. Além das **listas** temos também **tuplas**, **dicionários**, **sets** e até mesmo **strings**.Temos também funções que geram estruturas iteráveis, como a função `range()`. De todas as formas, uma **Estrutura Iterável** é qualquer estrutura em Python que podemos iterar sobre elas, seja utilizando `for` ou `while`.Para exemplificar o que acabamos de ver, e revisar o que já vimos, vamos exemplificar a iteração de cada uma dessas estruturas:<jupyter_code>idade = 32
idade
idade = 42
idade
r = range(3, 16)
r
for indice in r:
    print(indice)
l = ['Pedro', 32, 1.83, True]
#      0       1    2     3
l[1]
for elemento in l:
    print(elemento)
for indice, elemento in enumerate(l):
    print(f'O valor do indice é = {indice}; O valor do elemento é = {elemento}')
for elemento in range(len(l)): # range(4) -- range(5)
    print(elemento)
dicionario = {
    'nome': 'Pedro', 
    'idade': 32, 
    'altura': 1.83
}
dicionario
dicionario['casado'] = True
dicionario
dicionario['casado'] = 'Qualquer outra coisa'
dicionario
dicionario['casado'] = None
dicionario
dicionario.keys()
for chave, valor in dicionario.items():
    print(f'O valor da chave = {chave}; O valor do valor = {valor}')
for chave in dicionario:
    print(chave)
for valor in dicionario.values():
    print(valor)
lista = ['Pedro', 'Guilherme', 'Volpato', 'Pedro','Pedro', 'Guilherme']
dic = {}

for elemento in lista:
    if elemento in dic.keys():
        dic[elemento] += 1 # Soma uma unidade no valor
    else:
        dic[elemento] = 1 # Cria a chave e coloca o valor igual a 1
    
dic
from collections import Counter

l = Counter(lista)
l
s = set(lista)
s
# s = set(dicionario.values())
# s
for elemento in s:
    print(elemento)
nome = 'Pedro'
nome
for letra in nome:
    print(letra)<jupyter_output>P
e
d
r
o<jupyter_text>Função `range( stop ) | range( start, stop [, step] )` Como vimos nas aulas anteriores, a função `range()` cria uma estrutura iterável, onde passamos o valor inicial no parâmetro `start`, qual o ponto de parada no argumento `stop` e o tamanho do incremento no parâmetro opcional `step`. Além disso, podemos utilizar a forma "abreviada" da função e passar somente o argumento `stop`.Exemplos:<jupyter_code># declaração de uma variável do tipo range
a = range(10)

# Prints de Debug
print(f'O conteudo da variavel a = {a}') 
print(f'O tipo da variavel a = {type(a)}')<jupyter_output>O conteudo da variavel a = range(0, 10)
O tipo da variavel a = <class 'range'><jupyter_text>Como iterar sobre um range<jupyter_code># Forma normal
for index in range(0, 5, 2):
    print(f'Somente valores pares = {index}')
# Forma abreviada
for index in range(5):
    print(f'Todos os valores = {index}')
# Iterar de forma decrescente
for index in range(10, 0, -2):
    print(f'Valores pares contados de forma decrescente = {index}')<jupyter_output>Valores pares contados de forma decrescente = 10
Valores pares contados de forma decrescente = 8
Valores pares contados de forma decrescente = 6
Valores pares contados de forma decrescente = 4
Valores pares contados de forma decrescente = 2<jupyter_text>Dessa forma, utilizamos o `range()` para criar uma estrutura que seja iterável. Normalmente utilizamos a forma abreviada quando não nos importamos nem em que valor será iniciada as repetições e nem em como os valores serão incrementados ou decrementados. Porém, se essas informações fazem sentido para resolver os nosso problema, então devemos utilizar a forma completa.  Iterar sobre listas **Listas** são estruturas em que inserimos conteúdos e que cada conteúdo possuí uma chave identificadora, um índice.Listas são estruturas iteráveis, o que significa que não precisamos utilizar nenhum outro método para itarar sobre elas além do **for**:<jupyter_code>lista = [1, 2, 3, 4, 5]

for elemento_interno in lista:
    print(f'Os elementos da lista sao = {elemento_interno}')<jupyter_output>Os elementos da lista sao = 1
Os elementos da lista sao = 2
Os elementos da lista sao = 3
Os elementos da lista sao = 4
Os elementos da lista sao = 5<jupyter_text>Note que percorremos todos os conteúdo da nossa lista e imprimimos somente o conteúdo de cada índice. Caso seja necessário verificar qual é o índice e o elemento desse índice, podemos utilizar a seguinte forma:<jupyter_code>for indice in range(len(lista)):
    print(f'O elemento que esta no indice {indice} é = {lista[indice]}')<jupyter_output>O elemento que esta no indice 0 é = 1
O elemento que esta no indice 1 é = 2
O elemento que esta no indice 2 é = 3
O elemento que esta no indice 3 é = 4
O elemento que esta no indice 4 é = 5<jupyter_text>Porém, para casos como estes, existem uma função em Python que nos permite trabalhar de forma mais simples e direta: a função `enumerate()`.Com essa função, conseguimos acessar não só o índice, mas também o conteúdo ou elemento que está dentro desse índice:<jupyter_code>for indice, elemento in enumerate(lista):
    print(f'O elemento que esta no indice {indice} é = {elemento}')<jupyter_output>O elemento que esta no indice 0 é = 1
O elemento que esta no indice 1 é = 2
O elemento que esta no indice 2 é = 3
O elemento que esta no indice 3 é = 4
O elemento que esta no indice 4 é = 5<jupyter_text>Iterar sobre tuplas Assim como as **listas**, as **tuplas** são estruturas que adicionamos valores e que cada valor dentro dela possuí um índice.Assim como as **listas**, as **tuplas** também são iteráveis e podemos utilizar os mesmo métodos que utilizamos para iterar sobre **listas**:<jupyter_code>tupla = (1, 2, 3, 4, 5)

for elemento_interno in tupla:
    print(f'Os elementos da tupla sao = {elemento_interno}')
for indice in range(len(tupla)):
    print(f'O elemento que esta no indice {indice} da tupla é = {tupla[indice]}')
for indice, elemento in enumerate(tupla):
    print(f'O elemento que esta no indice {indice} da tupla é = {elemento}')<jupyter_output>O elemento que esta no indice 0 da tupla é = 1
O elemento que esta no indice 1 da tupla é = 2
O elemento que esta no indice 2 da tupla é = 3
O elemento que esta no indice 3 da tupla é = 4
O elemento que esta no indice 4 da tupla é = 5<jupyter_text>Iterar sobre dicionários Dicionários são estruturas onde os elementos são compostos pelo conjunto `chave: valor`, onde a `chave` representa o índice da lista e o `valor` represnta o conteúdo que está associado a essa `chave`.Pense na estrutura **dicionário** exatamente como uma **lista** que pode ter outros tipos de índices, que não valores inteiros que iniciam em zero e vão até o quantidade da lista.Assim como as **listas**, os dicionários são estruturas iteráveis e temos algumas técnicas ou formas de iterarmos sobre elas. A primeira maneira seria utilizando a função `items()` do dicionário, pois dessa forma conseguimos recuperar não só as chaves, mas também os seus valores:<jupyter_code>dictionary = {
    'nome': 'Pedro',
    'idade': 32,
    'casado': True
}
for chave, valor in dictionary.items():
    print(f'A chave do elemento é = {chave}')
    print(f'O valor do elemento que está na chave {chave} é = {valor}')
    print(f'=============')<jupyter_output>A chave do elemento é = nome
O valor do elemento que está na chave nome é = Pedro
=============
A chave do elemento é = idade
O valor do elemento que está na chave idade é = 32
=============
A chave do elemento é = casado
O valor do elemento que está na chave casado é = True
=============<jupyter_text>Uma outra forma seria a iteração somente com as chaves:<jupyter_code>for chave in dictionary:
    print(f'A chave do elemento é = {chave}')
    print(f'O valor do elemento que está na chave {chave} é = {dictionary[chave]}')
    print(f'=============')<jupyter_output>A chave do elemento é = nome
O valor do elemento que está na chave nome é = Pedro
=============
A chave do elemento é = idade
O valor do elemento que está na chave idade é = 32
=============
A chave do elemento é = casado
O valor do elemento que está na chave casado é = True
=============<jupyter_text>Podemos iterar também somente sobre os valores do nosso dicionário. Para isso, utilizamos o método `values()`:<jupyter_code>for valor in dictionary.values():
    print(f'O valor do elemento é = {valor}')<jupyter_output>O valor do elemento é = Pedro
O valor do elemento é = 32
O valor do elemento é = True<jupyter_text>Iterar sobre sets **Set** é um tipo de estrutura de dado em Python que nos permite criar coleções de elementos não duplicados. **Sets** são muito utilizados quando desejamos criar uma estrutura iterável que tenham valores únicos, e esses valores podem ser relativos as nossas listas, tuplas ou até mesmo strings:<jupyter_code>uniques = set('paralelepipedo')
uniques
uniques = set(['Pedro', 'Guilherme', 'Ferraresi', 'Pedro', 'Pedro'])
uniques
uniques = set([1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4])
uniques
# Forma abreviada de criar um set
uniques = {1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}
uniques<jupyter_output><empty_output><jupyter_text>Observe que sempre que criamos um set, ele já vem **ordenado**.Porém, como nem tudo são flores, não é possível selecionar os valores internos de um **set** como fazemos com as **listas** e **dicionários**.É sempre útil trabalhar com **sets** é que teremos uma estrutura **ordenada** e com valores **únicos**.Para iterarmos sobre o **set**, podemos utilizar a seguinte técnica:<jupyter_code>for valor in uniques:
    print(f'O valor do elemento é = {valor}')<jupyter_output>O valor do elemento é = 1
O valor do elemento é = 2
O valor do elemento é = 3
O valor do elemento é = 4
O valor do elemento é = 5
O valor do elemento é = 6
O valor do elemento é = 7
O valor do elemento é = 8<jupyter_text>Iterar sobre um string Iterar sobre um **string** (objeto do tipo `str`) é tão simples quanto iterar sobre uma **lista** ou um **tupla**:<jupyter_code>string = 'Pedro'
for char in string:
    print(f'O caractere da vez é = {char}')<jupyter_output>O caractere da vez é = P
O caractere da vez é = e
O caractere da vez é = d
O caractere da vez é = r
O caractere da vez é = o<jupyter_text>Dessa forma, podemos percorrer caractere por caractere em nossa string, fazendo comparações e buscas dentro dela!  Exemplos  Utilizando a função range(), crie um iterador que gerará uma lista somente com os valores pares entre 1 e 20  Resolução<jupyter_code>lista = []
lista = list()
# Resolução Aqui
# [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
# range( stop ) | range( start, stop [, step] ) |


valores = list(range(2, 20, 2))
valores<jupyter_output><empty_output><jupyter_text>Utilizando a função range(), crie um iterador que gerará uma lista somente com os valores ímpares de 21 a 0  Resolução<jupyter_code># Resolução Aqui
# [21, 19, 17, 15, 13, 11, 9, 7, 5, 3, 1]

valores = list(range(21, 0, -2))
valores<jupyter_output><empty_output><jupyter_text>SLICING Vimos que Python possuí várias funções prontas para trabalharmos em nossas aulas anteriores e que essas funções estão atreladas aos tipos de dados. Ou seja, estruturas como listas, tuplas, mapas, strings, e etc possuem várias funções *built-in* para que possamos trabalhar de forma mais fácil com elas.Além dessas funções, existem outros métodos e técnicas que utilizamos em Python para selecionar sub partes de certas estruturas, e esse método de seleção é chamado de **slicing**, ou, em tradução literal, **fatiamento**.Podemos executar **slicing** em Strings, Listas e Tuplas. A ideia para essas 3 estruturas é a mesma:```pythonestrutura[ [indice_inicial] : [indice_final] [: incremento ] ]```Para exemplificar, vamos iniciar esse processo com Strings.<jupyter_code>nome = 'Pedro'
nome[1]
nome[0:2]
lista = ['Pedro', 'Guilherme', 'Volpato', 'Ferraresi','Ana', 'Carolina', 'Ubaldo']
#           0          1           2         3          4        5           6
lista[-6:-1:2]
lista[:4]
lista[4:]
nome = '<NAME>'
nome
nome[3:9:2]
nome[::-2]
valor = 32
valor
valor[0]
v = str(valor)
v
v[0]
lista = [
    '<EMAIL>',
    '<EMAIL>',
    '<EMAIL>',
    '<EMAIL>',
    '<EMAIL>',
    '<EMAIL>',
    '<EMAIL>',
]
lista
provedores = []

for email in lista:
    
    indice = email.find('@')
    
    provedores.append(email[indice+1:])
    
    
provedores = list(set(provedores))
provedores<jupyter_output><empty_output><jupyter_text>Silicing em Strings Para executar **slicings** em strings, utilizamos os colchetes e definimos um uma posição incial e final do nosso fatiamento:<jupyter_code>string = '<NAME>'
string
for index, char in enumerate(string):
    print(f'O índice é = {index}; valor do caractere é = {char}')
# Slicing - Selecionando a substring a partir do indice 3
# ate o indice 8
sub_str = string[3:8]
sub_str<jupyter_output><empty_output><jupyter_text>Observe que selecionamos os caracteres que iniciaram no índice `3` da String até o índice `8`, sendo que o índice `8` não foi incluido na substring gerada através do fatiamento.O fatiamento segue a seguinte regra: string[ inicio: fim [: passo] ]. Para exemplificar, vamos gerar uma nova string com mais caracteres:<jupyter_code>string = '<NAME>'
string
for index, char in enumerate(string):
    print(f'O índice é = {index}; valor do caractere é = {char}')<jupyter_output>O índice é = 0; valor do caractere é = P
O índice é = 1; valor do caractere é = e
O índice é = 2; valor do caractere é = d
O índice é = 3; valor do caractere é = r
O índice é = 4; valor do caractere é = o
O índice é = 5; valor do caractere é =  
O índice é = 6; valor do caractere é = G
O índice é = 7; valor do caractere é = u
O índice é = 8; valor do caractere é = i
O índice é = 9; valor do caractere é = l
O índice é = 10; valor do caractere é = h
O índice é = 11; valor do caractere é = e
O índice é = 12; valor do caractere é = r
O índice é = 13; valor do caractere é = m
O índice é = 14; valor do caractere é = e
O índice é = 15; valor do caractere é =  
O índice é = 16; valor do caractere é = V
O índice é = 17; valor do caractere é = o
O índice é = 18; valor do caractere é = l
O índice é = 19; valor do caractere é = p
O índice é = 20; valor do caractere é = a
O índice é = 21; valor do caractere é = t
O índice é = 22; valor do caractere é = o
O índice é = 23; valor do caractere é =  
O [...]<jupyter_text>Agora digamos que desejamos selecionar a substring que inicia no caractere 3 e finaliza no caractere 14, indo de dois em dois caracteres:<jupyter_code>sub_str = string[3:15:2]
sub_str<jupyter_output><empty_output><jupyter_text>Da mesma forma que, quando não passamos um parâmetro inicial ou de parada, o Python entenderá que, ou desejamos todos os elementos até o critério de parada, ou desejamos todos os elementos a partir do critério de início:<jupyter_code>sub_str = string[:15:2]
sub_str
sub_str = string[3::2]
sub_str<jupyter_output><empty_output><jupyter_text>Além disso, podemos utilizar índices negativos para fazer com que o **slicing** seja feito de forma reversa. Por exemplo, se colocamos o valor `-1` em nossa posição inical, estamos dizendo ao Python que desejamos iniciar do último elemento da String. Se colocarmos o valor `-10` na posição final, estamos dizendo que o critério de parada é o nono caractere de trás pra frente. E se colocarmos o valor `-1` no incremente, estamos dizendo que desejamos percorrer caractere por caractere:<jupyter_code>sub_str = string[-1:-10:-1]
sub_str<jupyter_output><empty_output><jupyter_text>Slicing em Listas e Tuplas O **slicing** em listas e tuplas funciona da mesma forma que nas strings, como demonstrado acima:<jupyter_code>lista = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
tupla = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
# Slicing em Listas
sub_lista = lista[2:8:2]
sub_lista
sub_lista = lista[-1:-8:-2]
sub_lista
# Slicing em Tuplas
sub_tupla = tupla[2:8:2]
sub_tupla
sub_tupla = tupla[-1:-8:-2]
sub_tupla<jupyter_output><empty_output><jupyter_text>Compreender corretamente como fazer slicig é extremamente importante pois será um conceito chave quando iniciarmos a utilização da biblioteca **pandas** para manipular **dataframes**.  Exemplos  Utilizando a técnica de slice, recupere somente os elementos pares da seguinte lista ```pythonlista = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]```  Resolução<jupyter_code># Resolução Aqui
# [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
#                      start, stop, step
# valores = list(range(  2,   20,    2))
lista = [11, 1, 22, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
#        0   1  2  3   4  5  6  7
lista
#             start : stop : step
pares = lista[ 2    :      :  2 ]
pares
#  matriz = [     [1, 2, 3],     [4, 5, 6],     [7, 8, 9],   ]
#                     0              1              2

matriz = [
    
    [1, 2, 3, 4, 5],  # 0
#    0  1  2  3  4   
    
    [4, 5, 6, 7, 8],  # 1
#    0  1  2  3  4    
    
    [7, 8, 9, 10, 11],  # 2
#    0  1  2  3   4    
    
    [10, 11, 12, 13, 14],  # 3
#    0   1   2   3   4 

    [15, 16, 17, 18, 19],  # 4
#    0   1   2   3   4 

]
l = matriz[0: : 2]
l
l = matriz[0: : 2][0::2][0][2]
l
l[0]
l[0][2]
matriz<jupyter_output><empty_output><jupyter_text>Utilizando a técnica de slice, recupere somente os elementos ímpares da seguinte lista ```pythonlista = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]```  Resolução<jupyter_code># Resolução Aqui
# [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
lista = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
#                                                              -5  -4  -3  -2  -1
l = lista[1::2]
l<jupyter_output><empty_output><jupyter_text>COMPREHENSIONS<jupyter_code>clientes = [
    { 'nome': 'Pedro',     'idade': 32, 'casado': True  }, # 0
    { 'nome': 'Guilherme', 'idade': 23, 'casado': False }, # 1
    { 'nome': 'Volpato',   'idade': 82, 'casado': True  }, # 2
    { 'nome': 'Ferraresi', 'idade': 52, 'casado': True  }, # 3
    { 'nome': 'Ana',       'idade': 29, 'casado': True  }, # 4
    { 'nome': 'Carolina',  'idade': 19, 'casado': False }, # 5
    { 'nome': 'Curvina',   'idade': 54, 'casado': True  }, # 6
    { 'nome': 'Ubaldo',    'idade': 85, 'casado': True  }, # 7
    { 'nome': 'Joao',      'idade': 26, 'casado': False }, # 8
    { 'nome': 'Gabriel',   'idade': 62, 'casado': False }, # 9
]
casados = []


for i in clientes:
    if cliente['casado']:
        casados.append(cliente)
# item for item in iteravel 

clientes_2 = [ cliente for cliente in clientes if cliente['casado'] ]
clientes_2
matriz = [
    
    ['Pedro', 'Guilherme', 'Volpato', 'Ferraresi'],  # 0
    ['Edu', 'Robson', 'Aline', 'Alex'],             # 1
    ['Fernando', 'Geová', 'Guilherme', 'Heitor'],   # 2
    ['Jacqueline', 'João', 'Carlos', 'Ana'],        # 3
    ['Bruno', 'Maria', 'Rafael', 'Victor'],         # 4

]
nomes_a = []


for nomes in matriz:
#     print(nomes)
    
    for nome in nomes:
#         print(nome)
#         print(nome[0])
        if nome[0] == 'A':
#         if nome.startswith('A'):
            nomes_a.append(nome)

#     print('==== fim da iteração =====')

nomes_a
nomes_a = []


for nomes in matriz:
    
    for nome in nomes:
        
        if nome[0] == 'A':
            nomes_a.append(nome)
m = [ nome for nomes in matriz for nome in nomes if nome[0] != 'A' ]
m<jupyter_output><empty_output><jupyter_text>Agora que vimos que no Python existem estruturas iteráveis, vamos aprender o que são **List Comprehensions**.**Comprehensions** em Python são formas de abreviadas e *in-line* de criarmos listas, sets ou dicionários. **Comprehensions** são muito utilizados na comunidade de Python quando desejamos criar uma estrutura de dados certos com resultados, sendo que esses resultados podem ou não atender a alguma condição.Ou seja, podemos utilizar uma **Comprehension** para percorrer uma lista de clientes, por exemplo, e criar um critério de seleção dentro da **Comprehension** para gerar uma lista de saída. **List Comprehensions** são as **Comprehensions** que geram uma lista como resultado.A estrutura básica para a geração / utilização de uma **list comprehension** é:```python[ item for item in iteravel ]```Para exemplificar, vamos criar uma lista com vários clientes dentro:<jupyter_code>clientes = [
    { 'nome': 'Pedro',     'idade': 32, 'casado': True  },
    { 'nome': 'Guilherme', 'idade': 23, 'casado': False },
    { 'nome': 'Volpato',   'idade': 82, 'casado': True  },
    { 'nome': 'Ferraresi', 'idade': 52, 'casado': True  },
    { 'nome': 'Ana',       'idade': 29, 'casado': True  },
    { 'nome': 'Carolina',  'idade': 19, 'casado': False },
    { 'nome': 'Curvina',   'idade': 54, 'casado': True  },
    { 'nome': 'Ubaldo',    'idade': 85, 'casado': True  },
    { 'nome': 'Joao',      'idade': 26, 'casado': False },
    { 'nome': 'Gabriel',   'idade': 62, 'casado': False },
]<jupyter_output><empty_output><jupyter_text>Agora vamos exemplificar a seleção de todos os clientes que não são casados utilizando uma estrutura **for** comum:<jupyter_code># Criação da lista que armazenará as respostas
selecionados = []

# Estrutura for que irá percorrer e selecionar os clientes
for cliente in clientes:
    if (not(cliente['casado'])):
        selecionados.append(cliente)
selecionados<jupyter_output><empty_output><jupyter_text>Agora vamos reimaginar essa mesma estrutura, só que utilizando uma **list comprehension** para selecionar os nossos clientes:<jupyter_code>selecionados = [ cliente for cliente in clientes if not(cliente['casado']) ]
selecionados<jupyter_output><empty_output><jupyter_text>Observe que utilizando somente uma linha conseguimos gerar a lista de clientes desejados.Utilizando as **list comprehensions**, podemos gerar todo e qualquer tipo de lista:<jupyter_code># Gerando lista de numeros pares
pares = [ numero for numero in range(10) if numero % 2 == 0 ]
pares
# Gerando lista de numeros pares
impares = [ numero for numero in range(10) if numero % 2 != 0 ]
impares
# Gerando valores da potencia com base 2
base_2 = [ 2 ** numero for numero in range(10) ]
base_2<jupyter_output><empty_output><jupyter_text>Além disso, conseguimos concatenar *ifs* e criar mais de uma condição de teste:<jupyter_code># Criando uma lista com os números de 0 a 20 que sejam
# Múltiplos de 2 E de 5 ao mesmo tempo
multiplos = [ numero for numero in range(1, 21) if numero % 2 == 0 if numero % 5 == 0 ]
multiplos<jupyter_output><empty_output><jupyter_text>Podemos também utilizar estruturas como `if .. else` utilizando o seguinte padrão:```python[ resultado_if if expr_condicional else resultado_else for item in iteravel ]```Por exemplo, vamos criar uma **list comprehension** que crie uma lista onde cada elemento dirá se um um determinado número é par ou ímpar:<jupyter_code># Cria uma lista com dizendo se o valor é par ou ímpar
par_impar = [ 
    
    f'{numero} é par' 
    if numero % 2 == 0 
    else f'{numero} é ímpar' 
    for numero in range(1, 11) 
]


par_impar
# Cria uma lista com dizendo se o valor é par ou ímpar
par_impar = [ 
    f'{numero} é par' 
    if numero % 2 == 0 
    else f'{numero} é ímpar' 
    for numero in range(1, 11) 
]


par_impar
pares = []
impares = []

# Cria uma lista com dizendo se o valor é par ou ímpar
par_impar = [ 
    pares.append(numero)
    if numero % 2 == 0 
    else impares.append(numero)
    for numero in range(1, 11) 
]


print(par_impar)
print(pares)
print(impares)<jupyter_output>[None, None, None, None, None, None, None, None, None, None]
[2, 4, 6, 8, 10]
[1, 3, 5, 7, 9]<jupyter_text>Podemos também gerar **list comprehensions** aninhadas, ou seja, uma dentro da outra! Para criarmos uma **list comprehension** aninhanda, utilizamos a seguinte regra:```python[ item_selecionado for variavel_for_externo in iteravel_externo for variavel_for_interna in iteravel_intero ]```Essa list Comprehension é equivalente ao seguinte bloco de código:```pythonfor variavel_for_externo in iteravel_externo:    for variavel_for_interno in iteravel_interno:        item_selecionado```Para exemplificar, imagine a seguinte matriz:```pythonmatrix = [    [100, 150, 125],     [200, 150, 100],     [200, 100, 250],]```Cada linha dessa matriz representa os últimos salários de cada um dos colaboradores de uma determinada empresa. Nosso chefe nos pediu para que gerássemos uma única lista com todos os salaários. Logo, o resultado deve ser o seguinte:```pythonmatrix = [ 100, 150, 125, 200, 150, 100, 200, 100, 250 ]```Uma maneira de fazer isso é utilizando estruturas *for* aninhadas:<jupyter_code># Matriz normal
matrix = [
    [100, 150, 125], # Salários do Funcionário 1
    [200, 150, 100], # Salários do Funcionário 2
    [200, 100, 250], # Salários do Funcionário 3
]
# Matriz das médias
salaries_matrix = []

# Já todos os salários de um funcionário
# é representado por uma linha da Matriz,
# Vamos percorrer linha a linha da matriz
for salarios in matrix:
    
    # Percorre todos os salários de cada
    # funcionário.
    for salario in salarios:
        
        # Seleciona cada um dos salários e
        # Adiciona na matriz de salários
        salaries_matrix.append(salario)
salaries_matrix<jupyter_output><empty_output><jupyter_text>Outra maneira é utilizando **list comprehensions** aninhadas:<jupyter_code>salaries_matrix = [ salario for salarios in matrix for salario in salarios ]
salaries_matrix<jupyter_output><empty_output><jupyter_text>Da mesma forma, podemos adicionar condicionais em nossas **list comprehensions** aninhadas. Imagine agora que desejamos selecionar somente os salários que forem maiores que R$ 150,00:<jupyter_code>salaries_matrix = [ salario for salarios in matrix for salario in salarios if salario > 150 ]
salaries_matrix<jupyter_output><empty_output><jupyter_text>**List Comprehensions** são ferramentas muito utilizadas dentro da comunidade Python, e suas principais funções são:* Selecionar de forma simples itens de uma lista* Criar listas com condicionais simplesAlém disso, **list comprehensions** são mais performáticas que estruturas **for** e "mais fáceis" de manter.  Exemplos  Utilizando List Comprehension, descubra os índices dos elementos diferentes de zero da lista abaixo. ```pythonlista = [1, 3, 0, 3, 0, 3, 2, 0, 1, 0]```  Resolução<jupyter_code># Resolução Aqui
# [0, 1, 3, 5, 6, 8]<jupyter_output><empty_output><jupyter_text>Utilizando List Comprehension, crie uma matriz identidade 5 x 5  Exemplo da Resposta (Saída):```python[    [1, 0, 0, 0, 0],    [0, 1, 0, 0, 0],    [0, 0, 1, 0, 0],    [0, 0, 0, 1, 0],    [0, 0, 0, 0, 1]]  ```  Resolução<jupyter_code># Resolução Aqui

# [
#   [1, 0, 0, 0, 0],
#   [0, 1, 0, 0, 0],
#   [0, 0, 1, 0, 0],
#   [0, 0, 0, 1, 0],
#   [0, 0, 0, 0, 1]
# ]<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1101180624517268, 'fraction_numerical': 0.03159365920041193, 'mean_word_length': 4.050148588410104, 'pattern_counts': {'":': 0, '<': 146, '<?xml version=': 0, '>': 147, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 25, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '37', 'dir': 'jupyter-structured-clean-dedup', 'id': '178492', 'license': 'MIT', 'n_tokens_mistral': 10749, 'n_tokens_neox': 10034, 'n_words': 3688, 'path': '01-Fundamentos-Programacao-DS/Scripts/Mod-02/aula_07_pt_2/aula_07.ipynb', 'repo_name': 'fgandrade/Estudos-Comunidade-DS', 'size': '27311'}