Chapter 2 - An array of sequences

Built-in sequences

  • Container sequências: armazena referências para os objetos que contém (aceita diferentes tipos)

    • list, tuple and collections.deque can hold items of different types.

  • Flat sequências:

    • str, bytes, bytearray, memoryview and array.array hold items of one type.

Mutabilidade

  • Mutable sequências

    • list, bytearray, array.array, collections.deque and memoryview

  • Immutable sequências

    • tuple, str and byte

List Comprehensions in Python (aka listcomps)

Often seen as a part of functional programming in Python, list comprehensions allow you to create lists with a for loop with less code.

normal way

my_list = []
for x in range(10):
  my_list.append(x * 2)
print(my_list)

listcomps way

comp_list = [x * 2 for x in range(10)]
print(comp_list)

Listcomps x map and filter

symbols = '$¢£¥€¤'
beyond_ascii = [ord(s) for s in symbols if ord(s) > 127]
beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols)))

Iterator protocol is implemented whenever you iterate over a sequence of data. For example, when you use a for loop the following is happening on a background:

  • first iter()method is called on the object to converts it to an iterator object.

  • next() method is called on the iterator object to get the next element of the sequence.

  • StopIteration exception is raised when there are no elements left to call.

O que são generators?

Generators são uma forma simples de criarmos iteradores. Ele irá retornar um objeto (iterador) para que nós possamos iterar sobre este objeto (um valor de cada vez).

# generator expressions
def generator():
    n = 1
    print("Essa  uma funcao Generator")
    print(n)
    yield n

    n += 1
    print(n)
    yield n

    n += 1
    print(n)
    yield n


a = generator()

next(a)
# 1

next(a)
# 2

next(a)
# 3

next(a)
# StopIteration - ERROR

É muito simples criar uma função Generator, mas existem algumas peculiaridades. Por exemplo, nós usamos a declaração yield ao invés de return. Se a função contém ao menos uma declaração yield então ela se torna uma função Generator.

  • O yield pode ser lido como um pause, que retorna um objeto do tipo generator.

  • O objeto Generator só pode ser iterado uma única vez.

Generator expressions

As Generators Expressions facilitam à criação de Generators. Assim como uma função lambda cria uma função anônima, uma Generator Expression cria uma função Generator anônima. A sintaxe é bem parecida com as famosas List Comprehensions com o pequeno detalhe de que os colchetes [ ] são subsituídos pelos parênteses ().

O padrão da maioria dessas expressões é algo dessa forma:

genexpr = (expression for item in collection)

A expressão acima corresponde a função abaixo:

def generator():
    for item in collection:
        yield expression

Tuples

  • listas imutáveis

  • armazenar registros

Tuples para agrupar dados

julia = ("Julia", "Roberts", 1967, "Duplicity", 2009, "Actress", "Atlanta, Georgia")

Tuple assignment and unpacking (Atribuição múltipla)

O poder das tuplas está no unpacking mechanism. Unpacking é a separação de um tuple (ou qualquer sequência) em várias variáveis. Cada variável recebe um valor do tuple.

(name, surname, b_year, movie, m_year, profession, b_place) = julia

Parallel Assignment

>>> lax_coordinates = (33.9425, -118.408056)
>>> latitude, longitude = lax_coordinates # tuple unpacking 
>>> latitude
33.9425
>>> longitude
-118.408056

Trocar os valores de 2 variáveis

Nesse caso, não precisa de um terceira variável temporária.

  • Antes:

    temp = a
    a = b
    b = temp
  • Tuplas:

name, surname = surname, name

Podemos usar (*) to unpack tuple

Função retorna (2 , 4) um par de números que consiste em seu quociente e o resto da divisão.

divmod(20, 8)
# (2, 4)

Então, podemos tentar utilizar a tupla como parâmetro.

# criamos a tupla t
t = (20, 8)
divmod(t)
#TypeError: divmod expected 2 arguments, got 1

No entanto, a função divmod() recebe 2 parâmetros e não apenas 1. Podemos desestrutura a tupla, utilizando simplesmente o caractere (*).

divmod(*t)

Além disso, já podemos usar o assingment:

quotient, remainder = divmod(*t)
print(quotient, remainder)

Namedtuple

Nesse caso, temos que passar para a função 2 parâmetros:

  • nome da classe

  • lista de campos

City = namedtuple('City', 'name country population coordinates')

tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667))

tokyo.population

novos atributos

  • ._fields: exibe o nome dos campos da classe

  • ._make(): pode instanciar uma namedtuple a partir de outra namedtuple.

>>> LatLong = namedtuple('LatLong', 'lat long')
>>> delhi_data = ('Delhi NCR', 'IN', 21.935, LatLong(28.613889, 77.208889))
>>> delhi = City._make(delhi_data)
>>> delhi
city(name='Delhi NCR', country='IN', population=21.935, coordinates=LatLong(lat=28.613889, long=77.208889))
  • ._asdict(): retorna collections.OrderedDict exibindo também os campos da named tuple.

>>> delhi._asdict()
OrderedDict([('name', 'Delhi NCR'), ('country', 'IN'), ('population', 21.935), ('coordinates', LatLong(lat=28.613889, long=77.208889))])

Uso do * para capturar itens excedentes

>>> a, b, *rest = range(15)
>>> a, b, rest
(0, 1, [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])

>>> type(rest)
<class 'list'>
>>> type(a)
<class 'int'>

args e *kwargs

Antes de mais nada, eles não precisam se chamar args ou kwargs, mas precisam ter o e *, respectivamente! **Os nomes “args” são apenas uma convenção.

https://www.geeksforgeeks.org/args-kwargs-python/

*args exemplo

O interessante é que o *ags é lido como uma tupla, então podemos iterá-lo para pegar os valores.

# Python program to illustrate 
# *args for variable number of arguments 
def myFun(*args): 
    print(type(args))
    for arg in args: 
        print (arg) 

myFun('Hello', 'Welcome', 'to', 'GeeksforGeeks') 

# <class 'tuple'>
# Hello
# Welcome
# to
# GeeksforGeeks

**kwargs exemplo

A diferença aqui é que não passamos somente uma tupla, mas uma lista de chave-valores.Então, nesse caso temo um dicionário.

# Python program to illustrate   
# *kargs for variable number of keyword arguments 

def myFun(**kwargs):  
    print(type(kwargs))
    for key, value in kwargs.items(): 
        print ("%s == %s" %(key, value)) 

# Driver code 
myFun(first ='Geeks', mid ='for', last='Geeks')

# <class 'dict'>
# first == Geeks
# mid == for
# last == Geeks

Passar uma lista como parâmetro para uma função que recebe n valores

def myFun(arg1, arg2, arg3): 
    print("arg1:", arg1) 
    print("arg2:", arg2) 
    print("arg3:", arg3)

Podemos passar a lista como parâmetro para invocar a função. usando *

# Now we can use *args or **kwargs to 
# pass arguments to this function : 
args = ("Geeks", "for", "Geeks") 
myFun(*args)

Ou podemos passar uma lista chave-valor na qual as chaves devem ter o nome dos argumentos da função.

kwargs = {"arg1" : "Geeks", "arg2" : "for", "arg3" : "Geeks"} 
myFun(**kwargs)

Slicing

https://www.youtube.com/watch?v=ajrtAuDg3yw

a[start:stop:step]  # items start through start and skip numbers considering number passed on step
a[start:stop]  # items start through stop-1
a[start:]      # items start through the rest of the array
a[:stop]       # items from the beginning through stop-1
a[:]           # a copy of the whole array
list = ['Carla','Leonardo','Carlos']
#input
list[:2]
['Carla', 'Leonardo']

Multi-dimensional slicing and ellipsis

Assigning to slices

Podemos modificar (alterar valores, adicionar, substituir) sequências mutáveis utilizando slice.

l = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Substituímos o valores nos índices 2, 3 e 4 por apenas 2 valores, reduzindo o tamanho da lista.

l[2:5] = [20, 30]

len(l)
>>> 19
l[2:5] = [115,120,123,125]

len(l)
>>> 21

Operations + * with sequences

Concatenação de sequências

lista_1 = [1, 2, 3]
lista_2 = [4, 5, 6]

lista_1 + lista_2

>>> [1, 2, 3, 4, 5, 6]

Repetição

lista_1 * 2
>>> [1, 2, 3, 1, 2, 3]

Nessas operações novos objetos são criados.

my_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
id(my_list)
>>> 4561859520

my_list = my_list * 2
id(my_list)
>>> 4562997328

my_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
id(my_list)
>>> 4561859520

Building lists of lists

Correto

Uma lista que contém 3 listas de 4 itens cada

board = [['_'] * 4 for i in range(3)]

board
>>> [['_', '_', '_','_'], ['_', '_', '_','_'], ['_', '_', '_','_']] 

board[1][2] = 'X'

board
>>> [['_', '_', '_','_'], ['_', '_', 'X','_'], ['_', '_', '_','_']]

Errado

Uma lista com 3 referêncis para mesma lista, o que não era nossa intenção.

weird_board = [['_'] * 3] * 3
weird_board
>>> [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']] 
weird_board[1][2] = 'O'
weird_board
>>> [['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']]

Operations += *= with sequences

  • += invoca o método especial __iadd__

  • *= invoca o método especial __imul__

Qual a diferença das operações em listas mutáveis e imutáveis?

Então, várias concatenações em listas imutáveis é ineficiente porque ao invés de apenas adicionar itens na lista, um novo objeto é criado com uma cópia do original.

A+= (corner case)

  • o interpretador consegue alterar o valor da lista t[2], pois é uma lista

  • mas não consegue adicionar t[2] na lista t, pois t é uma tupla.

Dá para ver melhor com bytecode:

t=(1,2,[30,40])

type(t)
# <class 'tuple'>

type(t[2])
# <class 'list'>

type(t[0])
# <class 'int'>

Alguns aprendizados a partir disso:

  • não inserir items mutáveis dentro de uma tupla

  • += não é uma operação atômica

  • entender melhor o bytecode pode ser algo útil

list.sort

O método list.sort() ordena a lista in-place - não cria uma cópia do objeto. Tanto que não podemos atribuí-lo para outra variável:

t = [10, 15, 20, 5]

t.sort()

x = t.sort()

t
# [5, 10, 15, 20]

type(x)
# <class 'NoneType'>

Convenção do Python: Funções ou métodos que alteram objetos in-place sempre retornam None.

sorted function

Algoritmo de ordenação Timsort baseado no Insertion Sort e Merge sort.

A built-in functionsortedcria uma nova lista e a retorna - possui 2 argumentos possíveis (reversed ekey).

t = [10, 15, 20, 5]

sorted(t)
# [5, 10, 15, 20]

x = sorted(t)
x
# [5, 10, 15, 20]

Bisect - binary search method in Python

Pode ser usado para buscar um elemento em uma lista.

Ex 1: nesse caso, a função bisect_left retorna o índice da primeira ocorrência do elemento 285.

import bisect

a = [-14,-10,2,108,108,285,285,285,401]

print(bisect.bisect_left(a, 285))

# 5

Ex 2: nesse caso, a função bisect_left retorna o índice da última ocorrência do elemento 285.

print(bisect.bisect_right(a, 285))

# 5

lo/hi arguments

bisect.insort

insort(seq, item) - insere um item na sequência de forma a mantê-la ordenada de forma crescente.

Arrays

  • mais eficiente para listas com um único tipo

array(data type, value list)

  • append

  • insert

  • pop

  • remove

  • index

  • reverse

array.tofile()

Memory views

Last updated

Was this helpful?