Você está na página 1de 142

♥ Python e algoritmos 2.

0 ♥
Um guia para aprender a voar
Marina Wahl
marina.w4hl@gmail.com
31 de agosto de 2014

“Não há nada a temer, a não ser o próprio medo.


Isso se chama recursão, e isso levaria você a
medo infinito. "
Olá humano! Bem-vindo à segunda edição do meu livro sobre (voar com) Python. Esta
nova revisão foi escrita durante meu (incrível) período no Hacker Escola. Ele contém
algumas melhorias e algumas atualizações. Você pode perceber uma grande diferença nos
últimos capítulos, sobre gráficos e árvores. Espero que você aproveite e divirta-se!
Marina, Hacker School, Nova Iorque Verão / 2014
Olá humano! Bem-vindo ao meu livro sobre Python e algoritmos! Se você é lendo isso,
você provavelmente concorda comigo que esses dois podem ser muito divertidos juntos
(ou você pode estar perdido, e, neste caso, sugiro que você dê uma tente mesmo assim!).
Além disso, muitos dos exemplos mostrados aqui estão disponíveis no meu repositório
git, junto com vários outros exemplos (mais avançados) para estruturas de dados
abstratas, árvores, gráficos e soluções para o Projeto Euler e o site Topcoder. Não se
esqueça de vê-los!
Este texto foi escrito puramente por diversão (eu sei, eu sei, essa é uma definição da
palavra diversão ...) sem pretensões de nada grande, então perdoe-me (ou melhor, me
avise) se encontrar algum erro de digitação ou erro. Não sou cientista da computação por
formação (na verdade sou um quase-eu-juro-é-fechar-doutorado em Física) então isso
talvez torne as coisas um um pouco menos usual (ou arriscado?). Espero que você se
divirta!
Marina, Stony Brook, Nova Iorque
Verão 2013
Conteúdo
Eu pego suas asas! Python é um propósito geral, linguagem de programação de alto nível,
que suporta paradigmas de programação simples, incluindo orientação a objetos,
programação ou procedimentos imperativos e funcionais estilos racionais. Na primeira
parte deste livro, aprenderemos todas essas palavras chiques.
1 Oh Hay, Números!
1.1 Inteiros. . . . . . . . . . . . . ......... . . .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.2 Flutuadores. . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.3 Números complexos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.4 O módulo de fração. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.5 O módulo decimal. . . . . . . . . . . . . . . . . . . . . . . 15
1.6 Outras representações. . . . . . . . . . . . . . . . . . . . . . 16
1.7 Fazendo alguma matemática. . . . . . . . . . . . . . . . . . . . . . . . . 16
1.8 O pacote NumPy. . . . . . . . . . . . . . . . . . . . . . . 23
2 tipos de sequência incorporados . . . . . . . . . . . .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.1 Strings. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.2 Tuplas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
2.3 Listas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38.
2.4 Bytes e matrizes de bytes. . . . . . . . . . . . . . . . . . . . . . 46.
2.5 Exemplos adicionais. . . . . . . . . . . . . . . . . . . . . . . . . 47
3 Estruturas de dados de coleta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.1 Conjuntos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.2 Dicionários. . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.3 Tipos de dados da coleção do Python. . . . . . . . . . . . . . . . 61
3.4 Exemplos adicionais. . . . . . . . . . . . . . . . . . . . . . . . . 65
4 Estrutura e módulos do Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
4.1 Módulos em Python. . . . . . . . . . . . . . . . . . . . . . . . 71
4.2 Fluxo de Controle. . . . . . . . . . . . . . . . . . . . . . . . . . . 75
4.3 Manuseio de arquivos. . . . . . . . . . . . . . . . . . . . . . . . . . . 81
4.4 Tratamento de erros no Python. . . . . . . . . . . . . . . . . . . . 88
5 Projeto Orientado a Objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
5.1 Classes e Objetos. . . . . . . . . . . . . . . . . . . . . . . . 94
5.2 Princípios de POO. . . . . . . . . . . . . . . . . . . . . . . . . 95
5.3 Padrões de design do Python. . . . . . . . . . . . . . . . . . . . . 98
6 Tópicos avançados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
6.1 Multiprocessamento e rosqueamento. . . . . . . . . . . . . . . . . 103
6.2 Boas práticas. . . . . . . . . . . . . . . . . . . . . . . . . . 105
6.3 Teste de Unidade. . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
II Algoritmos são divertidos! É hora de adicionar um pouco de molho em nosso vôo!
Nesta segunda parte, aprenderemos como para tornar o computador nosso incrível espaço
navio! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
7 Estruturas de dados abstratas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
7.1 Pilhas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
7.2 Filas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
7.3 Filas prioritárias e pilhas. . . . . . . . . . . . . . . . . . . . 126
7.4 Listas vinculadas. . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
7.5 Tabelas de Hash. . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
7.6 Exercícios adicionais. . . . . . . . . . . . . . . . . . . . . . . 140
8 Análise Assintótica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
8.1 Classes de complexidade. . . . . . . . . . . . . . . . . . . . . . . . 163
8.2 Recursão. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
8.3 Tempo de execução em funções. . . . . . . . . . . . . . . . . . . . . . . 166
9 Classificação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
9.1 Classificação quadrática. . . . . . . . . . . . . . . . . . . . . . . . . . 169
9.2 Classificação linear. . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
9.3 Classificação linear. . . . . . . . . . . . . . . . . . . . . . . . . . . 173
9.4 Comparação entre métodos de classificação. . . . . . . . . . . . . 183
9.5 Exercícios adicionais. . . . . . . . . . . . . . . . . . . . . . . 184
10 Pesquisando . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
10.1 Matrizes não ordenadas. . . . . . . . . . . . . . . . . . . . . . . . . . 187
10.1.1 Pesquisa sequencial. . . . . . . . . . . . . . . . . . . . . 187
10.1.2 Estatísticas de seleção rápida e ordem . . . . . . . . . . . . 189
10.2 Matrizes ordenadas. . . . . . . . . . . . . . . . . . . . . . . . . . . 191
10.2.1 Pesquisa binária. . . . . . . . . . . . . . . . . . . . . . . 191
10.3 Exercícios adicionais. . . . . . . . . . . . . . . . . . . . . . . 193
11 Programação dinâmica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
11.1 Memoização. . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
11.2 Exercícios adicionais. . . . . . . . . . . . . . . . . . . . . . . 201
III Escalada é tão na semana passada! Prefiro voar, não é? Hora de iniciar nossos motores
para alcançar os objetos mais divertidos do mundo dos algoritmos. Rapidez
até belos gráficos e árvores! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
12 Introdução aos gráficos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
12.1 Definições básicas . . . . . . . . . . . . . . . . . . . . . . . . . 207
12.2 A função de vizinhança. . . . . . . . . . . . . . . . . . . 209
12.3 Conexão com árvores. . . . . . . . . . . . . . . . . . . . . . . . 212
13 Árvores Binárias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
13.1 Árvores de pesquisa binária. . . . . . . . . . . . . . . . . . . . . . . . 224
13.2 BSTs com auto-equilíbrio. . . . . . . . . . . . . . . . . . . . . . . 226
14 Traversals e problemas em gráficos e árvores . . . . . . . . . . . . . . . . . . . . . . . . 229
14.1 Pesquisa em profundidade. . . . . . . . . . . . . . . . . . . . . . . . 229
14.2 Pesquisa pela primeira vez. . . . . . . . . . . . . . . . . . . . . . . 231
14.3 Representando árvores atravessadas. . . . . . . . . . . . . . . . . . . 231
14.4 Exercícios adicionais. . . . . . . . . . . . . . . . . . . . . . . 236
Parte I
Pegue suas asas! Python é um de uso geral, linguagem de programação de alto nível, que
suporta programação múltipla paradigmas, incluindo orientada a objetos, imperativa e
programação funcional ou estilos processuais. Na primeira parte deste livro,
aprenderemos todas essas palavras chiques.
Capítulo 1

Oh Hay, Números!
Quando você aprende uma nova linguagem, a primeira coisa que você costuma fazer é
gritar, Olá Mundo! Porque todos nós precisamos ser notados. A segunda coisa que
fazemos é verifique se a matemática faz sentido, brincando com números e aritmética
operações. Os computadores, no entanto, são muito mais hamletianos. Os crentes binários
têm um argumento: por que desperdiçar todos esses bytes se podemos apenas afirmar que
as coisas são (True) ou não (False)? Além disso, como os computadores se preocupam
com a igualdade de seres extraterrestres, eles também permitem representar coisas de
outras bases, como hexadecimal e octal.

1.1 Inteiros
Python representa números inteiros (números inteiros positivos e negativos) usando o
método tipo int (imutável). Para objetos imutáveis, não há diferença entre uma variável e
uma referência a objeto.
O tamanho dos números inteiros do Python é limitado apenas pela memória da máquina,
não por um número fixo de bytes (o intervalo depende do compilador C ou Java que o
Python foi construído com). Normalmente, números inteiros simples têm pelo menos 32
bits (4 bytes)1. Para ver quantos bytes um número inteiro precisa ser representado,
iniciando no Python 3.1, o método int.bit length () está disponível:
1
Para ter uma idéia de quanto isso significa: 1 K de memória em disco possui 1024 × 8
bits = 210 bytes.

CAPÍTULO 1. OH HAY, NÚMEROS!

>>> (999) .bit_length ()


10

Para converter uma string em um número inteiro (em alguma base) ou alterar a base de
um inteiro, usamos int (s, base):
>>> s = '11'
>>> d = int (s)
>>> imprimir (d)
11
>>> b = int (s, 2)
>>> impressão (b)
3

O argumento base opcional deve ser um número inteiro entre 2 e 36 (inclusive). Se a


sequência não puder ser representada como o número inteiro na base escolhida, esse
método gera uma exceção ValueError. Por exemplo, isso vai acontecer se tentarmos
obter uma representação binária com s = '12 '.
1.2 Flutuadores
Os números com uma parte fracionária são representados pelo tipo flutuante (imutável).
Quando usamos precisão única, uma camada de 32 bits é representada por: 1 bit para sinal
(negativo é 1, positivo é 0) + 23 bits para os dígitos significativos (ou mantissa) + 8 bits
para o expoente. Em um sistema de computador típico, um número de ponto binário de
precisão dupla (64 bits) possui um coeficiente de 53 bits, um expoente de 11 bits e um bit
de sinal. Além disso, o expoente geralmente é representado usando a notação tendenciosa,
na qual você adiciona o número 127 ao valor original2.

Comparando Floats
Nunca devemos comparar carros alegóricos pela igualdade nem subtraí-los. O motivo
para isso é que os Floats são representados em frações binárias. Existem vários

-- --- --- ---


2
a polarização é feita porque os expoentes precisam ser assinados com valores para poder
representar valores minúsculos e enormes, mas a representação usual dificulta a
comparação. Para resolver isso problema, o expoente é ajustado para estar dentro de um
intervalo não assinado adequado para comparação.
Saiba mais: http://www.doc.ic.ac.uk/ eedwards / compsys / float

1.2 FLOATS
Números exatos em uma base decimal, mas não exatos em uma base binária (por exemplo, o
decimal 0.1). Os testes de igualdade devem ser feitos em termos de alguma precisão
predefinida. Por exemplo, poderíamos empregar a mesma abordagem como o módulo mais
unívoco do Python: assert AlmostEqual:

>>> def a (x, y, lugares = 7):


... round de retorno ( abs (xy), lugares) == 0

Os números flutuantes também podem ser comparados por seus padrões de bits na
memória. Primeiro, precisamos lidar com a comparação de sinais separadamente: se os dois
números forem negativo, podemos compará-los sacudindo seus sinais, retornando o oposto
responda. Os padrões com o mesmo expoente são comparados de acordo com suas mantissas.

Métodos para floats e números inteiros


No Python, o operador de divisão / sempre retorna um float. Uma divisão de piso
(truncamento) é feito com o operador //. Uma operação do módulo (resto) é dado pelo
operador %. Além disso, o método divmod (x, y) retorna o quociente e o restante ao
dividir x por y:
>>> divmod (45,6)
(7, 3)
O método round (x, n) retorna x arredondado para n dígitos inteiros se n for um
int negativo ou retorna x arredondado para n casas decimais se n for um int positivo.
O valor retornado tem o mesmo tipo que x:
>>> rodada (100,96, -2)
100,0
>>> rodada (100.96,2)
100,96
O método como número inteiro () fornece o número inteiro representativo fracionário
ção de um flutuador:
>>> 2.75.as_integer_ratio ()
(11, 4)

1.3 Números complexos


O tipo de dados complexo é um tipo (imutável) que contém um par de flutuadores:
z = 3 + 4j. Possui métodos como: z.real, z.imag e z.conjugate ().
Números complexos são importados do módulo cmath, que fornece versões numéricas
complexas da maioria das funções trigonométricas e logarítmicas funções que estão no
módulo matemático, além de algumas funções complexas específicas de número opções
como: cmath.phase (), cmath.polar (), cmath.rect (), cmath.pi, e cmath.e.

1.4 O módulo de fração


O Python possui o módulo de fração para lidar com partes de uma fração. O fragmento a
seguir mostra os métodos básicos deste módulo:3

[general_problems/numbers/testing_floats.py]
from fractions import Fraction
def rounding_floats(number1, places):
''' some operations with float()'''
return round(number1, places)
def float_to_fractions(number):
return Fraction(*number.as_integer_ratio())
def get_denominator(number1, number2):
a = Fraction(number1, number2)
return a.denominator
def get_numerator(number1, number2):
a = Fraction(number1, number2)
return a.numerator
3
Todos os códigos mostrados neste livro estão na estrutura de diretórios projetada no git
repositório: https://github.com/mariwahl/Python-and-Algorithms-and-Data-Structures.
Observe que as diretrizes do PEP 8 (Proposta de aprimoramento do Python) recomendam
quatro espaços por nível de recuo e apenas espaços (sem guias). Isso não é evidente aqui
por causa de a maneira como o látex formata o texto.

def test_testing_floats (module_name = 'este módulo' ):


number1 = 1,25
number2 = 1
number3 = -1
number4 = 5/4
number6 = 6
assert (rounding_floats (número1, número2) == 1.2)
assert (rounding_floats (número1 * 10, número3) == 10)
assert (float_to_fractions (número1) == número4)
assert (get_denominator (número2, número6) == número6)
assert (get_numerator (número2, número6) == número2)
s = 'Testes em {name} têm {con}!'
print ( formato . s (nome = nome do módulo, con = 'passado' ))
se __name__ == '__main__' :
test_testing_floats ()
1.5 O módulo decimal
Nos casos em que precisamos de números decimais de ponto flutuante exatos,
Python inclui um tipo de flutuação adicional (imutável), o decimal.Decimal. O método
utiliza um número inteiro ou uma sequência como argumento (e a partir de o Python 3.1
também flutua, com a função decimal.Decimal.from float ()).
Essa é uma alternativa eficiente quando não queremos lidar com o arredondamento,
problemas de igualdade e subtração que flutuam vêm com:
>>> soma (0,1 para i no intervalo (10)) == 1,0
Falso
>>> da importação decimal Decimal
>>> sum (decimal ( "0,1" ) for i in range (10)) == decimal ( "1,0" )
True

Embora os módulos matemático e cmath não sejam adequados para o decimal módulo,
suas funções internas, como decimal.Decimal.exp (x), são suficientes para a maioria dos
casos.

1.6. Outras representações


O método bin (i) retorna a representação binária do int i como um corda:
>>> compartimento (999)
'0b1111100111'
O método hex (i) retorna a representação hexadecimal de i como um corda:
>>> hex (999)
'0x3e7'
O método oct (i) retorna a representação octal de i como uma string:
>>> out (999)
'0o1747'

1.7. Fazendo alguma matemática


Convertendo entre diferentes bases
Podemos escrever nossas próprias funções para mudar bases em números. O trecho abaixo
converte um número em qualquer base menor que 10 na base decimal:

[general_problems/numbers/convert_to_decimal.py]
def convert_to_decimal(number, base):
multiplier, result = 1, 0
while number > 0:
result += number%10*multiplier
multiplier *= base
number = number//10
return result
def test_convert_to_decimal():
number, base = 1001, 2
assert(convert_to_decimal(number, base) == 9)
print('Tests passed!')
if __name__ == '__main__':
test_convert_to_decimal()

Ao trocar todas as ocorrências de 10 por qualquer outra base em nossa anterior


Por exemplo, podemos criar uma função que converte de um número decimal em
outro número (2 ≤ base ≤ 10):

[general_problems/numbers/convert_from_decimal.py]

def convert_from_decimal(number, base):


multiplier, result = 1, 0
while number > 0:
result += number%base*multiplier
multiplier *= 10
number = number//base
return result

def test_convert_from_decimal():
number, base = 9, 2
assert(convert_from_decimal(number, base) == 1001)
print('Tests passed!')

if __name__ == '__main__':
test_convert_from_decimal()

Se a base estiver acima de 10, devemos usar caracteres não numéricos para representar
reenviar dígitos maiores. Podemos deixar 'A' representar 10, 'B' representar 11 e assim por
diante.
O código a seguir converterá um número de uma base decimal para qualquer outro base (até
20):

[general_problems/numbers/convert_from_decimal_larger_bases.py]

def convert_from_decimal_larger_bases(number, base):


strings = "0123456789ABCDEFGHIJ"
result = ""
while number > 0:
digit = number%base
result = strings[digit] + result
number = number//base
return result

def test_convert_from_decimal_larger_bases():
number, base = 31, 16
assert(convert_from_decimal_larger_bases(number, base) == '1F')
print('Tests passed!')
if _ _name_ _ == '_ _main_ _':
test_convert_from_decimal_larger_bases()

Por fim, o snippet abaixo é um módulo de conversão de base muito geral, usando um método
recursivo:
[general_problems/numbers/convert_dec_to_any_base_rec.py]

def convert_dec_to_any_base_rec(number, base):


''' convert an integer to a string in any base'''
convertString = '012345679ABCDEF'
if number < base: return convertString[number]
else:
return convert_dec_to_any_base_rec(number//base, base) +
convertString[number%base]
def test_convert_dec_to_any_base_rec(module_name='this module'):
number = 9
base = 2
assert(convert_dec_to_any_base_rec(number, base) == '1001')

s = 'Tests in {name} have {con}!'


print(s.format(name=module_name, con='passed'))
if _ _name_ _ == '_ _main_ _':
test_convert_dec_to_any_base_rec()

Maior Divisor Comum


O módulo a seguir calcula o maior divisor comum (MDC) entre dois inteiros dados:

[general_problems/numbers/finding_gcd.py]

def finding_gcd(a, b):


'' 'implementa o maior algoritmo divisor comum' ''
while(b != 0):
result = b
a, b = b, a % b
return result
def test_finding_gcd():
number1 = 21
number2 = 12
assert(finding_gcd(number1, number2) == 3)
print('Tests passed!')
if __name_ _ == '_ _main_ _':
test_finding_gcd()

O Módulo Aleatório
O snippet a seguir executa alguns testes no módulo aleatório do Python:

[general_problems/numbers/testing_random.py]

import random

def testing_random():
''' 'testando o módulo random'''
values = [1, 2, 3, 4]
print(random.choice(values))
print(random.choice(values))
print(random.choice(values))
print(random.sample(values, 2))
print(random.sample(values, 3))

''' shuffle in place '''


random.shuffle(values)
print(values)
''' create random integers '''
print(random.randint(0,10))
print(random.randint(0,10))
if _ _name_ _ == '_ _main_ _':
testing_random()

Sequências de Fibonacci
O módulo abaixo mostra como encontrar o enésimo número em uma sequência de Fibonacci de
três maneiras diferentes: (a) com um tempo de execução recursivo de O (2n); b) com uma
iteração tempo de execução O (n2) positivo ; e (c) usando uma fórmula que fornece um tempo
de execução O (1) (mas que não é preciso depois em torno do 70th elemento):

[general_problems/numbers/find_fibonacci_seq.py]
import math
def find_fibonacci_seq_iter(n):
if n < 2: return n
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a

def find_fibonacci_seq_rec(n):
if n < 2: return n
return find_fibonacci_seq_rec(n - 1) + find_fibonacci_seq_rec(n - 2)

def find_fibonacci_seq_form(n):
sq5 = math.sqrt(5)
phi = (1 + sq5) / 2
return int(math.floor(phi ** n / sq5))
def test_find_fib():
n = 10
assert(find_fibonacci_seq_rec(n) == 55)
assert(find_fibonacci_seq_iter(n) == 55)
assert(find_fibonacci_seq_form(n) == 55)
print('Tests passed!')
if _ _name__ == '__main__':
test_find_fib()

Primeiros
O programa a seguir descobre se um número é primo em três diferentes maneiras: (a)
usando força bruta; (b) rejeitar todos os candidatos até o quadrado raiz do número; e (c)
usando o teorema de Fermat com probabilística testes:
[general_problems/numbers/finding_if_prime.py]

import math
import random

def finding_prime(number):
num = abs(number)
if num < 4 : return True
for x in range(2, num):
if num % x = = 0:
return False
return True

def finding_prime_sqrt(number):
num = abs(number)
if num < 4 : return True
for x in range(2, int(math.sqrt(num)) + 1):
if number % x == 0:
return False
return True

def finding_prime_fermat(number):
if number <= 102:
for a in range(2, number):
if pow(a, number- 1, number) != 1:
return False
return True
else:
for i in range(100):
a = random.randint(2, number - 1)
if pow(a, number - 1, number) != 1:
return False
return True

def test_finding_prime():
number1 = 17
number2 = 20
assert(finding_prime(number1) == True)
assert(finding_prime(number2) == False)
assert(finding_prime_sqrt(number1) == True)
assert(finding_prime_sqrt(number2) == False)
assert(finding_prime_fermat(number1) == True)
assert(finding_prime_fermat(number2) == False)
print('Tests passed!')
if _ _name_ _ = = '_ _main_ _':
test_finding_prime()

O programa a seguir usa o módulo aleatório do Python para gerar n-bit números primos:
[general_problems/numbers/generate_prime.py]

import math
import random
import sys
from finding_prime import finding_prime_sqrt

def generate_prime(number=3):
while 1:
p = random.randint(pow(2, number-2), pow(2, number-1)-1)
p=2*p+1
if finding_prime_sqrt(p):
return p
if _ _name_ _ = = '_ _main_ _':
if len(sys.argv) < 2:
print ("Usage: generate_prime.py number")
sys.exit()
else:
number = int(sys.argv[1])
print(generate_prime(number))

1.8 O pacote NumPy


O pacote NumPy4 é uma extensão da linguagem de programação Python, adicionando
suporte para matrizes e matrizes multidimensionais grandes, juntamente com um grande
biblioteca de funções matemáticas de alto nível para operar nessas matrizes.
Matrizes no NumPy podem ter qualquer dimensão arbitrária. Eles podem ser generosos
de uma lista ou de uma tupla com o método array, que transforma sequências de
sequências em matrizes bidimensionais:
>>> x = np.array( ((11,12,13), (21,22,23), (31,32,33)) )
>>> print x
[[11 12 13]
[21 22 23]
[31 32 33]]
O atributo ndim nos diz o número de dimensões de uma matriz:
>>> x = matriz np (((11,12,13), (21,22,23)))
>>> x.ndim
2
4
http://www.numpy.org

Alguns outros exemplos interessantes são mostrados abaixo:

[general_problems/numbers/testing_numpy.py]
import numpy as np

def testing_numpy():
''' tests many features of numpy '''
ax = np.array([1,2,3])
ay = np.array([3,4,5])
print(ax)
print(ax*2)
print(ax+10)
print(np.sqrt(ax))
print(np.cos(ax))
print(ax-ay)
print(np.where(ax<2, ax, 10))
m = np.matrix([ax, ay, ax])
print(m)
print(m.T)
grid1 = np.zeros(shape=(10,10), dtype=float)
grid2 = np.ones(shape=(10,10), dtype=float)
print(grid1)
print(grid2)
print(grid1[1]+10)
print(grid2[:,2]*2)
if _ _name_ _ = = '_ _main_ _':
testing_numpy()

As matrizes NumPy também são muito mais eficientes do que as listas do Python, como
podemos veja nos testes de benchmark abaixo:

[general_problems/numbers/testing_numpy_speed.py]
import numpy
import time
def trad_version():
t1 = time.time()
X = range(10000000)
Y = range(10000000)
Z = []
for i in range(len(X)):
Z.append(X[i] + Y[i])
return time.time() - t1

def numpy_version():
t1 = time.time()
X = numpy.arange(10000000)
Y = numpy.arange(10000000)
Z=X+Y
return time.time() - t1

if _ _name_ _ == '_ _main_ _':


print(trad_version())
print(numpy_version())
'''
Results:
3.23564291
0.0714290142059
'''
Capítulo 2
Tipos de sequência incorporados
O próximo passo em nossos estudos é aprender como o Python representa dados de
sequência tipos. Um tipo de sequência é definido por ter as seguintes propriedades:
⋆ operador de associação (por exemplo, a capacidade de usar a palavra-chave em);
⋆ um método de tamanho (fornecido pelo método len (seq));
⋆ Propriedades de corte (por exemplo, seq [: - 1]); e
⋆ Iterabilidade (podemos iterar os dados dentro de loops).
O Python possui cinco tipos de sequência internos: strings, tuplas, listas, bytes matrizes e
bytes:1
>>> l = [ ]
>>> type (l)
< type 'list' >
>>> s = ''
>>> type (s)
< type 'str' >
>>> t = ()
>>> type (t)
< type 'tupla' >
>>> ba = bytearray (b ' ')
>>> type (ba)
< type 'bytearray' >
>>> b = bytes ([ ])
>>> type (byte)
< type 'type' >
1
Uma tupla nomeada também está disponível na biblioteca padrão, nas coleções pacote.

Mutabilidade
No último capítulo, aprendemos que números são tipos imutáveis em Python. Agora é
hora de aprender sobre objetos que são mutáveis. Em Pyhon, tupla, strings e bytes são
imutáveis, enquanto listas e matrizes de bytes são mutáveis. Tipos imutáveis são
geralmente mais eficientes que mutáveis. Além do que, além do mais, alguns tipos de
dados de coleta2 só pode ser indexado por tipos de dados imutáveis.
No Python, qualquer variável é uma referência a objeto, portanto, copiar objetos
mutáveis pode ser complicado. Quando você diz a = b, na verdade está apontando a para
onde b aponta para. Por esse motivo, é importante entender o conceito de profundidade
cópia:
Para fazer uma cópia profunda de uma lista:
>>> newList = myList[:]
>>> newList2 = list(myList2)
Para fazer uma cópia profunda de um conjunto (veremos no próximo capítulo), use:
>>> people = {"Buffy", "Angel", "Giles"}
>>> slayers = people.copy()
>>> slayers.discard("Giles")
>>> slayers.remove("Angel")
>>> slayers
{'Buffy'}
>>> people
{'Giles', 'Buffy', 'Angel'}
Para fazer uma cópia profunda de um dicionário (também no próximo capítulo), use o
seguinte:
>>> newDict = myDict.copy()

Para fazer uma cópia profunda de outro objeto, use o módulo de cópia:
>>> import copy
>>> newObj = copy.copy (myObj) # cópia rasa
>>> newObj2 = copy.deepcopy (myObj2) # cópia profunda
2
Os tipos de dados da coleção, como conjuntos e dicionários, são revisados no próximo
capítulo.

O operador de fatiar
Nos tipos de sequência do Python, o operador de fatiamento tem a seguinte sintaxe:
seq[start]
seq[start:end]
seq[start:end:step]

Se queremos começar a contar da direita, podemos representar o índice como negativo:

>>> word = "Let us kill some vampires!"


>>> palavra [-1]
'!'
>>> palavra [-2]
's'
>>> palavra [-2:]
's!
>>> palavra [: - 2]
'Let us kill some vampire'
>>> palavra [-0]
'L'

2.1 Strings
Python representa strings, ou seja, uma sequência de caracteres, usando o método
imutável tipo str. No Python, todo objeto tem duas formas de saída: enquanto as formas
de string são projetadas para serem legíveis por humanos, as formas representacionais são
projetadas para produz uma saída que, se alimentada a um interpretador Python, reproduz
a representação objeto representado. Quando escrevemos classes em Python, é importante
definir sua representação de string.

Strings Unicode
A codificação Unicode do Python é usada para incluir caracteres especiais na string (por
exemplo, espaço em branco). A partir do Python 3, todas as strings são Unicode, não apenas
bytes simples. Para criar uma string Unicode, usamos o prefixo 'u':
>>> Adeus \ u0020Mundo! '
'Adeus mundo !'
No exemplo acima, a sequência de escape indica o caractere Unicode com o valor ordinal
0x0020. Também é útil lembrar que, em geral as representações ASCII são dadas por apenas
8 bits, enquanto o Unicode representa precisa de 16 bits.
Métodos para cordas
O método join (list1):
Junção todas as cadeias de caracteres de uma lista em uma única cadeia de caracteres.
Embora pudéssemos usar + concatenar essas strings, quando um grande volume de dados
estiver envolvido, isso torna-se muito menos eficiente:
>>> slayer = [ "Buffy" , "Anne" , "Summers" ]
>>> " " .join (slayer)
Buffy Anne Summers
>>> "- <> -" .join (slayer)
'Buffy - <> - Anne - <> - Verões'
>>> "" .join (slayer)
'BuffyAnneSummers'
O join () também pode ser usado com o método reversed () interno:
>>> "" .join ( reversed(slayer))
'SummersAnneBuffy'
O método rjust (largura [, fillchar]) e ljust (largura [, fillchar]) ods:
Alguma formação (aligning) pode ser obtida com os métodos rjust () (adicione somente
no final), ljust () (adicione apenas no início):
>>> name = "Agent Mulder"
>>> name.rjust (50, '-' )
'----------------------------- Agent Mulder'

O método format ():


Usado para formatar ou adicionar valores variáveis a uma sequência:
>>> "{0} {1}" . format ("Eu sou o único!" , "Eu não sou" )
"Eu sou o único! Eu não sou"
>>> "{who} completou {idade} este ano!" . format (who = "Buffy" , idade = 17)
"Ela completou 88 anos este ano"
>>> "O { who } foi {0} na semana passada" . format (12, who = "garoto" )
Buffy fez 17 anos este ano!
No Python 3.1, é possível omitir nomes de campos; nesse caso, Python
efetivamente colocá-los para nós, usando números a partir de 0. Por exemplo:
>>> "{} {} {}" . formato ( "Python" , "pode" , "contar" )
'Python pode contar'
No entanto, o uso do operador + permitiria um estilo mais conciso aqui. Este o método
permite três especificadores: s para forçar a forma da string, r para forçar os representantes
forma organizacional e a para forçar a forma representacional, mas apenas usando ASCII
personagens:
>>> importar decimal
>>> "{0} {0! S} {0! R} {0! A}" . formato (decimal.Decimal ( "99.9" ))
"99,9 99,9 Decimal ('99 .9 ') Decimal ('99 .9')"
Descompactar String (Mapeamento)
O operador de desembalagem do mapeamento é ** e produz uma lista de valores-chave
adequada capaz de passar para uma função. As variáveis locais que estão atualmente no
escopo estão disponíveis nos locais internos () e isso pode ser usado para alimentar o
método format ():
>>> herói = "Buffy"
>>> número = 999
>>> "O elemento {número} é um {herói}" . format (** locais ())
'Elemento 999 é um Buffy'
O método splitlines (f):
Retorna a lista de linhas produzidas dividindo a string nos terminadores de linha,
eliminando os terminadores, a menos que f seja True:
>>> slayers = "Buffy \ nFé"
>>> slayers.splitlines ()
[ 'Buffy' , 'Faith' ]
O método de divisão (t, n):
Retorna uma lista de cadeias divididas no máximo n vezes na cadeia t. Se n não for dado,
divide-se tantas vezes quanto possível. Se t não for fornecido, ele será dividido em espaço
em branco:
>>> Slayers = " Buffy*Slaying-Vamps*16"
>>> fields = slayers.split ( "*" )
>>> fields
[ 'Buffy' , 'Slaying-Vamps' , '16' ]
>>> job = fields [1] .split ( "-" )
>>> job
[ 'Matando' , 'Vampiros' ]
Podemos usar split () para escrever nosso próprio método para apagar espaços de
cordas:
>>> def erase_space_from_string (string):
...
s1 = string.split ( "" )
...
s2 = "". joint (s1)
...
return s2

Um método semelhante, rsplit (), divide a string da direita para a esquerda.

O método strip ('chars'):


Retorna uma cópia da string com espaços em branco à esquerda e à direita (ou o caractere
chars) removidos:
>>> Slayers = "Buffy e Faith999"
>>> slayers.strip ( "999" )
"Buffy e fé"
O programa abaixo usa strip () para listar cada palavra e o número de as vezes em que
ocorrem em ordem alfabética para algum arquivo:3
[general_problems / strings / count_unique_words.py]
import string
import sys
def count_unique_word():
words = {} # create an empty dictionary
strip = string.whitespace + string.punctuation + string.digits
+ "\"'"
for filename in sys.argv[1:]:
with open(filename) as file:
for line in file:
for word in line.lower().split():
word = word.strip(strip)
if len(word) > 2:
words[word] = words.get(word,0) +1
for word in sorted(words):
print("'{0}' occurs {1} times.".format(word, words[word]))

Métodos semelhantes são: lstrip (), que retorna uma cópia da string com todos os espaços
em branco no início da corda foram arrancados; e rstrip (), que retorna uma cópia da
sequência com todos os espaços em branco no final da sequência despido.

O método swapcase ('chars'):


O método swapcase () retorna uma cópia da string com caracteres maiúsculos altera
caracteres minúsculos e minúsculos.
>>> Slayers = "Buffy and Faith"
3
Um exemplo semelhante é mostrado na seção Dicionários padrão.
>>> slayers.swapcase ()
'BUFFY AND FAITH'

Métodos semelhantes são:

⋆ capitalize () retorna uma cópia da string apenas com o primeiro caractere em


maiúsculas;
⋆ lower () retorna uma cópia da string original, mas com todos os caracteres em
minúsculas;
⋆ upper () retorna uma cópia da string original, mas com todos os caracteres em
maiúsculas.

Os métodos index (x) e find (x):


Existem dois métodos para encontrar a posição de uma string dentro de outra string. index
(x) retorna a posição de índice da substring x ou gera um ValueError exceção em caso de
falha. find (x) retorna a posição do índice da substring x, ou -1 na falha:
>>> Slayers = "Buffy e Faith"
>>> slayers.find ( "y" )
4
>>> slayers.find ( "k" )
-1
>>> slayers.index ( "k" )
Traceback (most recent call last):
File "<stdin>" , line 1, in <module>
ValueError: substring not found
>>> slayers.index ( "y" )
4

As extensões dos métodos anteriores são: rfind (string), que retorna o índice dentro da
string da última ocorrência (da direita) de 'string'; e rindex (string), que retorna o índice
dentro da string do último (à direita) ocorrência de 'string' (causando um erro se não puder
ser encontrado).

O método count (t, start, end):


Retorna o número de ocorrências da sequência t na sequência s:
>>> slayer = "Buffy é Buffy é Buffy"
>>> slayer.count ( "Buffy" , 0, -1)
2
>>> slayer.count ( "Buffy" )
3

O método de substituição (t, u, n):


Retorna uma cópia da string com todas as ocorrências (ou no máximo n, se fornecidas)
ocorrências da string t substituídas pela string u:
>>> slayer = "Buffy é Buffy é Buffy"
>>> slayer.replace ( "Buffy" , "quem" , 2)
'who is who is Buffy'

2.2 Tuplas
Uma tupla é um tipo de sequência imutável do Python que consiste em valores separados
por vírgulas:
>>> t1 = 1234, 'hello!'
>>> t1 [0]
1234
>>> t1
(12345, ' hello!')
>>> t2 = t2, (1, 2, 3, 4, 5) # aninhado
>>> u
((1234, ' hello!' ), (1, 2, 3, 4, 5))

Onde as strings têm um caractere em todas as posições, as tuplas têm um objeto referência
em cada posição. Portanto, é possível criar tuplas que contêm objetos mutáveis, como
listas. Tuplas vazias são construídas por um par vazio de parênteses. Uma tupla com um
item é construída por após um valor com vírgula (não é suficiente colocar um único valor
em parênteses):

>>> vazio = ()
>>> t1 = ' hello!' ,
>>> len (vazio)
00
>>> len (t1)
1
>>> t1
( ' hello!' )

Métodos para Tuplas


O método count (x) conta quantas vezes x aparece na tupla:
>>> t = 1, 5, 7, 8, 9, 4, 1, 4
>>> contagem (4)
2
O método index (x) retorna a posição do índice do elemento x:
>>> t = 1, 5, 7
>>> t.index (5)
1
Desempacotamento de tupla
No Python, qualquer objeto iterável pode ser descompactado usando a sequência
descompactar operador *. Quando usado com duas ou mais variáveis no lado esquerdo
de uma atribuição, uma das quais precedida por *, itens são atribuídos às variáveis, com
todos os que sobraram atribuídos à variável com estrela:
>>> x, * y = (1, 2, 3, 4)
>>> x
1
>>> y
[2, 3, 4]

Tuplas nomeadas
Coleções de pacotes do Python4 contém um tipo de dados de sequência chamado tupla.
Esses objetos se comportam exatamente como a tupla interna, com o mesmo desempenho
características de desempenho, mas também possuem a capacidade de se referir a itens
em a tupla por nome e por posição do índice. Isso permite a criação de agregados de itens
de dados:
>>> import collections
>>> MonsterTuple = collections.namedtuple("Monsters","name age power")
>>> MonsterTuple = ('Vampire', 230, 'immortal')
>>> MonsterTuple
('Vampire', 230, 'immortal')

O primeiro argumento para collections.namedtuple é o nome do costume tipo de dados


da tupla a ser criado. O segundo argumento é uma cadeia de espaço nomes separados, um
para cada item que a tupla personalizada terá. O primeiro argumento e os nomes no
segundo argumento devem ser válidos em Python identificadores.

[general_problems / tuples / namedtuple_example.py]

from collections import namedtuple

def namedtuple_example():
''' show an example for named tuples
>>> namedtuple_example()
slayer
'''
sunnydale = namedtuple('name', ['job', 'age'])
buffy = sunnydale('slayer', '17')
print(buffy.job)

if _ _name_ _ = = '_ _main_ _':


namedtuple_example()
4
Vamos explorar as coleções nos próximos capítulos.
2.3 Listas
Em geral, na ciência da computação, matrizes são uma estrutura de dados muito simples
onde os elementos são armazenados sequencialmente na memória continuada e nas listas
vinculadas são estruturas em que vários nós separados se vinculam. Iterando sobre o
conteúdo da estrutura de dados é igualmente eficiente para ambos os tipos, mas acessar
diretamente um elemento em um determinado índice possui tempo de execução O (1)
(complexidade)5 em uma matriz, enquanto possui O (n) em uma lista vinculada com n
nós (você teria para atravessar a lista desde o início). Além disso, em uma lista vinculada,
uma vez você sabe onde deseja inserir algo, a inserção é O (1), não importa quantos
elementos a lista possui. Para matrizes, uma inserção teria que se mover todos os
elementos que estão à direita do ponto de inserção ou movendo todos os elementos para
uma matriz maior, se necessário, sendo O (n).
No Python, o objeto mais próximo de uma matriz é uma lista, que é uma dinâmica
redimensionando array e não tem nada a ver com o conceito formal de listas vinculadas.
As listas vinculadas são uma estrutura de dados abstratos muito importante (nós veremos
mais sobre eles no capítulo seguinte) e é fundamental entender o que os torna diferentes
de matrizes (ou listas de Python).
As listas em Python são criadas por valores separados por vírgula, entre quadrados
suportes. Os itens da lista não precisam ter o mesmo tipo de dados. Ao contrário cordas
imutáveis, é possível alterar elementos individuais de uma lista (as listas são mutáveis):
>>> q = [2, 3]
>>> p = [1, q, 4]
>>> p [1] .append ( "buffy" )
>>> p
[1, [2, 3, 'buffy' ], 4]
>>> q
[2, 3, 'buffy' ]
>>> q
[2, 3, 'buffy' ]

Para inserir itens, as listas apresentam melhor desempenho (O (1)) quando itens são
adicionados ou retornados movido no final, usando os métodos append () e pop (),
respectivamente.
O pior desempenho (O (n)) ocorre com operações que precisam pesquisar
-- --- --- --- ---
5
A notação Big-O é a chave para entender algoritmos! Vamos aprender mais sobre isso
nos capítulos seguintes e use-os extensivamente em nossos estudos. Por enquanto, apenas
mantenha-se em mina que O (1) vezes ≪ O (n) ≪ O (n 2 ), etc ...

para itens da lista, por exemplo, usando remove () ou index () ou usando para teste de
associação.6
Se for necessária uma pesquisa rápida ou teste de associação, um tipo de coleção como
como um conjunto ou um dicionário pode ser uma escolha mais adequada (como veremos
em próximo capítulo). Como alternativa, as listas podem fornecer uma pesquisa rápida se
estiverem mantidos em ordem ao serem classificados (veremos métodos de pesquisa que
são executados em O (log n) para sequências classificadas, em particular a pesquisa
binária, nos capítulos seguintes).
Métodos para listas
O método append (x):
Adiciona um novo elemento no final da lista. É equivalente à lista [len (list):] = [x]:
>>> pessoas = [ "Buffy" , " Faith" ]
>>> people.append ("Giles")
>>> pessoas
[ 'Buffy' , 'Faith' , 'Giles' ]
>>> pessoas [ len (pessoas):] = [ "Xander" ]
>>> pessoas
[ 'Buffy' , 'Faith' , 'Giles' , 'Xander' ]

O método extend (c):


Este método é usado para estender a lista, anexando todos os itens iteráveis em lista
fornecida. Equivalente a [len (a):] = L ou usando + =:
>>> pessoas = [ "Buffy" , "Fé" ]
>>> people.extend ( "Giles" )
>>> pessoas
[ 'Buffy' , 'Faith' , 'G' , 'i' , 'l' , 'e' , 's' ]
>>> pessoas + = "Willow"
>>> pessoas
[ 'Buffy' , 'Faith' , 'G' , 'i' , 'l' , 'e' , 's' , 'W' , 'i' , 'l' , 'l' , 'o' , 'w' ]

--- ---- -----


6
Isso explica por que append () é muito mais eficiente que insert ().

>>> pessoas + = [ "Xander" ]


>>> pessoas
[ 'Buffy' , 'Faith' , 'G' , 'i' , 'l' , 'e' , 's' , 'W' , 'i' , 'l' , 'l' , 'o' , 'w' , 'Xander' ]

O método insert (i, x):


Insere um item em uma determinada posição i: o primeiro argumento é o índice do
elemento antes do qual inserir:
>>> pessoas = [ "Buffy" , "Faith" ]
>>> people.insert (1, "Xander" )
>>> pessoas
[ 'Buffy' , 'Xander' , 'Faith' ]

O método remove ():


Remove o primeiro item da lista cujo valor é x. Gera um ValueError exceção se x não for
encontrado:
>>> pessoas = [ "Buffy" , "Faith" ]
>>> people.remove ( "Buffy" )
>>> pessoas
[ 'Faith' ]
>>> people.remove ( "Buffy" )
Traceback (última chamada mais recente):
Arquivo "<stdin>" , linha 1, em <module>
ValueError: list .remove (x): x não está na lista
O método pop ():
Remove o item na posição especificada na lista e depois o retorna. Se não
Se o índice for especificado, pop () retornará o último item da lista:
>>> pessoas = [ "Buffy" , " Faith " ]
>>> people.pop ()
' Faith '
>>> pessoas
[ 'Buffy' ]

O método del:
Exclui a referência do objeto, não o argumento, ou seja, é uma maneira de remover um
item de uma lista, considerando seu índice em vez de seu valor. Isso também pode ser
usado para remova fatias de uma lista:
>>> a = [-1, 4, 5, 7, 10]
>>> del a [0]
>>> a
[4, 5, 7, 10]
>>> del a [2: 3]
>>> a
[4, 5, 10]
>>> del a
# também usado para excluir a variável inteira
Quando uma referência a um objeto é excluída e se nenhum outro objeto se refere à sua
dados, o Python agenda o item de dados para coleta de lixo.7

O método index (x):


Retorna o índice na lista do primeiro item cujo valor é x:
>>> pessoas = [ "Buffy" , "Fé" ]
>>> people.index ( "Buffy" )
00

O método count (x):


Retorna o número de vezes que x aparece na lista:
>>> pessoas = [ "Buffy" , "Fé" , "Buffy" ]
>>> people.count ( "Buffy" )
2
---- ---- ----
7
Garbage é uma memória ocupada por objetos que não são mais referenciados e garbage
coleção é uma forma de gerenciamento automático de memória, liberando a memória
ocupada por lixo.

O método sort ():


Classifica os itens da lista, no local:
>>> pessoas = [ "Xander" , "Faith" , "Buffy" ]
>>> people.sort ()
>>> pessoas
[ 'Buffy' , 'Faith' , 'Xander' ]

O método reverse ():


Inverte os elementos da lista, no local:
>>> pessoas = [ "Xander" , "Faith" , "Buffy" ]
>>> people.reverse ()
>>> pessoas
[ 'Buffy' , 'Faith' , 'Xander' ]

List Unpacking
Semelhante à descompactação da tupla:
>>> first, * rest = [1,2,3,4,5]
>>> primeiro
1
>>> rest
[2, 3, 4, 5]
O Python também possui um conceito relacionado chamado argumento com estrela, que
pode ser usado como argumento de passagem para uma função:
>>> def example_args (a, b, c):
...
retornar a * b * c # aqui * é o operador de multiplicação
>>> L = [2, 3, 4]
>>> example_args (* L)
24
>>> example_args (2, * L [1:])
24

Compreensões da lista (List Comprehensions)


Uma compreensão de lista é uma expressão e um loop (com uma condição opcional)
entre parênteses:
[item por item em iterável]
[expressão para item em iterável]
[expressão para item em condição iterável se ]
---
[item for item in iterable]
[expression for item in iterable]
[expression for item in iterable if condition]
---
Alguns exemplos de compreensão de lista:
>>> a = [y for y in range(1900, 1940) if y%4 = = 0]
>>> a
[1900, 1904, 1908, 1912, 1916, 1920, 1924, 1928, 1932, 1936]
>>> b = [2**i for i in range(13)]
>>> b
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096]
>>> c = [x for x in a if x%2==0]
>>> c
[0, 4, 16, 36, 64]
>>> d = [str(round(355/113.0,i)) for i in range(1,6)]
>>> d
[ '3.1' , '3.14' , '3.142' , '3.1416' , '3.14159' ]
>>> words = 'Buffy is awesome and a vampire slayer'.split()
>>> e = [[w.upper(), w.lower(), len(w)] for w in words]
>>> for i in e:
. . . print(i)
...
[ 'BUFFY' , 'buffy' , 5]
[ 'IS' , 'é' , 2]
[ 'IMPRESSIONANTE' , 'incrível' , 7]
[ 'AND' , 'e' , 3]
[ 'A' , 'a' , 1]
[ 'VAMPIRO' , 'vampiro' , 7]
[ 'SLAYER' , 'slayer' , 6]

A compreensão da lista deve ser usada apenas para casos simples, quando cada parte se
encaixa em uma linha (nenhum múltiplo para cláusulas ou expressões de filtro):

[Good]
result = [ ]
for x in range(10):
for y in range(5):
if x * y > 10:
result.append((x, y))
for x in range(5):
for y in range(5):
if x != y:
for z in range(5):
if y != z:
yield (x, y, z)

return ((x, complicated_transform(x))


for x in long_generator_function(parameter)
if x is not None)

squares = [x * x for x in range(10)]


eat(jelly_bean for jelly_bean in jelly_beans
if jelly_bean.color == 'black')

[Bad]
result = [(x, y) for x in range(10) for y in range(5) if x * y > 10]

return ((x, y, z)
for x in xrange(5)
for y in xrange(5)
if x != y
for z in xrange(5)
if y != z)

Análise de tempo de execução para listas


Para entender melhor o desempenho das listas do Python, comparamos alguns métodos
das listas. No trecho abaixo, usamos o módulo timeit do Python para criar um objeto
Timer cujo primeiro parâmetro é o que queremos cronometrar e o segundo parâmetro é
uma instrução para configurar o teste. O módulo timeit será quanto tempo leva para
executar a instrução várias vezes (uma milhões de vezes por padrão). Quando o teste é
concluído, ele retorna o tempo como um flutuante valor em ponto que representa o
número total de segundos:
[general_problems/lists/runtime_lists_with_timeit_module.py]

def test1():
l=[]
for i in range(1000):
l = l + [i]
def test2():
l=[]
for i in range(1000):
l.append(i)
def test3():
l = [i for i in range(1000)]
def test4():
l = list(range(1000))

if _ _name_ _ = = '_ _main_ _':


import timeit
t1 = timeit.Timer("test1()", "from __main__ import test1")
print("concat ",t1.timeit(number=1000), "milliseconds")
t2 = timeit.Timer("test2()", "from __main__ import test2")
print("append ",t2.timeit(number=1000), "milliseconds")
t3 = timeit.Timer("test3()", "from __main__ import test3")
print("comprehension ",t3.timeit(number=1000), "milliseconds")
t4 = timeit.Timer("test4()", "from __main__ import test4")
print("list range ",t4.timeit(number=1000), "milliseconds")

""" The results are:


('concat ', 2.366791009902954, 'milliseconds')
('append ', 0.16743111610412598, 'milliseconds')
('comprehension ', 0.06446194648742676, 'milliseconds')
('list range ', 0.021029949188232422, 'milliseconds')

So we see the following pattern for lists:


Operation Big-O Efficiency
index [ ] O(1)
index assignment O(1)
append O(1)
pop() O(1)
pop(i) O(n)
insert(i,item) O(n)
del operator O(n)
iteration O(n)
contains (in) O(n)
get slice [x:y] O(k)
del slice O(n)
set slice O(n+k)
reverse O(n)
concatenate O(k)
sort O(n log n)
multiply O(nk)
"""

2.4 Bytes e matrizes de bytes


O Python fornece dois tipos de dados para lidar com bytes brutos: bytes que são imutáveis
e bytearray, que é mutável. Ambos os tipos mantêm uma sequência de zero de mais
números inteiros não assinados de 8 bits no intervalo de 0 a 255. O tipo de byte é muito
semelhante ao tipo de string e o bytearray fornece mutação métodos semelhantes às listas.

Operações de bits e bit a bit


As operações bit a bit podem ser muito úteis para manipular números representados como
bits (por exemplo, reproduz uma divisão sem usar a operação de divisão). Podemos
calcular rapidamente 2 x pela operação de deslocamento para a esquerda: 1 x.
Também podemos verificar rapidamente se um número é uma potência de 2, verificando se
x & (x - 1) é 0 (se x não for uma potência uniforme de 2, a posição mais alta de x com 1
também terá 1 em x − 1, caso contrário, x será 100 ... 0 e x – 1 será 011 ... 1; adicioná-los
juntos, eles retornarão 0).

2.5 Exemplos adicionais


Inverter palavras em uma string, sem reverter a Palavras
Aqui, queremos inverter as palavras em uma string, sem revertê-las. É importante lembrar
que as sequências de caracteres Python são imutáveis, então podemos deseja converter em
listas em alguns casos.
Existem muitas maneiras de resolver esses problemas. Podemos usar os métodos Python
internos para listas e strings, ou podemos trabalhar com ponteiros. No segundo caso, a solução
consiste em dois loops. O primeiro loop reverterá tudo os caracteres utilizando dois ponteiros.
O segundo loop procurará espaços e depois reverta as palavras.
Outras advertências desses problemas são que podemos representar o espaço como '' ou como
unicodes (u0020). Também podemos ter que prestar atenção na última palavra, uma vez que
não termina com um espaço. Por fim, uma extensão desse problema é para procurar símbolos
como "!?; -.".
[arrays_strings_lists/reversing_words_string.py]
# EXAMPLE NUMBER 1
def reverser(string1, p1=0, p2=None):
if len(string1) < 2:
return string1
p2 = p2 or len(string1)-1
while p1 < p2:
aux = string1[p1]
string1[p1] = string1[p2]
string1[p2] = aux
p1 += 1
p2 -= 1
def reversing_words_setence_logic(string1):
reverser(string1)
p=0
start = 0
final = []
while p < len(string1):
if string1[p] == u"\u0020":
reverser(string1,start,p-1)
start = p+1
p += 1
reverser(string1,start,p-1)
return "".join(string1)

# EXAMPLE NUMBER 2 AND 3 USING PYTHON AWESOMESAUCE

def reversing_words_setence_py(str1):
''' reverse the words in a sentence'''
words = str1.split()
rev_set = " ".join(reversed(words))
return rev_set

def reversing_words_setence_py2(str1):
"""
Reverse the order of the words in a sentence.
:param string: the string which words wilL be reversed.
:return: the reversed string.
"""
words = str1.split(' ')
words.reverse()
return ' '.join(words)

# EXAMPLE 4, VERY SILLY, USING BRUTE FORCE


#
def reverse_words_brute(string):
"""
Reverse the order of the words in a sentence.
:param string: the string which words wil lbe reversed.
:return: the reversed string.
"""
word, sentence = [], []
for character in string:
if character != ' ':
word.append(character)
else:
# So we do not keep multiple whitespaces. An empty list evaluates to False.
if word:
sentence.append(''.join(word))
word = []
# So we do not keep multiple whitespaces. An empty list evaluates to False.
if word != '':
sentence.append(''.join(word))
sentence.reverse()
return ' '.join(sentence)

# TESTS
def test_reversing_words_sentence():
str1 = "Buffy is a Vampire Slayer"
assert(reversing_words_setence_py(str1) == "Slayer Vampire a is Buffy")
assert(reversing_words_setence_py2(str1) == "Slayer Vampire a is Buffy")
assert(reverse_words_brute(str1) == "Slayer Vampire a is Buffy")
assert(reversing_words_setence_logic(list(str1)) == "Slayer Vampire a is Buffy")
print("Tests passed!")
if _ _name_ _ == '_ _main_ _':
test_reversing_words_sentence()
Capítulo 3

Estruturas de dados de coleta


Diferentemente das estruturas de dados de sequência do último capítulo, onde os dados
ordenadas ou fatiadas, as estruturas de dados de coleta são recipientes que agrega dados
sem relacioná-los. As estruturas de dados de coleta também têm algumas propriedades
que os tipos de sequência têm:
⋆ Operador de associação (por exemplo, usando in);
⋆ Um método de tamanho (fornecido por len (seq)); e
⋆ Iterabilidade (podemos iterar os dados em loops).
No Python, os tipos de dados de coleção internos são dados por conjuntos e dicts. Além
disso, muitos dados úteis da coleção são encontrados no pacote de coleções, conforme
discutimos na última parte deste capítulo.

3.1 Conjuntos
No Python, um conjunto é um tipo de dados de coleção não ordenada que é iterável, tabela
e não possui elementos duplicados. Conjuntos são usados para teste de associação e
eliminando entradas duplicadas. Os conjuntos têm inserção O (1), portanto, o tempo de
execução de união é O (m + n). Para interseção, é necessário apenas atravessar a conjunto
menor, então o tempo de execução é O (n). 1
1
Pacote de coleção do Python tem suporte para conjuntos ordenados. Esse tipo de dados
impõe alguma comparação predefinida para seus membros.

Conjuntos congelados
Conjuntos congelados são objetos imutáveis que suportam apenas métodos e operadores que
produzem um resultado sem afetar o conjunto ou conjuntos congelados aos quais são
aplicados.

Métodos para conjuntos


O método add (x):
Adiciona o item x para definir, se ainda não estiver no conjunto:
>>> people = { "Buffy" , "Angel" , "Giles" }
>>> people.add ( "Willow" )
>>> pessoas
{ 'Willow' , 'Giles' , 'Buffy' , ' Angel' }

O s.update (t) ou s | = t Métodos:

Ambos retornam um conjunto s com elementos adicionados de t.


Os métodos s.union (t) ou s | t:
Ambos realizam a união dos dois conjuntos.
Os métodos s.intersection (t) ou s & t:
Ambos retornam um novo conjunto que possui cada item dos conjuntos:
>>> pessoas = { "Buffy" , "Anjo" , "Giles" , "Xander" }
>>> people.intersection ({ "Anjo" , "Giles" , "Salgueiro" })
{ 'Giles' , 'Anjo' }
Os métodos s.difference (t) ou s - t:
Ambos retornam um novo conjunto que possui todos os itens que não estão no segundo
conjunto:
>>> pessoas = { "Buffy" , "Anjo" , "Giles" , "Xander" }
>>> vampiros = { "Spike" , "Angel" , "Drusilia" }
>>> people.difference (vampiros)
{ 'Xander' , 'Giles' , 'Buffy' }

O método clear ():


Remove todos os itens do conjunto:
>>> pessoas = { "Buffy" , "Anjo" , "Giles" }
>>> people.clear ()
>>> pessoas
set ()

Os métodos de descarte (x), remove (x) e pop ():


descarte (x) remove o item x do aparelho remove (x) remove o item x do conjunto ou gera
uma exceção KeyError se o elemento não estiver no conjunto pop () retorna e remove um
item aleatório do conjunto ou gera um KeyError exceção se o conjunto estiver vazio.
Conjuntos com listas e dicionários você pode transmitir um conjunto de uma lista. Por
exemplo, o trecho abaixo mostra algumas das operações de conjunto disponíveis nas
listas:
[general_problems / sets / set_operations_with_lists.py]

def difference(l1):
"" "retorna a lista com os elementos duplicados removidos" ""
return list(set(l1))

def intersection(l1, l2):


"" "retorna a interseção de duas listas" ""
return list(set(l1) & set(l2))

def union(l1, l2):


"" "retorna a união de duas listas" ""
return list(set(l1) | set(l2))

def test_sets_operations_with_lists():
l1 = [1,2,3,4,5,9,11,15]
l2 = [4,5,6,7,8]
l3 = []
assert(difference(l1) == [1, 2, 3, 4, 5, 9, 11, 15])
assert(difference(l2) == [8, 4, 5, 6, 7])
assert(intersection(l1, l2) == [4,5])
assert(union(l1, l2) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 15])
assert(difference(l3) == [])
assert(intersection(l3, l2) == l3)
assert(sorted(union(l3, l2)) == sorted(l2))
print('Tests passed!')
if _ _name_ _ = = '_ _main_ _':
test_sets_operations_with_lists()

Também podemos usar as propriedades dos conjuntos nos dicionários:2

[general_problems/dicts/set_operations_dict.py]

from collections import OrderedDict

def set_operations_with_dict():
pairs = [('a', 1), ('b',2), ('c',3)]
d1 = OrderedDict(pairs)
print(d1) # ('a', 1), ('b', 2), ('c', 3)

d2 = {'a':1, 'c':2, 'd':3, 'e':4}


print(d2) # {'a': 1, 'c': 2, 'e': 4, 'd': 3}

union = d1.keys() & d2.keys()


print(union) # {'a', 'c'}

union_items = d1.items() & d2.items()


print(union_items) # {('a', 1)}

subtraction1 = d1.keys() - d2.keys()


print(subtraction1) # {'b'}

subtraction2 = d2.keys() - d1.keys()


print(subtraction2) # {'d', 'e'}

subtraction_items = d1.items() - d2.items()


print(subtraction_items) # {('b', 2), ('c', 3)}

''' we can remove keys from a dict doing: '''


d3 = {key:d2[key] for key in d2.keys() - {'c', 'd'}}
print(d3) {'a': 1, 'e': 4}

if _ _name_ _ = = '_ _main_ _':


set_operations_with_dict()
2
Sets as propriedades podem ser usadas nos atributos items () e keys () dos atributos do
dict, no entanto, os valores () não suportam operações definidas.

3.2 Dicionários
Os dicionários em Python são implementados usando tabelas de hash3. Funções hash
calculam algum valor inteiro aleatório de um objeto arbitrário em tempo constante, que
pode ser usado como um índice em uma matriz:
>>> hash (42)
42.
>>> hash ( "olá" )
355070280260770553

Um ditado é um tipo de mapeamento de coleção que é iterável e suporta os operadores de


concessionário e a função size len (). Mapeamentos são coleções itens de valor-chave,
fornecendo métodos para acessar itens e suas chaves e valores. Quando iterados, tipos de
mapeamento não ordenados fornecem seus itens em uma ordem arbitrária.
O acesso aos dicionários possui o tempo de execução O (1), portanto eles são usados para
manter as contagens de itens exclusivos (por exemplo, contando o número de cada palavra
exclusiva em um arquivo) e para teste rápido de associação. Os dicionários são mutáveis,
para que possamos facilmente adicionar ou remover itens, mas como não são ordenados,
eles não têm noção de posição do índice (para que não possam ser fatiadas ou listradas):

3
Uma tabela de hash é uma estrutura de dados usada para implementar uma matriz
associativa, uma estrutura que pode mapear chaves para valores.

>>> tarantino = {}
>>> tarantino['name'] = 'Quentin Tarantino'
>>> tarantino['job'] = 'director'
>>> tarantino
{'job': 'director', 'name': 'Quentin Tarantino'}
>>>
>>> sunnydale = dict({"name":"Buffy", "age":16, "hobby":"slaying"})
>>> sunnydale
{'hobby': 'slaying', 'age': 16, 'name': 'Buffy'}
>>>
>>> sunnydale = dict(name="Giles", age=45, hobby="Watch")
>>> sunnydale
{'hobby': 'Watch', 'age': 45, 'name': 'Giles'}
>>>
>>> sunnydale = dict([("name", "Willow"), ("age",15), ("hobby", "nerding")])
>>> sunnydale
{'hobby': 'nerding', 'age': 15, 'name': 'Willow'}

Métodos para dicionários


O método setdefault (key [, default]):
O método setdefault () é usado quando queremos acessar uma chave no dicionário sem ter
certeza de que ela existe (se simplesmente tentarmos acessar uma chave inexistente em um
dicionário, obteremos uma exceção). Com setdefault (), se key estiver no dicionário, obtemos
o valor. Caso contrário, inserimos com sucesso a nova chave com o valor padrão:

[general_problems/dicts/setdefault_example.py]
def usual_dict(dict_data):
newdata = {}
for k, v in dict_data:
if k in newdata:
newdata[k].append(v)
else:
newdata[k] = [v]
return newdata
def setdefault_dict(dict_data):
newdata = {}
for k, v in dict_data:
newdata.setdefault(k, []).append(v)
return newdata

def test_setdef(module_name='this module'):


dict_data = (('key1', 'value1'),
('key1', 'value2'),
('key2', 'value3'),
('key2', 'value4'),
('key2', 'value5'),)
print(usual_dict(dict_data))
print(setdefault_dict(dict_data))

s = 'Tests in {name} have {con}!'


print(s.format(name=module_name, con='passed'))
if _ _name_ _ == '_ _main_ _':
test_setdef()

O método update ([other]):


Atualiza o dicionário com os pares chave / valor de outros, substituindo chaves existentes.

O método get (key):


Retorna o valor associado à chave ou nenhum se a chave não estiver no dicionário:
>>> sunnydale = dict (nome = "Xander" , age = 17, hobby = "winning" )
>>> sunnydale.get ("hobby")
'winning'

Os Métodos items (), values () e keys ():


Os métodos items (), keys () e values () retornam visualizações de dicionário. Uma
exibição de dicionário é efetivamente um objeto iterável somente leitura que parece
conter os itens, chaves ou valores do dicionário:
>>> sunnydale = dict(name="Xander", age=17, hobby="winning")
>>> sunnydale.items()
dict_items([('hobby', 'winning'), ('age', 17), ('name', 'Xander')])
>>> sunnydale.values()
dict_values(['winning', 17, 'Xander'])
>>> sunnydale.keys()
dict_keys(['hobby', 'age', 'name'])

Os métodos pop () e popitem ():


O método pop () remove um item arbitrário do dicionário, retornando-o. O método
popitem () remove um arbitrário (chave, valor) do dicionário, retornando-o também.

O método clear ():


Remove todos os itens do dicionário:
>>> sunnydale.clear ()
>>> Sunnydale
{}
Análise de tempo de execução para dicionários
Analisamos o desempenho do dicionário Python comparando alguns métodos aceitáveis.
O trecho abaixo mostra que a operação de associação para listas é O (n) e para dicionários
é O (1)):
[general_problems/dicts/runtime_dicts_with_timeit_module.py]
import timeit
import random

for i in range(10000,1000001,20000):
t = timeit.Timer("random.randrange(%d) in x"%i, "from __main__
import random,x")
x = list(range(i))
lst_time = t.timeit(number=1000)
x = {j:None for j in range(i)}
d_time = t.timeit(number=1000)
print("%d,%10.3f,%10.3f" % (i, lst_time, d_time))
""" There results are:
10000, 0.192, 0.002
30000, 0.600, 0.002
50000, 1.000, 0.002
70000, 1.348, 0.002
90000, 1.755, 0.002
110000, 2.194, 0.002
130000, 2.635, 0.002
150000, 2.951, 0.002
170000, 3.405, 0.002
190000, 3.743, 0.002
210000, 4.142, 0.002
230000, 4.577, 0.002
250000, 4.797, 0.002
270000, 5.371, 0.002
290000, 5.690, 0.002
310000, 5.977, 0.002

Assim, podemos ver o bloco linear para listas e a constante para dict!

Grande eficiência de operações de dicionário Python


Big-O Efficiency of Python Dictionary Operations
Operation Big-O Efficiency
copy O(n)
get item O(1)
set item O(1)
delete item O(1)
contains (in) O(1)
iteration O(n)
"""
Iterando sobre Dicionários
Um loop sobre um dicionário itera sobre suas chaves por padrão. As teclas serão aparecem
em uma ordem arbitrária, mas podemos usar a sorted () para iterar sobre os itens de
maneira ordenada. Isso também funciona para os atributos keys( ), values( ), e items( ):
>>> for key in sorted(dict.keys()):
... print key, dict[key]
Uma ferramenta útil para criar uma lista de itens-chave para um dicionário é usar
geradores:
def items_in_key_order(d):
for key in sorted(d):
yield key, d[key]

Os dicionários também suportam iteração reversa usando reversed (). Finalmente,


iteradores padrão devem ser usados para tipos que os suportam:
[Good] for key in adict: ...
if key not in adict: ...
[Bad] for key in adict.keys(): ...
if not adict.has_key(key): ...

Ramificação usando Dicionários


Podemos usar dicionários para escrever um menu ramificado:
if action == "a":
add_to_dict(db)
elif action == "e":
edit_dict(db)

E uma maneira mais eficiente:


functions = dict(a=add_to_dict, e=edit_dict,...)
functions[actions](db)

3.3 Tipos de dados da coleção do Python


O módulo de coleções do Python implementa tipos de dados de contêineres especializados,
oferecendo alternativas de alto desempenho aos contêineres internos de uso geral.

Dicionários padrão
Os dicionários padrão são um tipo de mapeamento não ordenado adicional, fornecido
pelas coleções.defaultdict do Python. Eles têm todos os operadores e métodos que um
dicionário interno possui, mas, além disso, eles lidam com chaves:

[general_examples/dicts/defaultdict_example.py]
from collections import defaultdict

def defaultdict_example():

''' show some examples for defaultdicts '''


pairs = {('a', 1), ('b',2), ('c',3)}

d1 = {}
for key, value in pairs:
if key not in d1:
d1[key] = []
d1[key].append(value)
print(d1)

d2 = defaultdict(list)
for key, value in pairs:
d2[key].append(value)
print(d2)
if _ _name_ _ == '_ _main_ _':
defaultdict_example()

Dicionários ordenados
Os dicionários ordenados são um tipo de mapeamento ordenado fornecido pelo Python
collections.OrderedDict. Eles têm todos os métodos e propriedades de um dict interno,
mas além disso, eles armazenam itens na ordem de inserção:

[general_examples/dicts/OrderedDict_example.py]

from collections import OrderedDict


pairs = [('a', 1), ('b',2), ('c',3)]
d1 = {}
for key, value in pairs:
if key not in d1:
d1[key] = []
d1[key].append(value)
for key in d1:
print(key, d1[key])
d2 = OrderedDict(pairs)
for key in d2:
print(key, d2[key])

if _ _name_ _ = = '_ _main_ _':


OrderedDict_example()
"""
a [1]
c [3]
b [2]
a1
b2
c3
"""
Podemos criar dicionários ordenados de forma incremental:
>>> tasks = coleções.OrderedDict ()
>>> tasks [8031] = "Backup"
>>> tasks [4027] = " Scan Email"
>>> tasks [5733] = " Build System"
>>> tasks
OrderedDict ([(8031, 'Backup' ), (4027, 'Scan Email' ), (5733, 'Build System ' )])
Se alterarmos um valor-chave, a ordem não será alterada. Para mover um item para no
final, devemos excluir e reinserir. Também podemos chamar popitem () para remova e
retorne o último item de valor-chave no dicionário ordenado.
Em geral, usar um dicionário ordenado para produzir um dicionário classificado faz
sentido apenas se esperamos repetir o dicionário várias vezes, e se não esperamos fazer
inserções (ou muito poucas).

Dicionários de contador
Um tipo de contador especializado (subclasse para contar objetos hashable) é fornecido
pelas coleções do Python.
[general_examples / dicts / Counter_example.py]

from collections import Counter


def Counter_example():
'' 'mostre alguns exemplos para o contador' ''
'' 'é um dicionário que mapeia os itens para o número de ocorrências '' '
seq1 = [1, 2, 3, 5, 1, 2, 5, 5, 2, 5, 1, 4]
seq_counts = Counter(seq1)
print(seq_counts)

'' 'podemos incrementar manualmente ou usar o método update ()' ''


seq2 = [1, 2, 3]
seq_counts.update(seq2)
print(seq_counts)
seq3 = [1, 4, 3]
for key in seq3:
seq_counts[key] += 1
print(seq_counts)

'' 'também, podemos usar operações definidas como ab ou a + b' ''


seq_counts_2 = Counter(seq3)
print(seq_counts_2)
print(seq_counts + seq_counts_2)
print(seq_counts - seq_counts_2)
if _ _name_ _ = = '_ _main_ _':
Counter_example()

"""
Counter({5: 4, 1: 3, 2: 3, 3: 1, 4: 1})
Counter({1: 4, 2: 4, 5: 4, 3: 2, 4: 1})
Counter({1: 5, 2: 4, 5: 4, 3: 3, 4: 2})
Counter({1: 1, 3: 1, 4: 1})
Counter({1: 6, 2: 4, 3: 4, 5: 4, 4: 3})
Counter({1: 4, 2: 4, 5: 4, 3: 2, 4: 1})
"""
3.4 Exemplos adicionais
Frequência de contagem de itens
No exemplo abaixo, usamos collections.Counter () é o most_common() método para
encontrar as N palavras recorrentes principais em uma sequência:
[general_problems/dicts/find_top_N_recurring_words.py]

from collections import Counter

def find_top_N_recurring_words(seq, N):


dcounter = Counter()
for word in seq.split():
dcounter[word] += 1
return dcounter.most_common(N)

def test_find_top_N_recurring_words(module_name='this module'):


seq = 'buffy angel monster xander a willow gg buffy the monster super buffy angel'
N=3
assert(find_top_N_recurring_words(seq, N) == [('buffy', 3), ('monster', 2),
('angel', 2)])

s = 'Tests in {name} have {con}!'


print(s.format(name=module_name, con='passed'))

if _ _name_ _ == '_ _main_ _':


test_find_top_N_recurring_words()

O programa abaixo conta todas as palavras exclusivas em um arquivo:

[general_problems/dicts/count_unique_words.py]

import collections
import string
import sys

def count_unique_word():
words = collections.defaultdict(int)
strip = string.whitespace + string.punctuation + string.digits + "\" ' "
for filename in sys.argv[1:]:
with open(filename) as file:
for line in file:
for word in line.lower().split():
word = word.strip(strip)
if len(word) > 2:
words[word] = +1
for word in sorted(words):
print("'{0}' occurs {1} times.".format(word, words[word]))
Anagramas
O programa a seguir descobre se duas palavras são anagramas. Desde conjuntos não conte
ocorrência, e classificando uma lista é O (n log n), as tabelas de hash podem ser a melhor
solução nesse caso. O procedimento que usamos é: digitalizamos a primeira string e
adicione todas as ocorrências de caracteres. Então digitalizamos a segunda string,
diminuindo todas as ocorrências de caracteres. No final, se todas as entradas são zero, a
cadeia é um anagrama:

[general_problems/dicts/verify_two_strings_are_anagrams.py]

def verify_two_strings_are_anagrams(str1, str2):


ana_table = {key:0 for key in string.ascii_lowercase}

for i in str1:
ana_table[i] += 1

for i in str2:
ana_table[i] -= 1

# verify whether all the entries are 0


if len(set(ana_table.values())) < 2: return True
else: return False

def test_verify_two_strings_are_anagrams():
str1 = 'marina'
str2 = 'aniram'
assert(verify_two_strings_are_anagrams(str1, str2) == True)
str1 = 'google'
str2 = 'gouglo'
assert(verify_two_strings_are_anagrams(str1, str2) == False)
print('Tests passed!')

if _ _name_ _ == '_ _main_ _':


test_verify_two_strings_are_anagrams()

Outra maneira de descobrir se duas palavras são anagramas é usar as propriedades da


função hash, onde cada quantidade diferente de caracteres deve fornecer um resultado
diferente. No programa a seguir, ord () retorna um número inteiro que representa o ponto
de código do caractere Unicode quando o argumento é um objeto unicode ou o valor do
byte quando o argumento é uma string de 8 bits:

[general_problems/dicts/find_anagram_hash_function.py]

def hash_func(astring, tablesize):


sum = 0
for pos in range(len(astring)):
sum = sum + ord(astring[pos])
return sum%tablesize
def find_anagram_hash_function(word1, word2):
tablesize = 11
return hash_func(word1, tablesize) == hash_func(word2, tablesize)

def test_find_anagram_hash_function(module_name='this module'):


word1 = 'buffy'
word2 = 'bffyu'
word3 = 'bffya'
assert(find_anagram_hash_function(word1, word2) == True)
assert(find_anagram_hash_function(word1, word3) == False)
s = 'Tests in {name} have {con}!'
print(s.format(name=module_name, con='passed'))

if __name__ == '__main__':
test_find_anagram_hash_function()

Soma de caminhos
O programa a seguir usa dois contêineres de dicionário diferentes para determinar o
número de maneiras que dois dados podem somar para um determinado valor:

[general_problems/dicts/find_dice_probabilities.py]

from collections import Counter, defaultdict

def find_dice_probabilities(S, n_faces=6):


if S > 2*n_faces or S < 2: return None
cdict = Counter()
ddict = defaultdict(list)
for dice1 in range(1, n_faces+1):
for dice2 in range(1, n_faces+1):
t = [dice1, dice2]
cdict[dice1+dice2] += 1
ddict[dice1+dice2].append( t)
return [cdict[S], ddict[S]]

def test_find_dice_probabilities(module_name='this module'):


n_faces = 6
S=5
results = find_dice_probabilities(S, n_faces)
print(results)
assert(results[0] == len(results[1]))

if __name__ == '__main__':
test_find_dice_probabilities()

Localizando duplicatas
O programa abaixo usa dicionários para encontrar e excluir todos os caracteres duplicados
em uma string:
[general_problems/dicts/delete_duplicate_char_str.py]

import string
def delete_unique_word(str1):
table_c = { key : 0 for key in string.ascii_lowercase}
for i in str1:
table_c[i] += 1
for key, value in table_c.items():
if value > 1:
str1 = str1.replace(key, "")
return str1

def test_delete_unique_word():
str1 = "google"
assert(delete_unique_word(str1) == 'le')
print('Tests passed!')

if __name__ == '__main__':
test_delete_unique_word()
Capítulo 4

Estrutura do Python e Módulos


4.1 Módulos em Python
No Python, os módulos são definidos usando o nome interno def. Quando def é
executado, um objeto de função é criado junto com sua referência de objeto. Se não
definirmos um valor de retorno, o Python retornará automaticamente None (como em C,
chamamos a função de procedimento quando ele não retorna um valor).
Um registro de ativação acontece toda vez que invocamos um método: as informações
são colocadas na pilha para suportar a invocação. Os registros de ativação são processados
na seguinte ordem:
Registros de Ativação
1. os parâmetros reais do método são empurrados para a pilha,
2. o endereço de retorno é colocado na pilha,
3. o índice de topo de pilha é incrementado pela quantidade total exigida por as variáveis
locais dentro do método,
4. um salto para o método.

O processo de desenrolar um registro de ativação acontece nas seguintes ordem:


1. o índice do topo da pilha é diminuído pela quantidade total de memória consumido,
2. o endereço retornado é retirado da pilha,
3. o índice de topo de pilha é diminuído pela quantidade total de memória pelos
parâmetros reais.

Valores padrão em módulos


Sempre que você criar um módulo, lembre-se de que objetos mutáveis não devem ser
usado como valores padrão na definição de função ou método:
[Good]
def foo(a, b=None):
if b is None:
b = []
[Bad]
def foo(a, b=[]):

O arquivo __init__.py
Um pacote é um diretório que contém um conjunto de módulos e um arquivo chamado
__init__.py. Isso é necessário para fazer o Python tratar os diretórios como contendo
pacotes, impedindo diretórios com um nome comum (como "string") de ocultar módulos
válidos que ocorrem posteriormente no caminho de pesquisa do módulo:

>>> import foldername.filemodulename

No caso mais simples, pode ser apenas um arquivo vazio, mas também pode executar
código de inicialização do pacote ou defina __all__ variável em __init__.py como:
_ _all_ _ = [ "file" , ...]
(sem .py no final).
Além disso, a declaração:
from foldername import *

significa importar todos os objetos do módulo, exceto aqueles cujos nomes começam
com, ou se o módulo tiver uma variável global __all__, a lista.

Verificando a existência de um módulo


Para verificar a existência de um módulo, usamos o sinalizador -c:
$ python -c "import decimas"
Traceback (última chamada mais recente):
File "<string>" , line 1, in <module>
ImportError: nenhum módulo chamado decimas

O __nome__ Variável
Sempre que um módulo é importado, o Python cria uma variável para ele chamada
__name__ e armazena o nome do módulo nessa variável. Nesse caso, todos os abaixo da
declaração:

if _ _name_ _ == '_ _main_ _' :

não será executado. Por outro lado, se executarmos o arquivo .py diretamente, Python
define o __name__ como __main__ e todas as instruções após as instruções acima a
instrução será executada.

Módulos compilados Byte-coded


O código compilado em bytes, na forma de arquivos .pyc, é usado pelo compilador para
acelerar o tempo de inicialização (tempo de carregamento) para programas curtos que
usam muitos módulos.
Quando o interpretador Python é chamado com o sinalizador -O, o código otimizado é
gerado e armazenado em arquivos .pyo. O otimizador remove instruções de afirmação.
Isso também pode ser usado para distribuir uma biblioteca de código Python de uma
forma que é moderadamente difícil de fazer engenharia reversa.

O módulo sys
A variável sys.path é uma lista de strings que determina o tamanho do interpretador
caminho de pesquisa para módulos. Ele é inicializado para um caminho padrão, retirado
da variável de ambiente PYTHONPATH ou de um padrão interno. Você pode modificar
usando operações de lista padrão:
>>> import sys
>>> sys.path.append (/ buffy / lib / python)

As variáveis sys.ps1 e sys.ps2 definem as strings usadas como primárias e secundários


prompts. A variável sys.argv nos permite usar o argumento procedimentos passados na
linha de comando dentro de nossos programas:
import sys
def main ():
'' 'imprimir argumentos da linha de comando' ''
for arg em sys.argv [1:]:
print arg
if _ _name_ _ == "_ _main_ _" :
main( )
O método interno dir () é usado para encontrar quais nomes um módulo define
(todos os tipos de nomes: variáveis, módulos, funções). Retorna uma lista classificada de
cordas:
>>> import sys
>>> dir (sys)
[ __name__ , argv , builtin_module_names , copyright ,
exit , maxint , modules , path , ps1 ,
ps2 , setprofile , settrace , stderr ,
stdin , stdout , version ]

Ele não lista os nomes das funções e variáveis internas. Portanto, podemos ver que dir()
é útil para encontrar todos os métodos ou atributos de um objeto.

4.2 Fluxo de Controle


if
A instrução if substitui as instruções switch ou case em outras linguagens.1
>>> x = int(input("Please enter a number: "))
>>> if x < 0:
... x=0
. . . print "Negative changed to zero"
>>> elif x == 0:
. . . print "Zero"
>>> elif x == 1:
. . . print "Single"
>>> else:
. . . print "More"

recrutramentofamepi@gmail.com

for
A instrução for em Python difere de C ou Pascal. Em vez de sempre iterando sobre uma
progressão aritmética de números (como em Pascal), ou dando ao usuário a capacidade
de definir a etapa de iteração e a condição de parada (como C), a instrução for do Python
itera sobre os itens de qualquer sequência (por exemplo, uma lista ou sequência), na
ordem em que aparecem na sequência:
>>> a = [ "buffy" , "willow" , "xander" , "giles" ]
>>> for i in range ( len (a)):
. . . print (a [i])
buffy
willow
xander
giles
1
Observe que dois pontos são usados com else, elif e em qualquer outro lugar em que um
conjunto seja seguido.
Falso e verdadeiro em Python
False é definido pela constante predefinida False ou pelo objeto especial None, ou por
uma sequência vazia de coleção (string vazia ' ', lista [ ] ou tupla ()).
Qualquer outra coisa é verdadeira. Também é possível atribuir o resultado de uma
comparação ou outra expressão booleana para uma variável:
>>> string1, string2, string3 = '' , 'monsters' , 'aliens'
>>> non_null = string1 ou string2 ou string3
>>> non_null
'monstros'
O Google Python Style Guide sets seguintes regras para o uso implícito Falso em Python:
⋆ Nunca use == ou != Para comparar singletons, como a variável interna None. O uso é
ou não é.
⋆ Cuidado ao escrever if x: quando você realmente quer dizer if x é not None.
⋆ Nunca compare uma variável booleana com False usando ==. Use if not x: em vez de.
Se você precisar distinguir False de None, encadeie o expressões, como if not x e x not
None:.
⋆ Para sequências (strings, listas, tuplas), use o fato de que sequências vazias são False,
portanto, if not seq: ou if seq: é preferível if len (seq): ou if not len (seq) :.
⋆ Ao manipular números inteiros, False implícito pode envolver mais riscos do que
benefício, como lidar acidentalmente com None como 0:
[Good]
if not users: print 'no users'
if foo == 0: self.handle_zero()
if i % 10 == 0: self.handle_multiple_of_ten()

[Bad]
if len(users) == 0: print 'no users'
if foo is not None and not foo: self.handle_zero()
if not i % 10: self.handle_multiple_of_ten()

yield vs. Return


No Python, os geradores são uma maneira conveniente de escrever um iterador. Se um
objeto possui os métodos __iter__ () e __next__ () definidos, diz-se que implementa o
protocolo do iterador. Nesse contexto, o yield é útil.
A diferença entre as palavras-chave yield e return é que a primeira retorna cada valor ao
chamador e, em seguida, somente retorna ao chamador quando todos os valores a serem
retornados estiverem esgotados, e a segunda faz com que o método saia e retorne o
controle ao chamador.
Um ótimo recurso do Python é como ele lida com a iterabilidade. Um iterador é um objeto
de contêiner que implementa o protocolo iterador e se baseia em dois métodos: next, que
retorna o próximo item no contêiner, e iter que retorna o próprio iterador. Como todos os
métodos em Python são virtuais, são livres para modificar como a iterabilidade funciona
em nossas funções (e classes) do jeito que gostamos.
O paradigma de produtividade se torna importante no contexto de geradores, que são uma
ferramenta poderosa para criar iteradores. Geradores são como funções regulares, mas em
vez de retornar um valor final no final, eles usam a declaração de yield para retornar dados
durante a execução. Em outras palavras, os valores são extraídos do iterador um de cada
vez, chamando seu método next () e em cada uma dessas chamadas, o valor da expressão
de yield é retornado. Isto acontece até a chamada final, quando um StopIteration é gerado:
>>> def f (a):
... while a:
... yield a.pop ()

Os geradores são muito robustos e eficientes e devem ser considerados sempre que você
lida com uma função que retorna uma sequência ou cria um loop. Por exemplo, o
programa a seguir implementa uma sequência de Fibonacci usando o paradigma do
iterador:
def fib_generator():
a, b = 0, 1
while True:
yield b
a, b = b, a+b

if _ _name_ _ == '_ _main_ _':


fib = fib_generator()
print(next(fib))
print(next(fib))
print(next(fib))
print(next(fib))

break vs. continue


O comando break, interrompe o menor loop for ou while e alterna o controle para a instrução
seguindo o loop mais interno no qual a instrução break aparece (ela sai do loop). Por outro
lado, continue, continua com a próxima iteração do loop e alterna o controle para o início do
loop (continua com a próxima iteração do loop).
As instruções de loop podem ter uma cláusula else que é executada quando o loop termina
por exaustão da lista (com for) ou quando a condição se torna falsa (com while), mas não
quando o loop é finalizado por uma instrução break.

O método range ()
Este método gera listas contendo progressões aritméticas. É útil quando você precisar
repetir uma sequência de números:
>>> range (10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> range (4, 10)
[4, 5, 6, 7, 8, 9]
>>> range (0, 10, 3)
[0, 3, 6, 9]

O método enumerate ()
Retorna uma tupla com o item e os valores de índice de um iterável. Por exemplo,
podemos usar enumerate para escrever nossa própria função grep, que obtém um palavra
e arquivos da linha de comando e saídas onde a palavra aparece:

[general_problems / modules / grep_word_from_files.py]


sys de importação
def grep_word_from_files ():
palavra = sys.argv [1]
para o nome do arquivo em sys.argv [2:]:
com open (filename) como arquivo :
para lino, linha enumerada ( arquivo , start = 1):
se palavra na linha:
print ( "{0}: {1}: {2: .40}" . formato (nome do arquivo, lino,
line.rstrip ()))
se __name__ == '__main__' :
se len (sys.argv) <2:
print ( "Uso: palavra grep_from_files.py palavra infile1
[infile2 ...] " )
sys.exit ()
mais :
grep_word_from_files ()
O método zip ()
A função zip usa duas ou mais seqüências e cria uma nova sequência
de tuplas em que cada tupla contém um elemento de cada lista:
>>> a = [1, 2, 3, 4, 5]
>>> b = [ 'a' , 'b' , 'c' , 'd' , 'e' ]
>>> zip (a, b)
< objeto zip em 0xb72d65cc>
>>> lista ( zip (a, b))
[(1, 'a' ), (2, 'b' ), (3, 'c' ), (4, 'd' ), (5, 'e' )]
O método de filtro (função, sequência)
Este método retorna uma sequência que consiste nos itens da sequência
para qual função (item) é verdadeira:

Page 80
80
CAPÍTULO 4. ESTRUTURA E MÓDULOS DE PYTHON
>>> def f (x): retorna x% 2! = 0 e x% 3! = 0
>>> f (33)
Falso
>>> f (17)
Verdade
>>> filtro (f, intervalo (2, 25))
[5, 7, 11, 13, 17, 19, 23]
O mapa (função, lista) Função
Uma maneira conveniente de transformar um par de listas em uma lista de pares. Aplica um
para cada item de um iterável e, em seguida, retorna uma lista dos resultados:
>>> def cubo (x): retorna x * x * x
>>> mapa (cubo, intervalo (1, 11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
>>> seq = intervalo (8)
>>> def quadrado (x): retorna x * x
>>> mapa (Nenhum, seq, mapa (quadrado, seq))
[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25), (6, 36), (7,
49)]
A função lambda
A função lambda é uma maneira dinâmica de compactar funções dentro do
código. Por exemplo, a função:
>>> área de definição (b, h):
...
retorno 0,5 * b * h
...
>>> área (5,4)
10.0
poderia ser escrito como
>>> area = lambda b, h: 0,5 * b * h
>>> área (5, 4)
10.0

Page 81
4.3 MANIPULAÇÃO DE ARQUIVOS
81
As funções Lambda são muito úteis para criar chaves em dicionários:
>>> importar coleções
>>> minus_one_dict = collections.defaultdict ( lambda : -1)
>>> point_zero_dict = collections.defaultdict ( lambda : (0, 0))
>>> message_dict = collections.defaultdict ( lambda : "Nenhuma mensagem" )
4.3 Tratamento de arquivos
O manuseio de arquivos é muito fácil no Python. Por exemplo, o trecho abaixo mostra
um arquivo e exclua todas as linhas em branco:
[general_problems / modules / remove_blank_lines.py]
importação os
sys de importação
def read_data (nome do arquivo):
linhas = []
fh = Nenhum
tente :
fh = aberto (nome do arquivo)
para linha em fh:
se line.strip ():
lines.append (linha)
exceto (IOError, OSError) como err:
imprimir (err)
finalmente :
se fh não for Nenhum:
fh.close ()
linhas de retorno
def write_data (linhas, nome do arquivo):
fh = Nenhum
tente :
fh = aberto (nome do arquivo, "w" )
para linha em linha:
fh.write (linha)

Page 82
82
CAPÍTULO 4. ESTRUTURA E MÓDULOS DE PYTHON
exceto (EnvironmentError) como err:
imprimir (err)
finalmente :
se fh não for Nenhum:
fh.close ()
def remove_blank_lines ():
se len (sys.argv) <2:
print ( "Uso: noblank.py infile1 [infile2 ...]" )
para o nome do arquivo em sys.argv [1:]:
lines = read_data (nome do arquivo)
se linhas:
write_data (linhas, nome do arquivo)
se __name__ == '__main__' :
remove_blank_lines ()
Métodos para manipulação de arquivos
O método open (nome do arquivo, modo):
Retorna um objeto de arquivo. O argumento mode é opcional e a leitura será
assumido se for omitido, as outras opções são:
For r para leitura,
Como escrever (um arquivo existente com o mesmo nome será apagado),
⋆ a para anexar (todos os dados gravados no arquivo são automaticamente adicionados ao
o fim),
⋆ er + para leitura e escrita.
fin = aberto (nome do arquivo, codificação = "utf8" )
fout = aberto (nome do arquivo, "w" , codificação = "utf8" )

Page 83
4.3 MANIPULAÇÃO DE ARQUIVOS
83
O método de leitura (tamanho):
Lê alguma quantidade dos dados e a retorna como uma sequência. O tamanho é uma opção
argumento numérico internacional, quando omitido ou negativo, todo o conteúdo
do arquivo será lido e retornado. Se o final do arquivo foi atingido,
read () retornará uma string vazia:
>>> f.read ()
'Este é o arquivo inteiro. \ N'
>>> f.read ()
''
O método readline ():
Lê uma única linha do arquivo. Um caractere de nova linha é deixado no final do
string e só é omitido na última linha do arquivo se o arquivo Isto faz
o valor de retorno inequívoco.
O método readlines ():
Retorne uma lista contendo todas as linhas de dados no arquivo. Se for fornecido um opcional
tamanho do parâmetro, ele lê muitos bytes do arquivo e bastante mais para
complete uma linha e retorne as linhas a partir dela. Isso geralmente é usado para permitir
leitura eficiente de um arquivo grande por linhas, mas sem ter que carregar todo o arquivo
arquivo na memória. Somente linhas completas serão retornadas:
>>> f.readlines ()
[ 'Esta é a primeira linha do arquivo. \ N' , 'Segunda linha do
arquivo \ n ' ]
O método write ():
Grava o conteúdo de uma sequência no arquivo, retornando Nenhum. Escreva bytes / bytear-
objeto de raio para o arquivo, se aberto no modo binário, ou objeto de cadeia, se aberto
no modo de texto:
>>> f.write ( 'Este é um teste \ n' )

Page 84
84
CAPÍTULO 4. ESTRUTURA E MÓDULOS DE PYTHON
Os métodos tell () e seek ():
O método tell () retorna um número inteiro indicando a posição atual do objeto do arquivo
no arquivo, medido em bytes desde o início do arquivo.
Para alterar a posição do objeto de arquivo, use seek (deslocamento, de quê). o
é calculada a partir da adição do deslocamento a um ponto de referência e a referência
ponto de presença é selecionado pelo argumento de quê. Um valor de que de 0
medidas desde o início do arquivo, 1 usa a posição atual do arquivo e
2 usa o final do arquivo como ponto de referência.
O método close ():
Fecha o arquivo e libera todos os recursos do sistema ocupados pelo arquivo aberto. isto
retorna True se o arquivo estiver fechado.
O método input ():
Aceita entrada do usuário. Esta função aceita um argumento de cadeia opcional
(que será impresso no console), aguardará o usuário digitar
em uma resposta e para finalizar pressionando Enter (ou Return).
Se o usuário não digitar nenhum texto, mas apenas pressionar Enter, a função
retorna uma string vazia; caso contrário, ele retornará uma string contendo o que o
digitado pelo usuário, sem nenhum terminador de linha.
>>> def get_int (msg):
...
enquanto True:
...
tente :
...
i = int ( entrada (msg))
...
retornar i
...
exceto ValueError como err:
...
imprimir (err)
>>> age = get_int ( "Digite sua idade:" )
O método peek (n):
Retorna n bytes sem mover a posição do ponteiro do arquivo.

Page 85
4.3 MANIPULAÇÃO DE ARQUIVOS
85
O método fileno ():
Retorna o descritor de arquivo do arquivo subjacente (disponível apenas para objetos de
arquivo
que possuem descritores de arquivo).
O pacote shutil
Este pacote é útil para manipular arquivos no sistema. Por exemplo, o
O fragmento a seguir obtém um arquivo e uma extensão da linha de comando e
produz uma cópia desse arquivo com sua extensão alterada para a sequência especificada:
[general_problems / files / change_ext_file.py]
importação os
sys de importação
fechadura de importação
def change_file_ext ():
se len (sys.argv) <2:
print ( "Uso: change_ext.py filename.old_ext 'new_ext'" )
sys.exit ()
nome = os.path.splitext (sys.argv [1]) [0] + "." + sys.argv [2]
print (nome)
tente :
shutil.copyfile (sys.argv [1], nome)
exceto OSError como err:
imprimir (err)
se __name__ == '__main__' :
change_file_ext ()
O módulo pickle
O módulo pickle pode levar quase qualquer objeto Python (mesmo algumas formas
do código Python!) e converta-o em uma representação de string. Esse processo é

Page 86
86
CAPÍTULO 4. ESTRUTURA E MÓDULOS DE PYTHON
chamado decapagem, onde reconstruir o objeto a partir da representação de string
é chamado de imperceptível.
Se você tiver um objeto xe um objeto de arquivo f que foi aberto para
Ao escrever, a maneira mais simples de selecionar o objeto requer apenas uma linha de
código:
>>> pickle.dump (x, f)
Em seguida, para remover a seleção deste objeto:
>>> x = pickle.load (f)
Exportando dados com Pickle
O exemplo abaixo mostra um uso de picles:
[general_problems / files / export_pickle.py]
picles de importação
def export_pickle (dados, nome do arquivo = 'test.dat' , compress = False):
fh = Nenhum
tente :
se comprimir:
fh = gzip. open (filename, "wb" ) # escreve binário
mais :
fh = open (nome do arquivo, "wb" ) # formato compacto de pickle binário
pickle.dump (dados, fh, pickle.HIGHEST_PROTOCOL)
exceto (EnvironmentError, pickle.PickingError) como err:
print ( "{0}: erro de exportação:
{1} " . Formato (os.path.basename (sys.argv [0], err)))
retornar falso
finalmente :
se fh não for Nenhum:
fh.close ()
def test_export_pickle ():
mydict = { 'a' : 1, 'b' : 2, 'c' : 3}
export_pickle (mydict)

Page 87
4.3 MANIPULAÇÃO DE ARQUIVOS
87
se __name__ == '__main__' :
test_export_pickle ()
Em geral, booleanos, números e seqüências de caracteres podem ser decapados, assim como
as instâncias
de classes e tipos de coleção internos (desde que contenham apenas opções selecionáveis
objetos, isto é, seu ditado é selecionável).
Lendo dados com Pickle
O exemplo abaixo mostra como ler dados em conserva:
[problemas_ gerais / arquivos / importação .py]
picles de importação
def import_pickle (nome do arquivo):
fh = Nenhum
tente :
fh = aberto (nome do arquivo, "rb" )
mydict2 = pickle.load (fh)
return mydict2
exceto (EnvironmentError) como err:
print ( "{0}: erro de importação:
{0} " . Formato (os.path.basename (sys.arg [0]), err))
retornar falso
finalmente :
se fh não for Nenhum:
fh.close ()
def test_import_pickle ():
pkl_file = 'test.dat'
mydict = import_pickle (pkl_file)
print (mydict)
se __name__ == '__main__' :

Page 88
88
CAPÍTULO 4. ESTRUTURA E MÓDULOS DE PYTHON
test_import_pickle ()
O módulo struct
Podemos converter objetos Python para e de uma representação binária adequada
usando struct. Este objeto pode manipular apenas cadeias de caracteres com um comprimento
específico.
struct permite a criação de uma função que pega uma string e retorna um
objeto de bytes com uma contagem de comprimento inteiro e uma sequência de contagem de
comprimento
Bytes codificados em UTF-8 para o texto.
Alguns métodos são: struct.pack () (pega uma string no formato struct e
e retorna um objeto de byte), struct.unpack () (pega um formato e um bytes
ou bytearray e retorna uma tupla de valores) e struct.calcsize ()
(pega um formato e retorna quantos bytes uma estrutura usando o formato
ocupar):
>>> data = struct.pack ( "<2h" , 11, -9)
>>> items = struct.unpack ( "<2h" , dados) # little endian
4.4 Tratamento de erros no Python
Existem dois tipos distintos de erros quando compilamos um programa em
Python: erros de sintaxe (erros de análise) e exceções (erros detectados durante
execução, não incondicionalmente fatal). Embora os erros de sintaxe nunca permitam
programa a ser compilado, exceções só podem ser detectadas em alguns casos
e por esse motivo, eles devem ser manuseados com cuidado.
Tratamento de exceções
Quando uma exceção é gerada e não tratada, o Python gera um retorno
junto com a mensagem de erro da exceção. Um retorno (às vezes chamado de
backtrace) é uma lista de todas as chamadas feitas a partir do ponto em que as chamadas não
tratadas
ocorreu uma exceção no topo da pilha de chamadas.
Podemos lidar com exceções previsíveis usando o try-exceto-finalmente
paradigma:

Page 89
4.4 MANUSEIO DE ERROS NO PITÃO
89
tente :
try_suite
exceto exceção1 como variável1:
exception_suite1
...
exceto exceçãoN como variávelN:
exception_suiteN
Se as instruções no conjunto do bloco try forem executadas sem gerar
uma exceção, os blocos de exceção são ignorados. Se uma exceção for gerada dentro
No bloco try, o controle é imediatamente passado para o conjunto correspondente ao
primeira exceção correspondente. Isso significa que quaisquer declarações no conjunto que
siga o que causou a exceção não será executado:
enquanto 1:
tente :
x = int ( raw_input ( "Digite um número:" ))
quebrar
exceto ValueError:
print "Opa! Esse não era um número válido. Tente novamente ..."
A instrução raise permite que o programador force uma exceção especificada
ocorrer:
cadeia de importação
sys de importação
tente :
f = aberto ( 'meuarquivo.txt' )
s = f.readline ()
i = int (string.strip (s))
exceto IOError, (errno, strerror):
print "Erro de E / S (% s):% s" % (erro, erro)
exceto ValueError:
print "Não foi possível converter dados em um número inteiro."
exceto :
print "Erro inesperado:" , sys.exc_info () [0]
levantar
Também podemos usar outra coisa:

Page 90
90
CAPÍTULO 4. ESTRUTURA E MÓDULOS DE PYTHON
para arg em sys.argv [1:]:
tente :
f = aberto (arg, 'r' )
exceto IOError:
impressão 'não pode abrir' , arg
mais :
print arg, 'has' , len (f.readlines ()), 'linhas'
f.close ()
O G o o g l e Guia de Estilo Python para Exceções
As exceções devem ser usadas com cuidado e devem seguir certas condições:
⋆ Crie exceções como esta: crie MyException ('mensagem de erro') ou
aumentar MyException. Não use o formulário de dois argumentos.
⋆ Módulos ou pacotes devem definir sua própria base específica de domínio
classe de exceção, que deve ser herdada da classe Exception interna.
A exceção básica para um módulo deve ser chamada de Erro.
classe Error (Exception):
passar
⋆ Nunca use catch-all, exceto: declarações, ou catch Exception ou StandardError,
a menos que você esteja levantando novamente a exceção ou no bloco mais externo da sua
thread (e imprimindo uma mensagem de erro).
Imize Minimize a quantidade de código em um bloco try / except. Quanto maior o
corpo da tentativa, maior a probabilidade de uma exceção ser levantada por
uma linha de código que você não esperava gerar uma exceção. Naqueles
casos, o bloco try / except oculta um erro real.
⋆ Use a cláusula finally para executar o código, independentemente de uma exceção ser ou
não
gerado no bloco try. Isso geralmente é útil para limpeza, ou seja, fechar um
Arquivo.
Capt Ao capturar uma exceção, use como em vez de vírgula. Para a prova-
ple:

Page 91
4.4 MANUSEIO DE ERROS NO PITÃO
91
tente :
raise Error
exceto Erro como erro:
passar

Page 92
92
CAPÍTULO 4. ESTRUTURA E MÓDULOS DE PYTHON

Page 93

capítulo 5
Design Orientado a Objetos
Suponha que desejamos definir um objeto em Python para representar um círculo. Nós
poderia lembrar sobre o módulo de coleções do Python e criar um nome
tupla para o nosso círculo:
>>> circle = collections.namedtuple ( "Círculo" , "raio xy" )
>>> círculo
< classe '__main__.Circle' >
>>> círculo = círculo (13, 84, 9)
>>> círculo
Círculo (x = 13, y = 84, raio = 9)
No entanto, muitas coisas estão faltando aqui. Primeiro, não há garantias
que qualquer pessoa que use nossos dados do círculo não digitará uma entrada inválida
valor, como um número negativo para o raio. Segundo, como poderíamos também
associar ao nosso círculo algumas operações que lhe são próprias, como
área ou perímetro?
Para o primeiro problema, podemos ver que a incapacidade de validar quando cria
a criação de um objeto é um aspecto muito ruim de se adotar uma abordagem puramente
processual
em programação. Mesmo se decidirmos incluir muitas exceções ao lidar com o
entradas inválidas para nossos círculos, ainda teríamos um contêiner de dados que não é
intrinsecamente fabricados e validados para seu real objetivo. Imagine agora se tivéssemos
escolheu uma lista em vez da tupla nomeada, como lidaríamos com o fato de que
listas têm propriedades de classificação?
Está claro no exemplo acima que precisamos encontrar uma maneira de criar
um objeto que possui apenas as propriedades que esperamos que ele tenha. Em outro
Em outras palavras, queremos encontrar uma maneira de empacotar dados e restringir seus
métodos. Aquele
93

Page 94
94
CAPÍTULO 5. PROJETO ORIENTADO A OBJETOS
é o que a programação orientada a objetos permite: criar seu próprio
tipo de dados personalizado, que neste exemplo seria uma classe de círculo.
5.1 Classes e Objetos
As classes são a maneira pela qual podemos reunir dados e métodos predefinidos especiais
para
gether. Nós os usamos criando objetos, que são instâncias de um determinado
classe. A forma mais simples de uma classe no Python se parece com o seguinte trecho:
classe ClassName:
<declaração-1>
.g
<declaração- N>
>>> x = Instanciação da classe ClassName () # class
Instanciação de Classe
A instanciação de classe usa notação de função para criar objetos em uma inicialização
conhecida.
estado inicial. A operação de instanciação cria um objeto vazio que possui
individualidade. No entanto, vários nomes (em vários escopos) podem ser vinculados a
o mesmo objeto (também conhecido como alias). No Python, quando um objeto é criado
primeiro, o método especial new () é chamado (o construtor) e depois
init () inicializa.
Atributos
Os objetos têm os atributos de suas classes, que são métodos e dados.
Os atributos de método são funções cujo primeiro argumento é a instância na qual
é chamado para operar (que em Python é convencionalmente chamado de self).
Atributos são qualquer nome após um ponto. Referências a nomes em módulos
são referências de atributo: na expressão modname.funcname, modname
é um objeto de módulo e funcname é um de seus atributos. Os atributos podem
seja somente leitura ou gravável. Os atributos graváveis podem ser excluídos com o del
declaração.

Page 95
5.2 PRINCÍPIOS DA OOP
95
Namespaces
Um espaço para nome é um mapeamento de nomes para objetos. A maioria dos namespaces
é
atualmente implementado como dicionários Python. Exemplos de namespaces são:
o conjunto de nomes internos, os nomes globais em um módulo e os nomes locais em
uma chamada de função. As instruções executadas pela chamada de nível superior de
o intérprete, lendo um arquivo de script ou interativamente, é considerado
parte de um módulo chamado main, para que eles tenham seu próprio espaço para nome
global.
Escopo
Um escopo é uma região textual de um programa Python em que um espaço para nome é
diretamente acessível. Embora os escopos sejam determinados estaticamente, eles são usados
dinamicamente. Os escopos são determinados textualmente: o escopo global de uma função
definido em um módulo é o espaço de nomes desse módulo. Quando uma definição de classe
é
inserido, um novo espaço para nome é criado e usado como o escopo local.
5.2 Princípios de POO
Especialização
Especialização (ou herança) é o procedimento para criar uma nova classe que
herda todos os atributos da superclasse (também chamada de classe base). Qualquer
O método pode ser substituído (reimplementado) em uma subclasse (em Python, todos os
métodos são virtuais). A herança é descrita como um relacionamento é-um.
Além disso G o o g l e Python Style Guide informa que se um herda de classe
de nenhuma outra classe base, devemos herdá-lo explicitamente do Python
classe mais alta, objeto:
classe OuterClass ( objeto ):
classe InnerClass ( objeto ):
Polimorfismo
Polimorfismo (ou ligação dinâmica de método) é o princípio em que métodos
pode ser redefinido dentro das subclasses. Em outras palavras, se tivermos um objeto
de uma subclasse e chamamos um método que também é definido na superclasse,

Page 96
96
CAPÍTULO 5. PROJETO ORIENTADO A OBJETOS
Python usará o método definido na subclasse. Se, por exemplo, precisamos
para recuperar o método da superclasse, podemos chamá-lo facilmente usando o
super().
Por exemplo, todas as instâncias de uma classe personalizada são hashable por padrão em
Python. Isso significa que o atributo hash () pode ser chamado, permitindo que eles
para ser usado como chaves de dicionário e ser armazenado em conjuntos. No entanto, se
voltarmos
implementar o atributo eq (), alteramos essa propriedade (o que pode resultar
em nossas instâncias não sendo mais laváveis).
Agregação
A agregação (ou composição) define o processo em que uma classe inclui um dos
mais variáveis de instância que são de outras classes. É um relacionamento tem-um.
No Python, toda classe usa herança (todas elas são classes personalizadas do
classe base de objeto) e a maioria usa agregação, já que a maioria das classes possui instância
variáveis de vários tipos.
Um primeiro exemplo de classe
Agora temos as ferramentas para escrever nossa primeira classe em Python. O exemplo
abaixo ilustramos como poderíamos escrever um contêiner de dados de círculo 1. usando o
objeto
paradigma de design orientado. Primeiro, criamos uma classe chamada Point com general
atributos de dados e métodos. Então usamos herança para criar um círculo
subclasse dele:
[general_problems / oop / ShapeClass.py]
importar matemática
classe Point:
def __init __ (próprio, x = 0, y = 0):
self.x = x # atributo de dados
self.y = y
def distance_from_origin (self): # atributo de método
retornar math.hypot (self.x, self.y)
1 containers,que é uma estrutura de dados genérica que permite armazenamento e recuperação de
itens de dados independentes do conteúdo.

Page 97
5.2 PRINCÍPIOS DA OOP
97
def __eq __ (próprio, outro):
voltar self.x == other.x e self.y == other.y
def __repr __ (próprio):
retornar "ponto ({0.x! r}, {0.y! r})" . formato (próprio)
def __str __ (próprio):
retornar "({0.x! r}, {0.y! r})" . formato (próprio)
classe Círculo (Ponto):
def __init __ (próprio, raio, x = 0, y = 0):
super () .__ init __ (x, y) # cria / inicializa
self.radius = raio
def edge_distance_from_origin (self):
retornar abs (self.distance_from_origin () - self.radius)
área de def (auto):
retornar math.pi * (self.radius ** 2)
circunferência da definição (auto):
retornar 2 * math.pi * self.radius
def __eq __ (próprio, outro): # evita recursões infinitas
return self.radius == other.radius e super () .__ eq __ (other)
def __repr __ (próprio):
retornar "círculo ({0. raio! r}, {0.x! r})" . formato (próprio)
def __str __ (próprio):
retornar repr (auto)
>>> importar ShapeClass como shape
>>> a = shape.Point (3,4)
>>> a
ponto (3, 4)
>>> repr (a)
«ponto (3, 4)»
>>> str (a)

Page 98
98
CAPÍTULO 5. PROJETO ORIENTADO A OBJETOS
'(3, 4)'
>>> a.distance_from_origin ()
5.0
>>> c = shape.Circle (3,2,1)
>>> c
círculo (3, 2)
>>> repr (c)
'círculo (3, 2)'
>>> str (c)
'círculo (3, 2)'
>>> c.circumference ()
18.84955592153876
>>> c. edge_distance_from_origin ()
0.7639320225002102
5.3 Padrões de design do Python
Padrões de design são uma tentativa de trazer uma definição formal para
estruturas projetadas para engenharia de software. Existem muitos designs diferentes
padrões para resolver diferentes problemas gerais.
Padrão Decorador
Os decoradores (também conhecidos como notação @) são uma ferramenta para especificar
elegantemente alguns
transformação em funções e métodos. O padrão decorador nos permite
para quebrar um objeto que fornece funcionalidade principal a outros objetos que
alterar essa funcionalidade. Por exemplo, o trecho abaixo foi copiado de
o G o o g G e Python Estilo guia:
classe C ( objeto ):
método def (self):
método = my_decorator (método)
pode ser escrito como
classe C ( objeto ):
@my_decorator

Page 99
5.3 PYTHON DESIGN PATTERNS
99
método def (self):
Um exemplo completo de um decorador, para uma função de benchmarking personalizada, é
mostrado abaixo:
[general_problems / oop / do_benchmark.py]
importação aleatória
def benchmark (func):
hora de importação
def wrapper (* args, ** kwargs):
t = time.clock ()
res = func (* args, ** kwargs)
print ( "\ t% s" % func .__ nome__, time.clock () - t)
retornar res
invólucro de retorno
@benchmark
def random_tree (n):
temp = [n para n no intervalo (n)]
para i no intervalo (n + 1):
temp [random.choice (temp)] = random.choice (temp)
temperatura de retorno
se __name__ == '__main__' :
random_tree (10000)
"" "
python3 do_benchmark.py
random_tree 0.04999999999999999
"" "
Os decoradores mais comuns são @classmethod e @staticmethod, por
conversão de métodos comuns em métodos de classe ou estáticos.

Page 100
100
CAPÍTULO 5. PROJETO ORIENTADO A OBJETOS
Padrão do observador
O padrão observador é útil quando queremos ter um objeto central que
mantém certos valores e, em seguida, tendo alguns observadores para criar serializados
cópias desse objeto. Isso pode ser implementado usando o @properties
decorador, colocado antes de nossas funções (antes de def). Isso irá controlar
acesso de tributo, por exemplo, para tornar um atributo como somente leitura. Propriedades
são usados para acessar ou configurar dados em vez de acessadores ou setters simples:
@propriedade
raio de definição (auto):
return self .__ raio
Padrão Singleton
Uma classe segue o padrão singleton se permitir exatamente uma instância de um
certo objeto a existir. Como o Python não possui construtores particulares,
usamos o novo método de classe para garantir que apenas uma instância seja sempre
criada. Quando a substituímos, primeiro verificamos se nossa instância singleton
foi criado. Caso contrário, criamos usando uma chamada de super classe:
>>> classe SinEx:
...
_sing = None
...
def __novo __ (próprio, * args, ** kwargs):
...
se não for self._sing:
...
self._sing = super (SinEx, self) .__ new __ (self, * args,
** kwargs)
...
retornar self._sing
>>> x = SinEx ()
>>> x
<__ main __. Objeto SinEx em 0xb72d680c>
>>> y = SinEx ()
>>> x == y
Verdade
>>> y
<__ main __. Objeto SinEx em 0xb72d680c>

Page 101
5.3 PYTHON DESIGN PATTERNS
101
Os dois objetos são iguais e estão no mesmo endereço; portanto, eles são os
mesmo objeto.

Page 102
102
CAPÍTULO 5. PROJETO ORIENTADO A OBJETOS
Page 103

Capítulo 6
Tópicos avançados
6.1 Multiprocessamento e rosqueamento
Cada programa no sistema operacional é um processo separado. Cada processo
possui um ou mais threads. Se um processo tiver vários encadeamentos, eles parecerão
execute simultaneamente. Duas abordagens podem ser usadas para espalhar a carga de
trabalho em
programas:
Múltiplos processos Múltiplos processos têm regiões separadas de memória
e só pode se comunicar por mecanismos especiais. O processador carrega
e salva um conjunto separado de registros para cada encadeamento. É inconve-
para comunicação e compartilhamento de dados. Isso é tratado pelo
módulo de subprocesso.
Vários threads Vários threads em um único processo têm acesso ao
mesma memória. Eles se comunicam simplesmente compartilhando dados, fornecendo
assegurando uma linha de cada vez, manuseada pelo módulo de rosqueamento.
Os threads compartilham os recursos do processo, incluindo o espaço de heap. Mas
cada thread ainda tem sua própria pilha.
Embora o Python tenha um mecanismo de encadeamento, ele não suporta true
execução paralela. No entanto, é possível usar processos paralelos, que
nos sistemas operacionais modernos são realmente eficientes.
103

Page 104
104
CAPÍTULO 6. TÓPICOS AVANÇADOS
O módulo do subprocesso
Usado para criar um par de programas "pai-filho". O programa pai é
iniciado pelo usuário e, por sua vez, executa instâncias do programa filho, cada
com trabalho diferente a fazer. Usar o processamento filho nos permite tirar o máximo
vantagem do processador multicore e deixa que os problemas de concorrência sejam tratados
pelo sistema operacional.
O módulo de rosqueamento
A complexidade surge quando queremos separar threads para compartilhar dados e
precisamos ter cuidado com uma política de bloqueios e evitar conflitos. Cada
O programa Python possui pelo menos um thread, o thread principal. Para criar vários
threads usamos o módulo threading:
⋆ chamando threading.thread () e passando um objeto que pode ser chamado,
⋆ chamando threading.threadclass e threading.threadsubclass.
A classe queue.queue pode lidar com todo o bloqueio internamente: podemos confiar
para serializar acessos, o que significa que apenas um segmento de cada vez tem acesso
para os dados (FIFO). O programa não será encerrado enquanto houver threads
corrida.
Isso pode criar um problema, uma vez que os threads de trabalho executam
trabalho, eles terminaram, mas tecnicamente ainda estão em execução. A solução
A intenção é transformar threads em daemons. Nesse caso, o programa irá
termina assim que não há threads do daemon em execução. O método
queue.queue.join () bloqueia o final até que a fila esteja vazia.
Mutexes e semáforos
Um mutex é como uma fechadura. Os mutexes são usados na programação paralela para
garantir
que apenas um thread pode acessar um recurso compartilhado por vez. Por exemplo,
digamos que um thread está modificando uma matriz. Quando chegar na metade
a matriz, o processador alterna para outro segmento. Se não estivéssemos usando
mutexes, o thread também poderia tentar modificar a matriz, ao mesmo tempo.
Conceitualmente, um mutex é um número inteiro que começa em 1. Sempre que um
encadeamento
precisa alterar a matriz, ele "bloqueia" o mutex. Isso faz com que o thread aguarde
até que o número seja positivo e diminua em um. Quando o fio

Page 105
6.2 BOAS PRÁTICAS
105
Ao modificar a matriz, ele "desbloqueia" o mutex, causando o número de
aumente em 1. Se tiver certeza de bloquear o mutex antes de modificar a matriz
e para desbloqueá-lo quando terminarmos, sabemos que não haverá dois threads
modifique a matriz ao mesmo tempo.
Os semáforos são mais gerais que os mutexes. O número inteiro de um semáforo pode
começa com um número maior que 1. O número no qual um semáforo começa
é o número de threads que podem acessar o recurso de uma só vez. Semáforos
suporta operações de “espera” e “sinal”, que são análogas às “travas”
e "desbloquear" operações de mutexes.
Impasse e Spinlock
O impasse é um problema que às vezes surge na programação paralela, quando
dois encadeamentos ficam presos indefinidamente. Podemos evitar um conflito se
atribuirmos um
solicitar nossos bloqueios e exigir que os bloqueios sejam sempre adquiridos para
(esta é uma abordagem muito geral e não precisa).
Spinlock é uma forma de espera ocupada, que pode ser útil para alto desempenho
(HPC) (quando todo o sistema é dedicado a um único aplicativo
e exatamente um thread por núcleo). Leva menos tempo que um semáforo.
O G o o g l e Guia de Estilo Python para rosqueamento
Não confie na atomicidade dos tipos internos (tipos de dados como dicionários
parecem ter operações atômicas, mas na verdade são baseadas em
métodos como hash ou eq).
Tipo de dados do módulo de fila como o
maneira preferida de comunicar dados entre threads. Caso contrário, use o
módulo de rosqueamento e suas primitivas de travamento.
6.2 Boas Práticas
Ambientes Virtuais
Quanto mais projetos você tiver, maior a probabilidade de você estar trabalhando
com versões diferentes do próprio Python ou pelo menos versões diferentes do Python
bibliotecas. Por esse motivo, usamos ambientes virtuais (virtualenvs ou venvs).

Page 106
106
CAPÍTULO 6. TÓPICOS AVANÇADOS
Virtualenv
A seguir http://docs.python-guide.org/en/latest/dev/virtualenvs/:
Crie um ambiente virtual:
$ virtualenv venv
Para começar a usar o ambiente virtual, ele precisa ser ativado:
$ fonte venv / bin / ativar
Se você é feito de trabalho em ambiente virtual para o momento,
você pode desativá-lo:
$ desativar
Para excluir um ambiente virtual, basta excluir sua pasta.
Virtualenvwrapper
O Virtualenvwrapper fornece um conjunto de comandos e também coloca todos os seus
ambientes individuais em um só lugar. Seguindo http://virtualenvwrapper.com:
$ pip install virtualenvwrapper
No seu arquivo bashrc :
exportar WORKON_HOME = $ HOME / .virtualenvs
exportar PROJECT_HOME = $ HOME / Devel
fonte / usr / local / bin /virtualenvwrapper.sh
Uso básico:
Crie um ambiente virtual:
$ mkvirtualenv test
Verifique se ele está trabalhando:
$ qual python
Trabalhe em um ambiente virtual:
$ workon test
Desativar ainda é o mesmo:
$ desativar

Page 107
6.2 BOAS PRÁTICAS
107
Deletar:
$ rmvirtualenv test
Verifique a configuração:
$ pip freeze
Outros comandos úteis
lsvirtualenvList
cdvirtualenv
cdsitepackages
lssitepackages
Depuração
O depurador Python, pdb, pode ser encontrado em http://pymotw.com/2/pdb/.
Corrida interativa:
Se você tem algum código em um arquivo de origem e deseja explorá-lo interativamente,
você pode executar o Python com a opção -i, assim: python -i example.py.
Também pode ser usado na linha de comando:
>>> python3 -m pdb program.py
ou adicionado como um módulo como a primeira declaração da função que queremos
examinar:
importar pdb
pdb.set_trace ()
Para executar a inspeção, digite: s para a etapa, p para o ponto en para a próxima
linha, lista para ver as próximas 10 linhas e ajuda para obter ajuda.
Criação de perfil
Se um programa for executado muito lentamente ou consumir muito mais memória do que o
esperado,
o problema é mais frequentemente devido à nossa escolha de algoritmos ou estruturas de
dados

Page 108
108
CAPÍTULO 6. TÓPICOS AVANÇADOS
ou devido a alguma implementação ineficiente. Alguma verificação de desempenho:
⋆ prefira tuplas para listar com dados somente leitura;
⋆ use geradores em vez de grandes listas ou tuplas para iteração;
⋆ ao criar grandes seqüências de caracteres, em vez de concatenar
pequenas, acumule todas elas em uma lista e junte-se à lista de strings
No final. Um bom exemplo é dado pela L o o g G e guia Python Estilo:
[Boa]
items = [ '<tabela>' ]
para last_name, first_name em employee_list:
items.append ( '<tr> <td>% s,% s </td> </tr>' % (sobrenome,
primeiro nome))
items.append ( '</table>' )
employee_table = '' .join (itens)
[Ruim]
employee_table = '<table>'
para last_name, first_name em employee_list:
employee_table + = '<tr> <td>% s,% s </td> </tr>' % (sobrenome,
primeiro nome)
employee_table + = '</table>'
O pacote cProfile:
Fornece um detalhamento detalhado dos tempos de chamada e pode ser usado para encontrar
desempenhos
gargalos de mance.
import cProfile
cProfile.run ( 'main ()' )
Você pode executá-lo digitando:
$ python3 -m cProfile - o profile.day mymodule.py
$ python3 -m pstats

Page 109
6.3 TESTE DE UNIDADE
109
O pacote timeit:
Usado para cronometrar pequenos pedaços do código:
>>> import timeit
>>> timeit.timeit ( "x = 2 + 2" )
0.034976959228515625
>>> timeit.timeit ( "x = soma (intervalo (10))" )
0.92387008666992188
> python -m timeit -s "importa mymodule como m" "m.myfunction ()"
O código a seguir mostra um exemplo simples de como cronometrar uma função:
[general_problems / modules / using_time_module.py]
hora de importação
def sumOfN2 (n):
'' 'um exemplo simples de como cronometrar uma função' ''
start = time.time ()
theSum = 0
para i no intervalo (1, n + 1):
theSum = theSum + i
end = time.time ()
retornar theSum, início final
se __name__ == '__main__' :
n=5
print ( "A soma é% d e é necessária% 10.7f segundos" % sumOfN2 (n))
n = 200
print ( "A soma é% d e é necessária% 10.7f segundos" % sumOfN2 (n))
6.3 Teste de Unidade
É uma boa prática escrever testes para funções, classes e métodos individuais
ods, para garantir que eles se comportem de acordo com as expectativas. Biblioteca padrão
do Python
fornece dois módulos de teste de unidade: doctest e unittest. Há também
ferramentas de teste de terceiros: nariz e py.test.

Page 110
110
CAPÍTULO 6. TÓPICOS AVANÇADOS
Nomenclatura do teste
Acessórios de teste O código necessário para configurar um teste (por exemplo, criando um
arquivo de entrada para testar e excluir posteriormente).
Casos de teste A unidade básica de teste.
Conjuntos de testes Coleção de casos de teste, criados pela subclasse unittest.testcase,
onde cada método tem um nome que começa com "test".
Executor de teste Um objeto que executa um ou mais conjuntos de testes.
doctest
Use-o ao escrever os testes dentro dos documentos e módulos das funções.
Em seguida, basta adicionar três linhas no final:
se __name__ = "__main__"
doctest de importação
doctest.testmod ()
Para executar o doctest do programa, existem duas abordagens:
⋆ Importando o módulo doctest e, em seguida, execute o programa:
$ python3 program.py -v
⋆ Criando um programa de teste separado usando o módulo mais unido, que é
modelado na biblioteca de unittesting JUnit do Java.
doctest de importação
importar mais unittest
import module_to_be_tested
suite = unittest.testsuite ()
suite.addtest (doctest.doctestsuite (module_to_be_tested)
runner = unittest.testtestrunner ()
print (runner.run (suíte))

Page 111
6.3 TESTE DE UNIDADE
111
pytest
Muito fácil de usar: inclua uma função que comece com teste em um arquivo que
começa com o teste:
Instale com:
pip install pytest
Exemplo:
def func (x):
retornar x + 1
def test_answer ():
assert func (3) == 51
Correr com
py.test
python -m pytest
No caso de mais de um teste:
py.test -q test_class.py
Crie um script autônomo pytest:
py.test --genscript = runtests.py
Soltando para pdb:
py.test --pdb
\início

Page 112
112
CAPÍTULO 6. TÓPICOS AVANÇADOS

Page 113

parte II
Algoritmos são divertidos! Está na hora
para adicionar um pouco de molho ao
nosso
voar! Nesta segunda parte, nós
vai aprender como fazer o
computador se tornar nosso incrível
nave espacial!
113

Page 114

Page 115

Capítulo 7
Estruturas de dados abstratas
Um tipo de dados abstratos (ADT) é um modelo matemático para uma determinada classe de
estruturas de dados com comportamento semelhante. Classes diferentes de dados abstratos
tipos têm muitas estruturas de dados diferentes, mas funcionalmente equivalentes, que
implementá-los.
As estruturas de dados podem ser classificadas como contíguas ou vinculadas, dependendo
se eles são baseados em matrizes ou ponteiros. No Python, por exemplo,
estruturas contíguas alocadas (compostas de placas únicas de memória)
inclua cadeias, listas, tuplas e dicionários. Nas seções a seguir iremos
veja exemplos de algumas estruturas e exemplos contínuos mais especializados
de algumas estruturas de dados vinculadas (pedaços distintos de memória ligados entre si
por ponteiros).
7.1 Pilhas
Uma pilha é uma estrutura de dados linear que pode ser acessada apenas em uma de suas
termina (que iremos referir como o topo) para armazenar ou recuperar. Em
Em outras palavras, o acesso ao array de elementos em uma pilha é restrito e eles são um
exemplo de uma estrutura LIFO (last-in-first-out). Você pode pensar em uma pilha como uma
enorme pilha de livros em sua mesa. As pilhas precisam ter as seguintes operações
rodando em O (1):
push Insira um item na parte superior da pilha.
pop Remova um item da parte superior da pilha.
115

Page 116
116
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
top / peek Procure o elemento na parte superior.
vazio / tamanho Verifique se a pilha está vazia ou retorne seu tamanho.
Pilhas em Python podem ser implementadas com listas e os métodos append ()
e pop () (sem um índice explícito):
[adt / stacks / stack.py]
'' 'defina a classe de pilha' ''
classe Stack ( objeto ):
def __init __ (próprio):
self.items = []
def isEmpty (self):
return not bool (self.items)
def push (auto, valor):
self.items.append (valor)
def pop (auto):
valor = self.items.pop ()
se valor:
valor de retorno
mais :
print ( "A pilha está vazia." )
tamanho def (auto):
retornar len (self.items)
def peek (auto):
se self.items:
retornar self.items [-1]
mais :
retornar 'A pilha está vazia.' )
def __repr __ (próprio):
retornar '{}' . formato (self.items)
se __name__ == '__main__' :
Page 117
7.1 PILHAS
117
stack = Stack ()
print ( "A pilha está vazia?" , stack.isEmpty ())
print ( "Adicionando 0 a 10 na pilha ..." )
para i na faixa (10):
stack.push (i)
print ( "Tamanho da pilha:" , stack.size ())
print ( "Stack peek:" , stack.peek ())
print ( "Pop ..." , stack.pop ())
print ( "Stack peek:" , stack.peek ())
print ( "A pilha está vazia?" , stack.isEmpty ())
impressão (pilha)
Outra abordagem para implementar uma pilha é pensar nela como um contêiner
de nós (objetos) seguindo uma ordem LIFO:1
"" "Uma pilha feita de lista vinculada" ""
classe Node ( objeto ):
def __init __ (próprio, valor = Nenhum, ponteiro = Nenhum):
self.value = value
self.pointer = ponteiro
classe Stack ( objeto ):
def __init __ (próprio):
self.head = Nenhum
def isEmpty (self):
return not bool (self.head)
def push (auto, item):
self.head = Nó (item, self.head)
def pop (auto):
se self.head:
node = self.head
self.head = node.pointer
1 Usaremos uma classe de nó semelhante em muitos exemplos no restante dessas notas.

Page 118
118
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
return node.value
mais :
print ( 'A pilha está vazia.' )
def peek (auto):
se self.head:
retornar self.head.value
mais :
print ( 'A pilha está vazia.' )
tamanho def (auto):
node = self.head
count = 0
enquanto nó:
contagem + = 1
node = node.pointer
contagem de retorno
def _printList (self):
node = self.head
enquanto nó:
print (node.value)
node = node.pointer
se __name__ == '__main__' :
stack = Stack ()
print ( "A pilha está vazia?" , stack.isEmpty ())
print ( "Adicionando 0 a 10 na pilha ..." )
para i na faixa (10):
stack.push (i)
stack._printList ()
print ( "Tamanho da pilha:" , stack.size ())
print ( "Stack peek:" , stack.peek ())
print ( "Pop ..." , stack.pop ())
print ( "Stack peek:" , stack.peek ())

Page 119
7.2 QUEUES
119
print ( "A pilha está vazia?" , stack.isEmpty ())
stack._printList ()
As pilhas são adequadas para algoritmos transversais de profundidade em gráficos, como nós
verá nos próximos capítulos.
7.2 Filas
Uma fila, diferentemente de uma pilha, é uma estrutura em que o primeiro elemento na fila
(na parte de trás) será o primeiro a ser desenfileirado (quando estiver na frente), ou seja,
uma fila é uma estrutura FIFO (primeiro a entrar, primeiro a sair). Você pode pensar em uma
fila como
uma fila de pessoas esperando por uma montanha-russa. Matriz de acesso de elementos em
filas é restrito e as filas devem ter as seguintes operações em execução
em O (1):
enfileirar Insira um item na parte de trás da fila.
desenfileirar Remova um item da frente da fila.
peek / front Recupere um item na frente da fila sem removê-lo.
vazio / tamanho Verifique se a fila está vazia ou especifique seu tamanho.
O exemplo abaixo mostra uma classe para uma fila no Python:
[adt / queues / queue.py]
classe Queue ( object ):
def __init __ (próprio):
self.items = []
def isEmpty (self):
return not bool (self.items)
def enfileirar (auto, item):
self.items.insert (0, item)
def dequeue (self):
retornar self.items.pop ()

Page 120
120
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
tamanho def (auto):
retornar len (self.items)
def peek (auto):
retornar self.items [-1]
def __repr __ (próprio):
retornar '{}' . formato (self.items)
se __name__ == '__main__' :
fila = Fila ()
print ( "A fila está vazia?" , queue.isEmpty ())
print ( "Adicionando 0 a 10 na fila ..." )
para i na faixa (10):
queue.enqueue (i)
print ( "Tamanho da fila:" , queue.size ())
print ( "Fila espiada:" , fila.peek ())
print ( "Desfileirar ..." , queue.dequeue ())
print ( "Fila espiada:" , fila.peek ())
print ( "A fila está vazia?" , queue.isEmpty ())
imprimir (fila)
No entanto, aprendemos que o método insert () para listas em Python
é muito ineficiente (lembre-se, as listas funcionam apenas em O (1) quando adicionamos ou
popamos
no final, porque, caso contrário, todos os outros elementos teriam que
seja deslocado na memória). Podemos ser mais espertos do que isso e escrever um
fila usando duas pilhas (duas listas) em vez de uma:
[adt / queues / queue_from_two_stacks.py]
'' 'um exemplo de fila implementada a partir de 2 pilhas' ''
classe Queue ( object ):
def __init __ (próprio):
self.in_stack = []

Page 121
7.2 QUEUES
121
self.out_stack = []
# métodos básicos
def _transfer (self):
while self.in_stack:
self.out_stack.append (self.in_stack.pop ())
def enfileirar (auto, item):
retornar self.in_stack.append (item)
def dequeue (self):
se não for self.out_stack:
self._transfer ()
se self.out_stack:
retornar self.out_stack.pop ()
mais :
retornar "Fila vazia!"
tamanho def (auto):
retornar len (self.in_stack) + len (self.out_stack)
def peek (auto):
se não for self.out_stack:
self._transfer ()
se self.out_stack:
retornar self.out_stack [-1]
mais :
retornar "Fila vazia!"
def __repr __ (próprio):
se não for self.out_stack:
self._transfer ()
se self.out_stack:
retornar '{}' . formato (self.out_stack)
mais :
Page 122
122
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
retornar "Fila vazia!"
def isEmpty (self):
não retorna ( bool (self.in_stack) ou bool (self.out_stack))
se __name__ == '__main__' :
fila = Fila ()
print ( "A fila está vazia?" , queue.isEmpty ())
print ( "Adicionando 0 a 10 na fila ..." )
para i na faixa (10):
queue.enqueue (i)
print ( "Tamanho da fila:" , queue.size ())
print ( "Fila espiada:" , fila.peek ())
print ( "Desfileirar ..." , queue.dequeue ())
print ( "Fila espiada:" , fila.peek ())
print ( "A fila está vazia?" , queue.isEmpty ())
print ( "Imprimindo a fila ..." )
imprimir (fila)
Outra abordagem é implementar uma fila como um contêiner para nós, como
fizemos para pilhas, mas agora os nós são inseridos e removidos em um
Ordem FIFO:
[adt / queues / linked_queue.py]
A fila atua como um contêiner para nós (objetos) que são
inserido e removido de acordo com o FIFO '' '
classe Node ( objeto ):
def __init __ (próprio, valor = Nenhum, ponteiro = Nenhum):
self.value = value
self.pointer = Nenhum
classe LinkedQueue ( objeto ):
def __init __ (próprio):
self.head = Nenhum

Page 123
7.2 QUEUES
123
self.tail = Nenhum
def isEmpty (self):
return not bool (self.head)
def dequeue (self):
se self.head:
valor = self.head.value
self.head = self.head.pointer
valor de retorno
mais :
print ( 'A fila está vazia, não pode desenfileirar.' )
def enfileirar (auto, valor):
node = Node (valor)
se não self.head:
self.head = nó
self.tail = nó
mais :
se self.tail:
self.tail.pointer = nó
self.tail = nó
tamanho def (auto):
node = self.head
num_nodes = 0
enquanto nó:
num_nodes + = 1
node = node.pointer
retornar num_nodes
def peek (auto):
retornar self.head.value
def _print (próprio):
node = self.head
enquanto nó:
print (node.value)
node = node.pointer

Page 124
124
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
se __name__ == '__main__' :
fila = LinkedQueue ()
print ( "A fila está vazia?" , queue.isEmpty ())
print ( "Adicionando 0 a 10 na fila ..." )
para i na faixa (10):
queue.enqueue (i)
print ( "A fila está vazia?" , queue.isEmpty ())
queue._print ()
print ( "Tamanho da fila:" , queue.size ())
print ( "Fila espiada:" , fila.peek ())
print ( "Desfileirar ..." , queue.dequeue ())
print ( "Fila espiada:" , fila.peek ())
queue._print ()
As filas são necessárias para os algoritmos de passagem pela respiração para gráficos, como
veremos nos próximos capítulos.
Deques
Um deque é uma fila dupla, que pode ser vista como uma união de
uma pilha e uma fila:
[adt / queues / dequeue.py]
#! / usr / bin / python
__author__ = "Mari Wahl"
__email__ = "marina.w4hl@gmail.com"
'' 'uma classe para uma fila dupla (também ineficiente)' ''
da fila Import Queue
classe Deque (Fila):
def enqueue_back (self, item):

Page 125
7.2 QUEUES
125
self.items.append (item)
def dequeue_front (self):
retornar self.items.pop (0)
se __name__ == '__main__' :
fila = Deque ()
print ( "A fila está vazia?" , queue.isEmpty ())
print ( "Adicionando 0 a 10 na fila ..." )
para i na faixa (10):
queue.enqueue (i)
print ( "Tamanho da fila:" , queue.size ())
print ( "Fila espiada:" , fila.peek ())
print ( "Desfileirar ..." , queue.dequeue ())
print ( "Fila espiada:" , fila.peek ())
print ( "A fila está vazia?" , queue.isEmpty ())
imprimir (fila)
print ( "\ nAgora usando os métodos de remoção da fila ..." )
print ( "Retirar fila da frente ..." , fila.dequeue_front ())
print ( "Fila espiada:" , fila.peek ())
imprimir (fila)
print ( "Fila de trás ..." )
queue.enqueue_back (50)
print ( "Fila espiada:" , fila.peek ())
imprimir (fila)
Vemos novamente o problema de inserir / remover itens nas listas do Python
em qualquer posição que não seja o fim. A boa notícia é que o Python é
seu amigo e suas coleções.deque nos dá um deque eficiente, com rapidez
anexando e aparecendo de ambas as extremidades:
>>> das coleções import deque
>>> q = deque ([ "buffy" , "xander" , "willow" ])
>>> q
deque ([ 'buffy' , 'xander' , 'salgueiro' ])
>>> q.append ( "giles" )
>>> q

Page 126
126
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
deque ([ 'buffy' , 'xander' , 'willow' , 'giles' ])
>>> q.popleft ()
'buffy'
>>> q.pop ()
'giles'
>>> q
deque ([ 'xander' , 'salgueiro' ])
>>> q.appendleft ( 'anjo' )
>>> q
deque ([ 'anjo' , 'xander' , 'salgueiro' ])
Observe que também podemos especificar o tamanho do nosso deque. Por exemplo, nós
poderia ter escrito q = deque (maxlen = 4) no exemplo acima. Outro
Um método interessante para deques é rotate (n), que girou as etapas de deque n
para a direita ou, se n for negativo, para a esquerda.
Curiosamente, os deques em Python são baseados em uma lista duplamente vinculada,2 não
está
matrizes dinâmicas. Isso significa que operações como inserir um item em qualquer lugar
são rápidos (O (1)), mas o acesso arbitrário ao índice pode ser lento (O (n)).
7.3 Filas prioritárias e pilhas
Uma fila de prioridade é um tipo de dados abstrato que é semelhante a uma fila regular
ou pilha, mas onde cada elemento tem uma prioridade associada a ele. Se dois
elementos têm a mesma prioridade, são servidos de acordo com sua ordem em
a fila.
Uma implementação sensata de uma fila de prioridade é fornecida por dados de heap
estrutura e vamos usá-lo para a maioria dos nossos exemplos.
Montões
Conceitualmente, um heap é uma árvore binária em que cada nó é menor (maior) que
seus filhos. Vamos aprender sobre árvores nos próximos capítulos, mas devemos
lembre-se de que quando modificações são feitas em uma árvore balanceada,
pode reparar sua estrutura com tempos de execução O (logn). Montões são geralmente úteis
2 Listasvinculadas são outra estrutura de dados abstrata sobre a qual aprenderemos no final
deste capítulo. Aqui, duplamente, significa que seus nós têm links para o próximo e para o
nó anterior.

Page 127
7.3 FILAS PRIORITÁRIAS E PILHAS
127
para aplicativos que acessam repetidamente o menor (maior) elemento no
Lista. Além disso, o min- (max-) heap permitirá encontrar o menor (maior)
elemento em O (1) e para extrair / adicionar / substituir em O (lnn).
Pacote heapq do Python
Uma implementação de heap muito eficiente em Python é fornecida pelo módulo heapq
, que fornece funções para inserir e remover itens enquanto mantém o
sequência como uma pilha.
Podemos usar o método heapq.heapify (x) para transformar uma lista em um
heap, no local e em O (n) tempo:
>>> lista1 = [4, 6, 8, 1]
>>> heapq.heapify (lista1)
>>> list1
[1, 4, 8, 6]
Depois de termos um heap, o método heapq.heappush (heap, item) é usado
para empurrar o item para ele:
>>> importar heapq
>>> h = []
>>> heapq.heappush (h, (1, 'comida' ))
>>> heapq.heappush (h, (2, 'divirta-se' ))
>>> heapq.heappush (h, (3, 'trabalho' ))
>>> heapq.heappush (h, (4, 'estudo' ))
>>> h
[(1, 'comida' ), (2, 'divirta-se' ), (3, 'trabalho' ), (4, 'estudo' )]
O método heapq.heappop (heap) é usado para exibir e retornar o menor
item da pilha:
>>> list1
[1, 4, 8, 6]
>>> heapq.heappop (lista1)
1
>>> list1
[4, 6, 8]
O método heapq.heappushpop (heap, item) é usado para enviar o item por push

Page 128
128
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
na pilha, ela aparece e retorna o menor item da pilha. Em
De maneira semelhante, heapq.heapreplace (heap, item) aparecerá e retornará o
menor item da pilha e empurre o novo item. O tamanho da pilha
não muda em nenhum desses métodos e são mais eficientes do que
usando cada método separadamente.
Além disso, muitas operações podem ser feitas usando a propriedade da pilha. Para
O exemplo heapq.merge (* iterables) mesclará várias entradas classificadas em
uma única saída classificada (retornando um iterador):
>>> para x no heapq.merge ([1,3,5], [2,4,6]):
...
imprimir (x, final = "\ n" )
...
1
2
3
4
5
6
Os métodos heapq.nlargest (n, iterable [, key]) e heapq.nsmallest (n,
iterável [, chave]) retornará uma lista com os n maiores e menores elementos
do conjunto de dados definido por iterável.
Uma classe para uma pilha
Se queremos escrever uma aula para nós, a primeira coisa a fazer é definir
um método para criar a propriedade da pilha (heapify). O código a seguir não
que, implementando um heap máximo para uma determinada lista:
[heap / heapify.py]
classe Heapify ( objeto ):
def __init __ (próprio, dados = Nenhum):
self.data = dados ou []
para i no intervalo ( len (data) // 2, -1, -1):
self .__ max_heapify __ (i)
def __repr __ (próprio):
retornar '{}' . formato (self.data)

Page 129
7.3 FILAS PRIORITÁRIAS E PILHAS
129
def pai (eu, i):
retornar i >> 1
def left_child (eu, i):
retornar (i << 1) + 1
def right_child (eu, i):
return (i << 1) + 2 # +2 em vez de +1 porque é
Indexado a 0.
def __max_heapify __ (self, i):
maior = i
left = self.left_child (i)
right = self.right_child (i)
n = len (self.data)
maior = (esquerda <n e self.data [esquerda]> self.data [i]) e
esquerda ou eu
maiores = (direita <n e self.data [direita]>
self.data [maior]) e direito ou maior
se eu não for maior:
self.data [i], self.data [maior] = self.data [maior],
self.data [i]
self .__ max_heapify __ (maior)
def extract_max (self):
n = len (self.data)
max_element = self.data [0]
self.data [0] = self.data [n - 1]
self.data = self.data [: n - 1]
self .__ max_heapify __ (0)
return max_element
def test_Heapify ():
l1 = [3, 2, 5, 1, 7, 8, 2]
h = Heapify (l1)
afirmar (h.extract_max () == 8)
print ( "Testes aprovados!" )

Page 130
130
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
se __name__ == '__main__' :
test_Heapify ()
Uma classe para uma fila prioritária
Para concluir esta seção, o exemplo a seguir mostra como usar o heapq
pacote para implementar uma classe de fila prioritária:
[heap / PriorityQueueClass.py]
heapq de importação
classe PriorityQueue ( objeto ):
'' 'implementa uma classe de fila prioritária' ''
def __init __ (próprio):
self._queue = []
self._index = 0 # comparando o mesmo nível de prioridade
def push (auto, item, prioridade):
heapq.heappush (self._queue, (-priority, self._index, item))
self._index + = 1
def pop (auto):
retornar heapq.heappop (self._queue) [- 1]
item de classe :
def __init __ (nome próprio):
self.name = name
def __repr __ (próprio):
retornar "Item ({! r})" . formato (nome próprio)
def test_PriorityQueue ():
'' 'push e pop são todos O (logN)' ''
q = PriorityQueue ()
q.push (Item ( 'test1' ), 1)
q.push (Item ( 'test2' ), 4)
q.push (Item ( 'test3' ), 3)
assert ( str (q.pop ()) == "Item ('test2')" )

Page 131
7.4 LISTAS LIGADAS
131
print ( 'Testes aprovados!' .center (20, '*' ))
se __name__ == '__main__' :
test_PriorityQueue ()
7.4 Listas vinculadas
Uma lista vinculada é como uma pilha (novos elementos são adicionados ao cabeçalho) ou
uma fila
(novos elementos são adicionados à cauda), exceto que podemos espiar qualquer elemento
na estrutura em O (1) (não apenas os elementos nas extremidades). Em geral, um
lista vinculada é simplesmente uma lista linear de nós que contêm um valor e um ponteiro
(um
referência) para o próximo nó. O último nó é uma exceção, com um valor nulo
referência (que em Python é Nenhum).
Para entender melhor esses conceitos, vamos começar a definir uma classe de nó,
com alguns métodos get e set:
[abstract_structures / linked_lists / node.py]
classe Node ( objeto ):
def __init __ (próprio, valor = Nenhum, ponteiro = Nenhum):
self.value = value
self.pointer = ponteiro
def getData (próprio):
return self.value
def getNext (próprio):
retornar self.pointer
def setData (self, newdata):
self.value = newdata
def setNext (self, newpointer):
self.pointer = newpointer

Page 132
132
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
se __name__ == '__main__' :
L = Nó ( "a" , Nó ( "b" , Nó ( "c" , Nó ( "d" )))))
assert (L.pointer.pointer.value == 'c' )
print (L.getData ())
print (L.getNext (). getData ())
L.setData ( 'aa' )
L.setNext (Nó ( 'e' ))
print (L.getData ())
print (L.getNext (). getData ())
Podemos obter uma lista vinculada LIFO como uma coleção desses nós:
'' 'Implementar uma lista vinculada não ordenada, ou seja, uma lista vinculada LIFO
(como uma pilha) '' '
do nó import Node
classe LinkedListLIFO ( objeto ):
def __init __ (próprio):
self.head = Nenhum
self.length = 0
# imprime o valor de cada nó, começando da cabeça
def _printList (self):
node = self.head
enquanto nó:
print (node.value)
node = node.pointer
# excluir um nó, dado o nó anterior
def _delete (self, prev, node):
self.length - = 1
se não for anterior:
self.head = node.pointer
mais :
prev.pointer = node.pointer
# adicione um novo nó, apontando para o nó anterior
Page 133
7.4 LISTAS LIGADAS
133
# na cabeça
def _add (próprio, valor):
self.length + = 1
self.head = Nó (valor, self.head)
# localizar nó com algum índice
def _find (próprio, índice):
prev = Nenhum
node = self.head
i=0
while node e i <index:
prev = nó
node = node.pointer
i+=1
nó de retorno , anterior, i
# localizar nó por valor
def _find_by_value (auto, valor):
prev = Nenhum
node = self.head
encontrado = 0
while node e não encontrado:
se node.value == value:
found = True
mais :
prev = nó
node = node.pointer
nó de retorno , anterior, encontrado
# localiza e exclui um nó por índice
def deleteNode (próprio, índice):
nó, prev, i = self._find (index)
se índice == i:
self._delete (anterior, nó)
mais :
print ( 'Nó com índice {} não encontrado' . formato (índice))
# localiza e exclui um nó por valor
def deleteNodeByValue (self, value):
nó, anterior, encontrado = self._find_by_value (value)

Page 134
134
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
se encontrado:
self._delete (anterior, nó)
mais :
print ( 'Nó com valor {} não encontrado' . formato (valor))
se __name__ == '__main__' :
ll = LinkedListLIFO ()
para i na faixa (1, 5):
ll._add (i)
print ( 'A lista é:' )
ll._printList ()
print ( 'A lista após excluir o nó com o índice 2:' )
ll.deleteNode (2)
ll._printList ()
print ( 'A lista após excluir o nó com o valor 3:' )
ll.deleteNodeByValue (2)
ll._printList ()
print ( 'A lista após adicionar o nó com o valor 15' )
ll._adicionar (15)
ll._printList ()
print ( "A lista depois de excluir tudo ..." )
para i no intervalo (ll.length-1, -1, -1):
ll.deleteNode (i)
ll._printList ()
Por fim, também podemos escrever uma classe para uma lista vinculada ao FIFO:
[adt / linked_lists / likeslist_fifo.py]
'' 'Uma classe para uma lista vinculada que possui os nós em uma ordem FIFO
(como uma fila) '' '
do nó import Node
classe LinkedListFIFO ( objeto ):
def __init __ (próprio):
self.head = Nenhum

Page 135
7.4 LISTAS LIGADAS
135
self.length = 0
self.tail = None # isso é diferente de ll lifo
# imprime o valor de cada nó, começando da cabeça
def _printList (self):
node = self.head
enquanto nó:
print (node.value)
node = node.pointer
# adicione um nó na primeira posição
# read nunca será alterado novamente enquanto não estiver vazio
def _addFirst (auto, valor):
self.length = 1
node = Node (valor)
self.head = nó
self.tail = nó
# excluir um nó na primeira posição, ou seja,
# quando não houver nó anterior
def _deleteFirst (próprio):
self.length = 0
self.head = Nenhum
self.tail = Nenhum
print ( 'A lista está vazia.' )
# adicione um nó em uma posição diferente da cabeça,
# ie, no final da lista
def _add (próprio, valor):
self.length + = 1
node = Node (valor)
se self.tail:
self.tail.pointer = nó
self.tail = nó
# adicionar nós em geral
def addNode (auto, valor):
se não self.head:
Page 136
136
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
self._addFirst (valor)
mais :
self._add (valor)
# localizar nó com algum índice
def _find (próprio, índice):
prev = Nenhum
node = self.head
i=0
while node e i <index:
prev = nó
node = node.pointer
i+=1
nó de retorno , anterior, i
# excluir nós em geral
def deleteNode (próprio, índice):
se não for self.head ou não self.head.pointer:
self._deleteFirst ()
mais :
nó, prev, i = self._find (index)
se i == índice e nó:
self.length - = 1
se i == 0 ou não anterior:
self.head = node.pointer
mais :
prev.pointer = node.pointer
se não for self.tail == nó:
self.tail = prev
mais :
print ( 'Nó com índice {} não encontrado' . formato (índice))
se __name__ == '__main__' :
ll = LinkedListFIFO ()
para i na faixa (1, 5):
ll.addNode (i)
print ( 'A lista é:' )
ll._printList ()
print ( 'A lista após excluir o nó com o índice 2:' )

Page 137
7.5 TABELAS HASH
137
ll.deleteNode (2)
ll._printList ()
print ( 'A lista após adicionar o nó com o valor 15' )
ll._adicionar (15)
ll._printList ()
print ( "A lista depois de excluir tudo ..." )
para i no intervalo (ll.length-1, -1, -1):
ll.deleteNode (i)
ll._printList ()
As listas vinculadas têm um tamanho dinâmico em tempo de execução e são boas para quando
você tem um número desconhecido de itens para armazenar. A inserção é O (1), mas a
exclusão
e a pesquisa pode ser O (n) porque a localização de um elemento em uma lista vinculada é
lenta
e é feito por uma pesquisa seqüencial. Percorrer uma lista vinculada para trás ou para trás
a classificação é ainda pior, sendo ambos O (n 2 ). Um bom truque para obter exclusão
de um nó i em O (1) está copiando os dados de i + 1 para ie depois para excluir
o nó i + 1.
7.5 Tabelas de Hash
Uma tabela de hash é usada para associar chaves a valores, para que cada chave seja associada
com um ou zero valores. Cada chave deve poder calcular uma função de hash.
A tabela de hash consiste em uma matriz de buckets de hash. Por exemplo, se um hash
o valor é 42 e há 5 baldes, um usa a função mod para decidir
coloque o mapeamento no balde 42 mod 5, que é 2.
Ocorre um problema quando duas chaves hash no mesmo bucket, chamado
uma colisão. Uma maneira de lidar com isso é armazenar uma lista vinculada de valores-
chave
pares para cada balde.
A inserção, remoção e pesquisa levam tempo O (1), desde que o hash
é aleatório. Na pior das hipóteses, cada chave faz o hash do mesmo balde, então cada
operação leva O (n) tempo.
[abstract_structures / Hash_Tables / Hash_Table.py]
#! / usr / bin / python
__author__ = "Mari Wahl"

Page 138
138
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
__email__ = "marina.w4hl@gmail.com"
'' 'projete uma tabela Hash usando encadeamento para evitar colisões.' ''
#import abstract_structures.linked_list.linked_list_fifo
#import abstract_structures.linked_list.node
de linked_list_fifo importar LinkedListFIFO
classe HashTableLL ( objeto ):
def __init __ (próprio, tamanho):
self.size = tamanho
self.slots = []
self._createHashTable ()
def _createHashTable (self):
para i no intervalo (tamanho próprio):
self.slots.append (LinkedListFIFO ())
def _find (self, item):
retornar item% self.size
def _add (self, item):
index = self._find (item)
self.slots [index] .addNode (item)
def _delete (auto, item):
index = self._find (item)
self.slots [index] .deleteNode (item)
def _print (próprio):
para i no intervalo (tamanho próprio):
print ( '\ nSlot {}:' . formato (i))
print (auto.slots [i] ._ printList ())

Page 139
7.5 TABELAS HASH
139
def test_hash_tables ():
H1 = HashTableLL (3)
para i no intervalo (0, 20):
H1._adicionar (i)
H1._print ()
print ( '\ n \ nAgora excluindo:' )
H1._delete (0)
H1._delete (1)
H1._delete (2)
H1._delete (0)
H1._print ()
se __name__ == '__main__' :
test_hash_tables ()

Page 140
140
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
7.6 Exercícios adicionais
Pilhas
Inverter uma String
As pilhas são muito úteis quando os dados precisam ser classificados e recuperados ao
contrário
ordem. No exemplo abaixo, usamos nossa classe Stack para reverter uma string:
[adt / stacks / reverse_string_with_stack.py]
'' 'Usa uma pilha para reverter uma string' ''
da pilha import Stack
def reverse_string_with_stack (str1):
s = Pilha ()
revStr = ''
para c em str1:
s.push (c)
enquanto não s.isEmpty ():
revStr + = s.pop ()
retornar revStr
se __name__ == '__main__' :
str1 = 'Buffy é um Slayer!'
print (str1)
print (string_ reversa_com_ pilha (str1))
Equilibrando parênteses em uma string
O exemplo a seguir usa uma pilha para equilibrar parênteses em uma sequência:
[adt / stacks / balance_parenthesis_str_stack.py]

Page 141
7.6 EXERCÍCIOS ADICIONAIS
141
'' 'use uma pilha para equilibrar as parenteses de uma string' ''
da pilha import Stack
def balance_par_str_with_stack (str1):
s = Pilha ()
equilibrado = Verdadeiro
index = 0
enquanto índice < len (str1) e balanceado:
símbolo = str1 [índice]
se símbolo == "(" :
s.push (símbolo)
mais :
se s.isEmpty ():
equilibrado = Falso
mais :
s.pop ()
índice = índice + 1
se equilibrado e s.isEmpty ():
return True
mais :
retornar falso
se __name__ == '__main__' :
print (balance_par_str_with_stack ( '((())))' ))
print (balance_par_str_with_stack ( '(()' )))

Page 142
142
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
Decimal para binário
O exemplo a seguir usa uma pilha para transformar um número decimal em binário
número:
[abstract_structures / stacks / dec2bin_with_stack.py]
'' 'transforma um número decimal em um número binário com uma pilha' ''
da pilha import Stack
def dec2bin_with_stack (decnum):
s = Pilha ()
str_aux = ''
enquanto decnum> 0:
dig = decnum% 2
decnum = decnum // 2
s.push (dig)
enquanto não s.isEmpty ():
str_aux + = str (s.pop ())
return str_aux
se __name__ == '__main__' :
decnum = 9
assert (dec2bin_with_stack (decnum) == '1001' )
Pilha com uma pesquisa constante
O exemplo a seguir implementa uma pilha que possui pesquisa mínima O (1):
[adt / stacks / stack_with_min.py]
'' 'Uma pilha com uma pesquisa mínima' ''

Page 143
7.6 EXERCÍCIOS ADICIONAIS
143
da pilha import Stack
classe NodeWithMin ( objeto ):
def __init __ (próprio, valor = Nenhum, mínimo = Nenhum):
self.value = value
self.minimum = mínimo
classe StackMin (Stack):
def __init __ (próprio):
self.items = []
self.minimum = Nenhum
def push (auto, valor):
se self.isEmpty () ou self.minimum> value:
self.minimum = value
self.items.append (NodeWithMin (valor, self.minimum))
def peek (auto):
retornar self.items [-1] .value
def peekMinimum (auto):
retornar self.items [-1] .minimum
def pop (auto):
item = self.items.pop ()
se item:
se item.value == self.minimum:
self.minimum = self.peekMinimum ()
retornar item.value
mais :
print ( "A pilha está vazia." )
def __repr __ (próprio):
aux = []

Page 144
144
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
para i em self.items:
aux.append (valor i)
retornar '{}' . formato (aux)
se __name__ == '__main__' :
stack = StackMin ()
print ( "A pilha está vazia?" , stack.isEmpty ())
print ( "Adicionando 0 a 10 na pilha ..." )
para i no intervalo (10, 0, -1):
stack.push (i)
para i na faixa (1, 5):
stack.push (i)
impressão (pilha)
print ( "Tamanho da pilha:" , stack.size ())
print ( "Stack peek e peekMinimum:" , stack.peek (),
stack.peekMinimum ())
print ( "Pop ..." , stack.pop ())
print ( "Stack peek e peekMinimum:" , stack.peek (),
stack.peekMinimum ())
print ( "A pilha está vazia?" , stack.isEmpty ())
impressão (pilha)
Conjunto de pilhas
O exemplo a seguir implementa um conjunto de pilhas. Cria uma nova pilha
quando a pilha anterior exceder a capacidade. Os métodos push e pop são
idêntico a uma única pilha:
[adt / stacks / set_of_stacks.py]
"" "define uma classe para um conjunto de pilhas" ""
da pilha import Stack
classe SetOfStacks (Stack):

Page 145
7.6 EXERCÍCIOS ADICIONAIS
145
def __init __ (auto, capacidade = 4):
self.setofstacks = []
self.items = []
self.capacity = capacidade
def push (auto, valor):
se self.size ()> = self.capacity:
self.setofstacks.append (self.items)
self.items = []
self.items.append (valor)
def pop (auto):
valor = self.items.pop ()
se self.isEmpty () e self.setofstacks:
self.items = self.setofstacks.pop ()
valor de retorno
def sizeStack (próprio):
retornar len (self.setofstacks) * self.capacity + self.size ()
def __repr __ (próprio):
aux = []
para s em self.setofstacks:
aux.extend (s)
aux.extend (self.items)
retornar '{}' . formato (aux)
se __name__ == '__main__' :
capacidade = 5
stack = SetOfStacks (capacidade)
print ( "A pilha está vazia?" , stack.isEmpty ())
print ( "Adicionando 0 a 10 na pilha ..." )
para i na faixa (10):
stack.push (i)

Page 146
146
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
impressão (pilha)
print ( "Tamanho da pilha:" , stack.sizeStack ())
print ( "Stack peek:" , stack.peek ())
print ( "Pop ..." , stack.pop ())
print ( "Stack peek:" , stack.peek ())
print ( "A pilha está vazia?" , stack.isEmpty ())
impressão (pilha)
Filas
Deques para Palíndromes
[adt / queues / palindrome_checker_with_deque.py]
cadeia de importação
importar coleções
do deque de importação Deque
STRIP = string.whitespace + string.punctuation + "\" '"
"" "Usando nossa classe deque e a classe deque do Python" ""
def palindrome_checker_with_deque (str1):
d1 = Deque ()
d2 = collections.deque ()
para s em str1.lower ():
se é não em STRIP:
d2.append (s)
d1.enqueue (s)
eq1 = True
enquanto d1.size ()> 1 e eq1:
if d1.dequeue_front ()! = d1.dequeue ():
eq1 = False

Page 147
7.6 EXERCÍCIOS ADICIONAIS
147
eq2 = True
enquanto len (d2)> 1 e eq2:
if d2.pop ()! = d2.popleft ():
eq2 = False
retornar eq1, eq2
se __name__ == '__main__' :
str1 = 'Senhora Im Adam'
str2 = 'Buffy é um Slayer'
print (palindrome_checker_with_deque (str1))
print (palindrome_checker_with_deque (str2))
Usando uma fila para modelar um abrigo de animais
[adt / fila / animal_shelter.py]
"" "Uma aula para um abrigo de animais com duas filas" ""
classe Node ( objeto ):
def __init __ (self, animalName = None, animalKind = None,
ponteiro = Nenhum):
self.animalName = animalName
self.animalKind = animalKind
self.pointer = ponteiro
self.timestamp = 0
classe AnimalShelter ( objeto ):
def __init __ (próprio):
self.headCat = Nenhum
self.headDog = Nenhum
self.tailCat = Nenhum
self.tailDog = Nenhum
self.animalNumber = 0

Page 148
148
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
# Enfileirar qualquer animal
def enfileirar (self, animalName, animalKind):
self.animalNumber + = 1
newAnimal = Nó (animalName, animalKind)
newAnimal.timestamp = self.animalNumber
se animalKind == 'gato' :
se não for self.headCat:
self.headCat = newAnimal
se self.tailCat:
self.tailCat.pointer = newAnimal
self.tailCat = newAnimal
elif animalKind == 'cachorro' :
se não self.headDog:
self.headDog = newAnimal
se self.tailDog:
self.tailDog.pointer = newAnimal
self.tailDog = newAnimal
# Métodos de desenfileiramento
def dequeueDog (self):
se self.headDog:
newAnimal = self.headDog
self.headDog = newAnimal.pointer
return str (newAnimal.animalName)
mais :
retornar 'Não cães!'
def dequeueCat (self):
se self.headCat:
newAnimal = self.headCat
self.headCat = newAnimal.pointer
return str (newAnimal.animalName)
mais :

Page 149
7.6 EXERCÍCIOS ADICIONAIS
149
retornar 'Sem gatos!'
def dequeueAny (self):
se self.headCat e não self.headDog:
retornar self.dequeueCat ()
elif self.headDog e não self.headCat:
retornar self.dequeueDog ()
elif self.headDog e self.headCat:
se self.headDog.timestamp <self.headCat.timestamp:
retornar self.dequeueDog ()
mais :
retornar self.dequeueCat ()
mais :
return ( 'Sem animais!' )
def _print (próprio):
print ( "Gatos:" )
cats = self.headCat
enquanto gatos:
print (cats.animalName, cats.animalKind)
cats = cats.pointer
print ( "Cães:" )
dogs = self.headDog
enquanto cães:
print (dogs.animalName, dogs.animalKind)
dogs = dogs.pointer
se __name__ == '__main__' :
qs = AnimalShelter ()
qs.enqueue ( 'bob' , 'cat' )
qs.enqueue ( 'mia' , 'gato' )
qs.enqueue ( 'yoda' , 'cachorro' )
qs.enqueue ( 'lobo' , 'cachorro' )
qs._print ()

Page 150
150
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
print ( "Deque um cachorro e um gato ..." )
qs.dequeueDog ()
qs.dequeueCat ()
qs._print ()
Filas prioritárias e pilhas
O exemplo a seguir usa o pacote heapq do Python para encontrar o N maior e
itens menores em uma sequência:
[adt / heap / find_N_largest_smallest_items_seq.py]
heapq de importação
def find_N_largest_items_seq (seq, N):
retornar heapq.nlargest (N, seq)
def find_N_smallest_items_seq (seq, N):
retornar heapq.nsmallest (N, seq)
def find_smallest_items_seq_heap (seq):
'' 'encontre os itens menores em uma sequência usando o heapify primeiro' ''
'' 'heap [0] é sempre o menor item' ''
heapq.heapify (seq)
retornar heapq.heappop (seq)
def find_smallest_items_seq (seq):
'' 'se for apenas um item, min () é mais rápido' ''
retorno mínimo (seq)
def find_N_smallest_items_seq_sorted (seq, N):
'' 'N ~ len (seq), melhor classificar' ''
retorno classificado (seq) [: N]
def find_N_largest_items_seq_sorted (seq, N):
'' 'N ~ len (seq), melhor classificar' ''
retornar classificado (seq) [ len (seq) -N:]

Page 151
7.6 EXERCÍCIOS ADICIONAIS
151
def test_find_N_largest_smallest_items_seq (module_name = 'this
module ' ):
seq = [1, 3, 2, 8, 6, 10, 9]
N=3
afirmar (find_N_largest_items_seq (seq, N) == [10, 9, 8])
assert (find_N_largest_items_seq_sorted (seq, N) == [8, 9, 10])
afirmar (find_N_smallest_items_seq (seq, N) == [1,2,3])
assert (find_N_smallest_items_seq_sorted (seq, N) == [1,2,3])
assert (find_smallest_items_seq (seq) == 1)
assert (find_smallest_items_seq_heap (seq) == 1)
s = 'Testes em {name} têm {con}!'
print ( formato . s (nome = nome do módulo, con = 'passado' ))
se __name__ == '__main__' :
test_find_N_largest_smallest_items_seq ()
O exemplo a seguir usa o pacote heapq do Python para mesclar dois
seqüências com pouca sobrecarga:3
[adt / heap / merge_sorted_seqs.py]
heapq de importação
def merge_sorted_seqs (seq1, seq2):
resultado = []
para c no heapq.merge (seq1, seq2):
result.append (c)
resultado de retorno
def test_merge_sorted_seq (module_name = 'este módulo' ):
seq1 = [1, 2, 3, 8, 9, 10]
seq2 = [2, 3, 4, 5, 6, 7, 9]
seq3 = seq1 + seq2
assert (merge_sorted_seq (seq1, seq2) == classificado (seq3))
s = 'Testes em {name} têm {con}!'
3 Observe que o resultado não seria classificado se apenas adicionássemos as duas listas.

Page 152
152
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
print ( formato . s (nome = nome do módulo, con = 'passado' ))
se __name__ == '__main__' :
test_merge_sorted_seq ()
Lista vinculada
Encontre o k-ésimo elemento no final de uma lista vinculada
[adt / links_lists / find_kth_from_the_end.py]
'' 'Encontre o mésimo penúltimo elemento de uma lista vinculada.
Uma opção é ter dois ponteiros, separados por m. P1 começa em
as raízes
(p1 = self.root) e p2 é o ponteiro m-behinf, criado
quando p1 está em m.
Quando p1 chega ao fim, p2 é o nó. '' '
de linked_list_fifo importar LinkedListFIFO
do nó import Node
classe LinkedListFIFO_find_kth (LinkedListFIFO):
def find_kth_to_last (self, k):
p1, p2 = self.head, self.head
i=0
enquanto p1:
se eu> k:
tente :
p2 = p2.pointer
exceto :
quebrar
p1 = p1.pointer
i+=1
return p2.value
se __name__ == '__main__' :
ll = LinkedListFIFO_find_kth ()

Page 153
7.6 EXERCÍCIOS ADICIONAIS
153
para i na faixa (1, 11):
ll.addNode (i)
print ( 'A lista vinculada:' )
print (ll._printList ())
k=3
k_from_last = ll.find_kth_to_last (k)
print ( "O elemento% dth até o último LL do tamanho% d é% d"
% (k, comprimento ll, k_do_ltimo))
Particionando uma Lista Vinculada em um Elementos
[adt / linked_lists / part_linked_list.py]
'' 'Esta função divide uma lista vinculada em um valor, em que
tudo menor que esse valor
vai para a frente e tudo grande vai para trás: '' '
de linked_list_fifo importar LinkedListFIFO
do nó import Node
def partList (ll, n):
more = LinkedListFIFO ()
less = LinkedListFIFO ()
node = ll.head
enquanto nó:
item = node.value
se o item <n:
less.addNode (item)
item elif > n:
more.addNode (item)
node = node.pointer

Page 154
154
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
less.addNode (n)
nodemore = more.head
enquanto nodemore:
less.addNode (nodemore.value)
nodemore = nodemore.pointer
retornar menos
se __name__ == '__main__' :
ll = LinkedListFIFO ()
l = [6, 7, 3, 4, 9, 5, 1, 2, 8]
para i em l:
ll.addNode (i)
print ( 'Antes da peça' )
ll._printList ()
print ( 'Após a parte' )
newll = partList (ll, 6)
newll._printList ()
Um FIFO de lista vinculada duplicada
[adt / linked_lists / doubled_linked_list_fifo.py]
'' 'Implemente uma lista com links duplos, que é muito simples, nós apenas
precisa herda
de uma classe de lista vinculada e adicione um atributo para o anterior. '' '
de linked_list_fifo importar LinkedListFIFO
classe dNode ( objeto ):
def __init __ (próprio, valor = Nenhum, ponteiro = Nenhum, anterior = Nenhum):
self.value = value
self.pointer = ponteiro

Page 155
7.6 EXERCÍCIOS ADICIONAIS
155
self.previous = anterior
classe dLinkList (LinkedListFIFO):
# imprime o valor de cada nó, começando pela cauda
def printListInverse (self):
node = self.tail
enquanto nó:
print (node.value)
tente :
node = node.previous
exceto :
quebrar
# adicione um nó em uma posição diferente da cabeça,
# ie, no final da lista
def _add (próprio, valor):
self.length + = 1
node = dNode (valor)
se self.tail:
self.tail.pointer = nó
node.previous = self.tail
self.tail = nó
# excluir um nó em alguma posição
def _delete (auto, nó):
self.length - = 1
node.previous.pointer = node.pointer
se não for node.pointer:
self.tail = node.previous
# localizar nó com algum índice
def _find (próprio, índice):
node = self.head
i=0
while node e i <index:
node = node.pointer
i+=1
nó de retorno , i

Page 156
156
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
# excluir nós em geral
def deleteNode (próprio, índice):
se não for self.head ou não self.head.pointer:
self._deleteFirst ()
mais :
nó, i = self._find (índice)
se i == índice:
self._delete (nó)
mais :
print ( 'Nó com índice {} não encontrado' . formato (índice))
se __name__ == '__main__' :
das coleções import Counter
ll = dLinkList ()
para i na faixa (1, 5):
ll.addNode (i)
print ( 'Imprimindo a lista ...' )
ll._printList ()
print ( 'Agora, imprimindo a lista inversamente ...' )
ll.printListInverse ()
print ( 'A lista após adicionar o nó com o valor 15' )
ll._adicionar (15)
ll._printList ()
print ( "A lista depois de excluir tudo ..." )
para i no intervalo (ll.length-1, -1, -1):
ll.deleteNode (i)
ll._printList ()
Verifique se uma lista vinculada é um Palíndromo
[adt / linked_lists / check_pal.py]
'' 'Dada uma lista vinculada, verifique se os nós formam um palíndromo' ''
de linked_list_fifo import LinkedListFIFO, Node
do nó import Node
Page 157
7.6 EXERCÍCIOS ADICIONAIS
157
def isPal (l):
se len (l1) <2:
return True
se l1 [0]! = l1 [-1]:
retornar falso
return isPal (l1 [1: -1])
def checkllPal (ll):
node = ll.head
l = []
enquanto nó:
l.append (node.value)
node = node.pointer
return isPal (l)
se __name__ == '__main__' :
ll = LinkedListFIFO ()
l1 = [1, 2, 3, 2, 1]
para i em l1:
ll.addNode (i)
assert (checkllPal (ll) == True)
ll.addNode (2)
ll.addNode (3)
assert (checkllPal (ll) == False)
Somando dígitos em listas vinculadas
[adt / linked_lists / sum_linked_list.py]

Page 158
158
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
'' 'Supondo duas listas vinculadas representando números, como em
cada um deles
nós eles carregam um dígito. Esta função soma os dois números
que estes
duas listas vinculadas representam, retornando uma terceira lista representando
a soma:'''
de linked_list_fifo importar LinkedListFIFO
do nó import Node
classe LinkedListFIFOYield (LinkedListFIFO):
# imprime o valor de cada nó, começando da cabeça
def _printList (self):
node = self.head
enquanto nó:
yield (node.value)
node = node.pointer
def sumlls (l1, l2):
lsum = LinkedListFIFOYield ()
dig1 = l1.head
dig2 = l2.head
ponteiro = 0
enquanto dig1 e dig2:
d1 = dig1.value
d2 = dig2.value
sum_d = d1 + d2 + ponteiro
se soma_d> 9:
ponteiro = soma_d // 10
lsum.addNode (sum_d% 10)
mais :
lsum.addNode (sum_d)

Page 159
7.6 EXERCÍCIOS ADICIONAIS
159
ponteiro = 0
dig1 = dig1.pointer
dig2 = dig2.pointer
se dig1:
sum_d = ponteiro + dig1.value
se soma_d> 9:
lsum.addNode (sum_d% 10)
mais :
lsum.addNode (sum_d)
dig1 = dig1.pointer
se dig2:
sum_d = ponteiro + dig2.value
se soma_d> 9:
lsum.addNode (sum_d% 10)
mais :
lsum.addNode (sum_d)
dig2 = dig2.pointer
retornar lsum
se __name__ == '__main__' :
l1 = LinkedListFIFOYield () # 2671
l1.addNode (1)
l1.addNode (7)
l1.addNode (6)
l1.addNode (2)
l2 = LinkedListFIFOYield () # 455
l2.addNode (5)
l2.addNode (5)
l2.addNode (4)
lsum = somas (l1, l2)
l = lista (lsum._printList ())
para i invertido (l):
imprimir i

Page 160
160
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS
Encontre uma lista vinculada circular
[adt / linked_lists / circular_linked_list.py]
'' 'implementa uma função para ver se uma lista vinculada é circular.
Para implementar isso, precisamos apenas de dois ponteiros com diferentes
passos (por exemplo, um vai duas vezes mais rápido) '' '
de linked_list_fifo importar LinkedListFIFO
do nó import Node
classe cicularLinkedListFIFO (LinkedListFIFO):
def _add (próprio, valor):
self.length + = 1
node = Node (valor, self.head)
se self.tail:
self.tail.pointer = nó
self.tail = nó
def isCircularll (ll):
p1 = ll.head
p2 = ll.head
enquanto p2:
tente :
p1 = p1.pointer
p2 = p2.pointer.pointer
exceto :
quebrar
se p1 == p2:
return True
retornar falso
se __name__ == '__main__' :
ll = LinkedListFIFO ()

Page 161
7.6 EXERCÍCIOS ADICIONAIS
161
para i na faixa (10):
ll.addNode (i)
ll._printList ()
print (isCircularll (ll))
lcirc = cicularLinkedListFIFO ()
para i na faixa (10):
lcirc.addNode (i)
print (isCircularll (lcirc))

Page 162
162
CAPÍTULO 7. ESTRUTURAS DE DADOS ABSTRATAS

Page 163

Capítulo 8
Análise Assintótica
A análise assintótica é um método para descrever o comportamento limitante e os
desempenho de algoritmos quando aplicado a conjuntos de dados de entrada muito grandes.
Para
entender por que a análise assintótica é importante, suponha que você precise classificar
um bilhão de números (n = 10 9 )1 em um computador desktop comum. Suponha
que este computador tem um tempo de clock de CPU de 1 GHz, o que significa
aproximadamente
que executa 10 9 ciclos (ou operações) do processador por segundo.2 Então, por
um algoritmo que tenha um tempo de execução de O (n 2 ), levaria aproximadamente um
bilhões de segundos para concluir a classificação (no pior caso), o que significa uma
Ano inteiro!
Outra maneira de visualizar a importância da análise assintótica é
olhando corretamente para o comportamento da função. Na Fig. 8, temos muitas classes
de funções plotadas juntas e é claro que quando n aumenta, o número
de operações para qualquer algoritmo polinomial ou exponencial é inviável.
8.1 Classes de complexidade
Uma classe de complexidade é um conjunto de problemas com complexidade relacionada.
Uma redução
é uma transformação de um problema em outro problema que é pelo menos
1 Lembre-se de que para gigabytes de memória significa 1024 3 = 2 30 bytes e para armazenamento
significa 1000 3 = 10 9 bytes. Além disso, os números inteiros geralmente levam 2 ou 4 bytes cada. No entanto, para
isso
Por exemplo, estamos simplificando isso dizendo que um 'número' tem 1 byte.
2 Neste exercício, não estamos considerando outros fatores que tornariam o processamento
mais lento, como latência de RAM, operações de cache de cópia, etc.
163

Page 164
164
CAPÍTULO 8. ANÁLISE ASSINTÓTICA
Figura 8.1: Comportamento assintótico de muitas classes de funções.
tão difícil quanto o problema original. A redução mais comumente usada
é uma redução no tempo polinomial, o que significa que o processo de redução leva
tempo polinomial. Um problema é difícil para uma classe de problemas, se todos os
problemas
nele pode ser reduzido ao problema original.
P
A classe de complexidade dos problemas de decisão que podem ser resolvidos em uma
determinada
máquina de Turing istic em tempo polinomial (no pior dos casos). Se pudermos transformar
um problema em um problema de decisão, o resultado pertenceria a P.
NP
A classe de complexidade dos problemas de decisão que podem ser resolvidos de maneira
não
máquina determinística de Turing (NTM) em tempo polinomial. Em outras palavras,
inclui todos os problemas de decisão cujas instâncias sim podem ser resolvidas em
tempo inicial com o NTM. Um problema é chamado completo se todos os problemas no
classe são reduzidas a ele. Portanto, a subclasse chamada NP-complete (NPC)
contém os problemas mais difíceis em todo o NP.

Page 165
8.2 RECURSÃO
165
Qualquer problema que seja pelo menos tão difícil (determinado pelo tempo polinomial
redução) como qualquer problema no PN, mas que não precisa estar em PN, é
chamado NP-hard. Por exemplo, localizando a rota mais curta através de um gráfico,
que é chamado de problema do vendedor ambulante (ou Salesrep) (TSP).
P = NP?
A classe co-NP é a classe dos complementos dos problemas de PN. Para cada
Resposta "sim", temos o "não" e vice-versa. Se NP é verdadeiramente assimétrico,
então essas duas classes são diferentes. Embora haja sobreposição entre eles
porque todo P está em sua interseção: os casos yes e no no
P pode ser resolvido em tempo polinomial com um NTM.
O que aconteceria se um NPC fosse encontrado na interseção de N e co-
NP? Primeiro, isso significaria que todo o NP estaria dentro do co-NP, então nós
mostraria NP = co-NP e a assimetria desapareceria. Segundo,
como todo P está nessa interseção, P = NP. Se P = NP, poderíamos resolver
qualquer problema (de decisão) que tivesse uma solução prática (verificável).
No entanto, acredita-se (fortemente) que NP e co-NP são diferentes. Para
Por exemplo, nenhuma solução polinomial para o problema de fatorar números foi
encontrado, e esse problema está no NP e no co-NP.
8.2 Recursão
As três leis da recursão são:
1. Um algoritmo recursivo deve ter um caso base.
2. Um algoritmo recursivo deve alterar seu estado e avançar em direção à base
caso.
3. Um algoritmo recursivo deve se chamar, recursivamente.
Para cada chamada recursiva, a função recursiva deve alocar memória no
a pilha de argumentos, endereço de retorno e variáveis locais, custando tempo para
empurre e coloque esses dados na pilha. Algoritmos recursivos levam pelo menos
O (n) espaço em que n é a profundidade da chamada recursiva.
A recursão é muito cara quando existem cálculos duplicados e / ou
há sobreposição entre subproblemas. Em alguns casos, isso pode causar a pilha

Page 166
166
CAPÍTULO 8. ANÁLISE ASSINTÓTICA
transbordar. Por esse motivo, onde os subproblemas se sobrepõem, soluções iterativas
pode ser uma abordagem melhor. Por exemplo, no caso dos Fibonacci
série, a solução iterativa é executada em O (n) enquanto a solução recursiva é executada
em tempo de execução exponencial.
Relações Recursivas
Para descrever o tempo de execução de funções recursivas, usamos relações recursivas:
T (n) = a · T (g (n)) + f (n),
onde a representa o número de chamadas recursivas, g (n) é o tamanho de cada
subproblema a ser resolvido recursivamente ef (n) é qualquer trabalho extra feito no
função. A tabela a seguir mostra exemplos de relações recursivas:
T (n) = T (n - 1) + 1
Em)
Processando uma sequência
T (n) = T (n - 1) + n
O (n 2 )
Problema de aperto de mão
T (n) = 2T (n - 1) + 1
O (2 n )
Torres de Hanói
T (n) = T (n / 2) + 1
O (ln n)
Pesquisa binária
T (n) = T (n / 2) + n
Em)
Seleção aleatória
T (n) = 2T (n / 2) + 1
Em)
Árvore transversal
T (n) = 2T (n / 2) + n
O (n ln n) Classifique por dividir e conquistar
Dividir e conquistar algoritmos
As recorrências para os algoritmos de divisão e conquista têm a forma:
T (n) = a · T (n / b) + f (n),
onde temos chamadas recursivas, cada uma com uma porcentagem 1 / b do conjunto de dados.
Resumindo, o algoritmo faz f (n) do trabalho. Para alcançar o problema de
T (1) = 1 na instância final (folha, como aprenderemos quando estudamos árvores),
a altura é definida como h = ln b n, Fig. 8.2.
8.3 Tempo de execução em funções
Agora estamos prontos para estimar os tempos de execução do algoritmo. Primeiro de tudo,
se o algo
rithm não possui nenhuma chamada recursiva, precisamos apenas analisar seus dados

Page 167
8.3 DURAÇÃO DAS FUNÇÕES
167
Figura 8.2: Árvore ilustrando dividir e conquistar recorrências.
estruturas e blocos de fluxo. Nesse caso, complexidades de blocos de código
um após o outro são adicionados e complexidades de loops aninhados são
multiplicado.
Se o algoritmo tiver chamadas recursivas, podemos usar as funções recursivas
da seção anterior para encontrar o tempo de execução. Quando escrevemos uma recorrência
relação para uma função, devemos escrever duas equações, uma para o caso geral
e um para o caso base (que deve ser O (1), de modo que T (1) = 1). Guardando
isso em mente, vamos dar uma olhada no exemplo do algoritmo para encontrar o
n th elemento em uma sequência de Fibonacci, que é conhecida como estar exponencial:
[general_poroblems / numbers / find_fibonacci_seq.py]
def find_fibonacci_seq_rec (n):
se n <2: retornar n
retornar find_fibonacci_seq_rec (n - 1) +
find_fibonacci_seq_rec (n - 2)
Aqui, g (n) s são n - 2 e n - 1, a é 2 ef (n) é 1, então a recursiva
relação neste caso é
T (n) = 2T (n - 1) + 1.
Agora vamos abrir esta equação para cada próxima recursão:
T (n) = 2 2 T (n - 2) + 2 → 2 k T (n - k) + k ...

Page 168
168
CAPÍTULO 8. ANÁLISE ASSINTÓTICA
Precisamos garantir que a função tenha O (1) no caso base, onde
é T (1) = 1, isso significa que n-k = 1 ou k = n-1. Então, reconectando-o
a equação, temos:
T (n) = 2 n-1 + n - 1 ± 2 n .
(8.3.1)
De fato, provamos que esse algoritmo é exponencial! O mesmo
processo pode ser feito para cada relação recursiva e a tabela a seguir mostra
os resultados do tempo de execução para muitos algoritmos:
O (n 2 )
quadrático
inserção, tipo de seleção
O (n ln n)
loglinear
algoritmos que dividem o problema em pedaços menores
por invocação e juntando os resultados,
classificação rápida e mesclada
Em)
linear
iteração sobre uma lista
O (ln n)
registro
algoritmos que dividem o problema em pedaços menores
por invocação, pesquisando uma árvore de pesquisa binária
O (1)
constante
pesquisa / modificação de tabela de hash
O (n k )
polinomial
aninhado em k para loops sobre n
O (k n )
exponencial
produzindo todos os subconjuntos de n itens
Em!)
fatorial
produzindo todos os pedidos de n valores

Page 169

Capítulo 9
Classificação
A maneira mais simples de classificar um grupo de itens é começar removendo o
menor item do grupo e colocá-lo em primeiro lugar. Em seguida, removendo o próximo
menor e colocá-lo em seguida e assim por diante. Este é claramente um algoritmo O (n 2 ),
então precisamos encontrar uma solução melhor. Neste capítulo, veremos muitos
exemplos de algoritmos de classificação e analisar suas características e tempos de execução.
Uma classificação no local não usa memória adicional para fazer a classificação
(por exemplo, trocando elementos em uma matriz). Uma classificação estável preserva o
ordem relativa de elementos de dados idênticos (por exemplo, se dois dados
elementos têm valores idênticos, o que estava à frente dos outros permanece
adiante). Em qualquer problema de classificação comparativa, uma chave é o valor (ou
valores) que
determina a ordem de classificação. Uma classificação de comparação requer apenas que
exista um
maneira de determinar se uma chave é menor que, igual a ou maior que outra chave.
A maioria dos algoritmos de classificação são tipos de comparação nos quais o pior caso é
executado
o tempo para tais tipos não pode ser melhor que O (nlnn).
9.1 Classificação quadrática
Classificação de inserção
A classificação por inserção é um algoritmo de classificação simples com o melhor tempo de
execução
O (n) e casos médios e piores de tempo de execução de O (n 2 ). Classifica repetidamente
inserindo o próximo elemento não classificado em um segmento classificado inicial da
matriz.
Para conjuntos de dados pequenos, pode ser preferível a algoritmos mais avançados, como
como mesclar classificação ou classificação rápida se a lista já estiver classificada (é uma boa
maneira de
169

Page 170
170
CAPÍTULO 9. CLASSIFICAÇÃO
adicionar novos elementos a uma lista predefinida):
[classificação / inserção_ort.py]
def inserttion_sort (seq):
para i no intervalo (1, len (seq)):
j=i
enquanto j> 0 e seq [j-1]> seq [j]:
seq [j-1], seq [j] = seq [j], seq [j-1]
j-=1
retorno seq
def inserttion_sort_rec (seq, i = Nenhum):
se i == Nenhum: i = len (seq) -1
se i == 0: retornar i
insertion_sort_rec (seq, i-1)
j=i
enquanto j> 0 e seq [ji]> seq [j]:
seq [j-1], seq [j] = seq [j], seq [j-1]
j-=1
retorno seq
def test_insertion_sort ():
seq = [3, 5, 2, 6, 8, 1, 0, 3, 5, 6, 2, 5, 4, 1, 5, 3]
assert (insertion_sort (seq) == classificado (seq))
assert (inserção_sort_rec (seq) == classificado (seq))
print ( 'Testes aprovados!' )
se __name__ == '__main__' :
test_insertion_sort ()
Classificação da seleção
A classificação de seleção é baseada na localização do elemento menor ou maior em uma
lista
e trocá-lo para o primeiro, depois encontrar o segundo, etc, até o fim
alcançado. Mesmo quando a lista é classificada, é O (n 2 ) (e não estável):
[sorting / selection_sort.py]

Page 171
9.1 ORDEM QUADRÁTICA
171
def selection_sort (seq):
para i no intervalo ( len (seq) -1, 0, -1):
max_j = i
para j no intervalo (max_j):
se seq [j]> seq [max_j]:
max_j = j
seq [i], seq [max_j] = seq [max_j], seq [i]
retorno seq
def test_selection_sort ():
seq = [3, 5, 2, 6, 8, 1, 0, 3, 5, 6, 2]
assert (selection_sort (seq) == classificado (seq))
print ( 'Testes aprovados!' )
se __name__ == '__main__' :
test_selection_sort ()
Uma versão mais sofisticada desse algoritmo é apresentada na próxima
capítulo e é chamado de seleção rápida e é usado para encontrar o k-ésimo elemento
de uma matriz e sua mediana.
Classificação do Gnomo
A classificação do Gnome funciona avançando para encontrar um valor equivocado e, em
seguida,
movendo-se para trás para colocá-lo na posição correta:
[classificação / gnome_sort.py]
def gnome_sort (seq):
i=0
enquanto eu < len (seq):
se i == 0 ou seq [i-1] <= seq [i]:
i+=1
mais :
seq [i], seq [i-1] = seq [i-1], seq [i]
i-=1
retorno seq

Page 172
172
CAPÍTULO 9. CLASSIFICAÇÃO
def test_gnome_sort ():
seq = [3, 5, 2, 6, 8, 1, 0, 3, 5, 6, 2, 5, 4, 1, 5, 3]
assert (gnome_sort (seq) == classificado (seq))
print ( 'Testes aprovados!' )
se __name__ == '__main__' :
test_gnome_sort ()
9.2 Classificação linear
Count Sort
A classificação de contagem classifica números inteiros com um pequeno intervalo de
valores, contando as ocorrências
e usando as contagens acumuladas para colocar diretamente os números no resultado,
atualizando as contagens.
Existe um limite loglinear de quão rápido você pode classificar se tudo o que você sabe sobre
seus dados são que eles são maiores ou menores que o outro. No entanto, se você
também pode contar eventos, a classificação se torna linear no tempo, O (n + k):
[classificação / count_sort.py]
das coleções import defaultdict
def count_sort_dict (a):
b, c = [], defaultdict ( lista )
para x em um:
c [x] .append (x)
para k na faixa ( min (c), max (c) + 1):
b. extensão (c [k])
retorno b
def test_count_sort ():
seq = [3, 5, 2, 6, 8, 1, 0, 3, 5, 6, 2, 5, 4, 1, 5, 3]
assert (count_sort_dict (seq) == classificado (seq))
print ( 'Testes aprovados!' )
se __name__ == '__main__' :

Page 173
9.3 LOGLINEAR SORT
173
test_count_sort ()
Se vários valores tiverem a mesma chave, eles terão a ordem original com
respeito um com o outro, então o algoritmo é estável.
9.3 Classificação linear
Os métodos sort () e ordenados ()
Em Python, normalmente classificamos uma lista usando o list.sort () (no local)
método ou qualquer outro item iterável pela função classificada (). Eles são ambos
uma implementação muito eficiente do algoritmo timsort do Python1. Com
a função classificada (), o objeto original não é alterado.
A função sorted () também pode ser personalizada através de argumentos opcionais
procedimentos, por exemplo: reverse = True; chave = len; str.lower (tratamento de
maiúsculas e minúsculas o mesmo); ou mesmo com uma função de classificação
personalizada.
Mesclar classificação
A classificação por mesclagem divide a lista ao meio para criar duas listas não classificadas.
Estes dois
listas não-ordenadas são classificadas e mescladas chamando continuamente a ordem de
mesclagem
até obter uma lista do tamanho 1. O algoritmo é estável, bem como
rápido para grandes conjuntos de dados. No entanto, como não está no local, requer muito
mais memória do que muitos outros algoritmos. A complexidade do espaço é O (n)
para matrizes e O (lnn) para listas vinculadas2. O melhor, o médio e o pior caso
os horários são todos O (nlnn).
A classificação de mesclagem é uma boa opção quando o conjunto de dados é muito grande
para caber no
memória. Os subconjuntos podem ser gravados no disco em arquivos separados até serem
pequeno o suficiente para ser classificado na memória. A fusão é fácil e envolve apenas
lendo elementos únicos por vez de cada arquivo e gravando-os na final
na ordem correta:
[classificação / merge_sort.py]
1 Timsorté um algoritmo de classificação híbrido, derivado da classificação de mesclagem e inserção e
inventado por Tim Peters para Python.
2 Nunca considere classificar uma lista vinculada como difícil.

Page 174
174
CAPÍTULO 9. CLASSIFICAÇÃO
'' 'Alguns exemplos de como implementar a Merge Sort em Python.
-> DURAÇÃO: PIOR / MELHOR / MÉDIA É O (nlogn)
-> complexidade do espaço é O (n) para matrizes
-> em geral, não no lugar, bom para matrizes grandes
-> No caso de duas matrizes: podemos mesclar duas matrizes usando
a função de mesclagem da classificação de mesclagem
-> podemos fazer isso também para arquivos, mesclando cada um
1) Se podemos modificar as matrizes (pop), podemos usar:
def merge (esquerda, direita):
se não for deixado ou não estiver certo: retorne para a esquerda ou direita
nada a ser mesclado
resultado = []
enquanto esquerda e direita:
se esquerda [-1]> = direita [-1]:
result.append (left.pop ())
outro:
result.append (right.pop ())
result.reverse ()
retorno (esquerda ou direita) + resultado
2) Se não podemos modificar ou queremos implementá-lo, precisamos de dois
ponteiros:
>>> l1 = [1, 2, 3, 4, 5, 6, 7]
>>> l2 = [2, 4, 5, 8]
>>> mesclagem (l1, l2)
[1, 2, 2, 3, 4, 4, 5, 5, 6, 7, 8]
3) Por exemplo, no caso, temos uma grande matriz preenchida com 0s
no final, e outra matriz com o tamanho do
número de 0s:
>>> l1 = [1, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0]
>>> l2 = [2, 4, 5, 8]
>>> merge_two_arrays_inplace (l1, l2)
[1, 2, 2, 3, 4, 4, 5, 5, 6, 7, 8]

Page 175
9.3 LOGLINEAR SORT
175
4) Se queremos mesclar arquivos classificados (e temos muitas opções
RAM para carregar todos os arquivos):
>>> list_files = ['1.dat', '2.dat', '3.dat']
>>> arquivos de mesclagem (arquivos de lista)
[1, 1, 2, 3, 3, 3, 4, 5, 5, 5, 6, 7, 8]
'' '
"" "
O exemplo típico ...
"" "
def merge_sort (seq):
se len (seq) <2:
retorno seq
mid = len (seq) // 2
lft, rgt = seq [: médio], seq [médio:]
se len (lft)> 1:
lft = merge_sort (lft)
se len (rgt)> 1:
rgt = merge_sort (rgt)
res = []
enquanto lft e rgt:
se lft [-1]> = rgt [-1]:
res.append (lft.pop ())
mais :
res.append (rgt.pop ())
res.reverse ()
return (lft ou rgt) + res
'' '
Também poderíamos dividir esse tipo em duas partes, separando
a parte de mesclagem em outra função
'' '
def merge_sort_sep (seq):
se len (seq) <2:

Page 176
176
CAPÍTULO 9. CLASSIFICAÇÃO
retorno seq
# caso base
mid = len (seq) // 2
left = merge_sort (seq [: meados])
right = merge_sort (seq [mid:]) # observe que mid está incluído!
retornar mesclagem (esquerda, direita)
# mesclar iterativamente
def merge (esquerda, direita):
se não for deixado ou não estiver certo:
retornar à esquerda ou à direita # nada a ser mesclado
resultado = []
i, j = 0, 0
enquanto i < len (esquerda) e j < len (direita):
se esquerda [i] <= direita [j]:
result.append (esquerda [i])
i+=1
mais :
result.append (direita [j])
j+=1
se for deixado [i:]: result.extend (left [i:]) # LEMBRE-SE DE ESTENDER, NÃO
ACRESCENTAR
se à direita [j:]: result.extend (à direita [j:])
resultado de retorno
'' 'As seguintes funções de mesclagem são O (2n) e
ilustram muitos recursos em Python que '' '
def merge_2n (esquerda, direita):
se não for deixado ou não certo: retorno esquerda ou direita # nada a ser
fundido
resultado = []
enquanto esquerda e direita:
se esquerda [-1]> = direita [-1]:
result.append (left.pop ())
mais :
result.append (right.pop ())
result.reverse ()
retorno (esquerda ou direita) + resultado

Page 177
9.3 LOGLINEAR SORT
177
'' 'Mesclar duas matrizes no lugar' ''
def merge_two_arrays_inplace (l1, l2):
se não for l1 ou não l2: retorne l1 ou l2 # nada a ser mesclado
p2 = len (l2) - 1
p1 = len (l1) - len (l2) - 1
p12 = len (l1) - 1
enquanto p2> = 0 e p1> = 0:
item_to_be_merged = l2 [p2]
item_bigger_array = l1 [p1]
se item_to_be_merged <item_bigger_array:
l1 [p12] = matriz_bigger_ item
p1 - = 1
mais :
l1 [p12] = item_para_mergir
p2 - = 1
p12 - = 1
retornar l1
'' 'Mesclar classificação para arquivos' ''
def merge_files (list_files):
resultado = []
final = []
para o nome do arquivo em list_files:
aux = []
com open (filename, 'r' ) como arquivo :
para linha no arquivo :
aux.append ( int (linha))
result.append (aux)
final.extend (result.pop ())
para l no resultado:
final = mesclagem (l, final)
retorno final
def test_merge_sort ():
seq = [3, 5, 2, 6, 8, 1, 0, 3, 5, 6, 2]
seq_sorted = classificado (seq)
assert (merge_sort (seq) == seq_sorted)
assert (merge_sort_sep (seq) == seq_sorted)

Page 178
178
CAPÍTULO 9. CLASSIFICAÇÃO
print ( 'Testes aprovados!' )
se __name__ == '__main__' :
test_merge_sort ()
Ordenação rápida
A classificação rápida funciona escolhendo um pivô e particionando a matriz para que o
elementos menores que o pivô vão para a esquerda. Então, recursivamente
classifica as partes esquerda e direita.
A escolha do valor do pivô é a chave para o desempenho. Pode ser mostrado
que sempre escolher o valor no meio do conjunto é a melhor escolha
para dados já classificados e não é pior do que a maioria das outras opções aleatórias
dados não classificados.
O pior caso é O (n 2 ) nos raros casos em que o particionamento mantém
reduzindo uma região de n - 1 elementos (quando o pivô é o mínimo ou o
valor máximo). O melhor caso produz duas listas de tamanho n / 2. Isso e o
caso médio são ambos O (nlnn). O algoritmo não é estável.
[classificação / quick_sort.py]
def quick_sort (seq):
se len (seq) <2: retornar seq
ipivot = len (seq) // 2
pivot = seq [ipivot]
antes = [x para i, x em enumerar (seq) se x <= pivô e i! =
ipivot]
depois = [x para i, x em enumerar (seq) se x> pivô e i! =
ipivot]
retornar qs (antes) + [pivô] + qs (depois)
"" "também podemos dividi-los em duas funções" ""
partição def (seq):
pi, seq = seq [0], seq [1:]
lo = [x para x em seq se x <= pi]
hi = [x para x em seq se x> pi]

Page 179
9.3 LOGLINEAR SORT
179
retornar lo, pi, oi
def quick_sort_divided (seq):
se len (seq) <2: retornar seq
lo, pi, oi = partição (seq)
retornar quick_sort_divided (lo) + [pi] + quick_sort_divided (oi)
'' 'quick_sort no lugar, não muito eficiente' ''
def quick_sort_in (seq):
se len (seq) <2: retornar seq
se len (seq) == 2 e seq [0]> seq [1]:
seq [0], seq [1] = seq [1], seq [0] # problemas quando apenas 2
elementos por causa da troca
pivot = seq [0] # começa no final porque não sabemos como
muitos elementos
p1, p2 = 1, len (seq) -1 # configuram ponteiros nas duas extremidades
enquanto p1 <p2: # deve estar <ou fora da faixa
se seq [p1] <= pivot: # deve ser <= por causa da troca de pivô
seq [p1], seq [p2] = seq [p2], seq [p1]
p2 - = 1
mais :
p1 + = 1
seq [0], seq [p1] = seq [p1], pivô
retornar quick_sort_in (seq [p1 + 1:]) + [seq [p1]] +
quick_sort_in (seq [: p1])
def test_quick_sort ():
seq = [3, 5, 2, 6, 8, 1, 0, 3, 5, 6, 2]
assert (quick_sort (seq) == classificado (seq))
afirmar (quick_sort_divided (seq) == classificado (seq))
print ( 'Testes aprovados!' )
se __name__ == '__main__' :
test_quick_sort ()

Page 180
180
CAPÍTULO 9. CLASSIFICAÇÃO
Heap Sort
A classificação de heap é semelhante a uma classificação de seleção, exceto que a região não
classificada é uma
heap, encontrar o maior elemento n vezes fornece um tempo de execução linear.
Em uma pilha, para cada nó que não seja a raiz, o valor do nó está em
pelo menos (no máximo) o valor de seu pai. Assim, o menor (maior) elemento é
armazenadas na raiz e as subárvores enraizadas em um nó contêm maior (menor)
valores do que o próprio nó.
Embora a inserção seja apenas O (1), o desempenho da validação (o
ordem de pilha) é O (lnn). Pesquisando (atravessando) é O (n). Em Python, um monte
A classificação pode ser implementada empurrando todos os valores para uma pilha e, em
seguida, aparecendo
fora dos menores valores, um de cada vez:
[classificação / heap_sort1.py]
heapq de importação
def heap_sort1 (seq):
'' 'classificação de heap com Pythons heapq' ''
h = []
para valor em seq:
heapq.heappush (h, valor)
retornar [heapq.heappop (h) para i no intervalo ( len (h))]
def test_heap_sort1 ():
seq = [3, 5, 2, 6, 8, 1, 0, 3, 5, 6, 2]
assert (heap_sort1 (seq) == classificado (seq))
print ( 'Testes aprovados!' )
se __name__ == '__main__' :
test_heap_sort1 ()
Se decidirmos usar a classe heap que temos nos últimos capítulos, nós
pode escrever uma classificação de heap simplesmente por:
[classificação / heap_sort2.py]
de heap import Heap

Page 181
9.3 LOGLINEAR SORT
181
def heap_sort2 (seq):
pilha = pilha (seq)
res = []
para i no intervalo ( len (seq)):
res.insert (0, heap.extract_max ())
retornar res
def test_heap_sort2 ():
seq = [3, 5, 2, 6, 8, 1, 0, 3, 5, 6, 2]
assert (heap_sort2 (seq) == classificado (seq))
print ( 'Testes aprovados!' )
se __name__ == '__main__' :
test_heap_sort2 ()
Por fim, também podemos escrever nossa classificação de heap explicitamente:
[classificação / heap_sort3.py]
def heap_sort3 (seq):
para início no intervalo (( len (seq) -2) // 2, -1, -1):
siftdown (seq, start, len (seq) -1)
para final no intervalo ( len (seq) -1, 0, -1):
seq [final], seq [0] = seq [0], seq [final]
siftdown (seq, 0, final - 1)
retorno seq
def siftdown (seq, início, fim):
root = start
enquanto True:
filho = raiz * 2 + 1
se filho> fim: pausa
se filho + 1 <= final e seq [filho] <seq [filho + 1]:
filho + = 1
se seq [raiz] <seq [filho]:
seq [raiz], seq [filho] = seq [filho], seq [raiz]
raiz = filho
mais :
quebrar

Page 182
182
CAPÍTULO 9. CLASSIFICAÇÃO
def test_heap_sort ():
seq = [3, 5, 2, 6, 8, 1, 0, 3, 5, 6, 2]
assert (heap_sort3 (seq) == classificado (seq))
print ( 'Testes aprovados!' )
se __name__ == '__main__' :
test_heap_sort3 ()

Page 183
9.4 COMPARAÇÃO ENTRE MÉTODOS DE CLASSIFICAÇÃO
183
9.4 Comparação entre métodos de classificação
Page 184
184
CAPÍTULO 9. CLASSIFICAÇÃO
9.5 Exercícios adicionais
Classificação quadrática
O programa a seguir implementa uma classificação de bolha, uma classificação muito
ineficiente
algoritmo:
[search / bubble_sort.py]
def bubble_sort (seq):
tamanho = len (seq) -1
para num num intervalo (tamanho, 0, -1):
para i no intervalo (num):
se seq [i]> seq [i + 1]:
temp = seq [i]
seq [i] = seq [i + 1]
seq [i + 1] = temp
retorno seq
def test_bubble_sort (module_name = 'este módulo' ):
seq = [4, 5, 2, 1, 6, 2, 7, 10, 13, 8]
afirmação (bubble_sort (seq) == classificado (seq))
s = 'Testes em {name} têm {con}!'
print ( formato . s (nome = nome do módulo, con = 'passado' ))
se __name__ == '__main__' :
test_bubble_sort ()
Classificação linear
O exemplo abaixo mostra uma classificação simples de contagem para pessoas de idades:
def counting_sort_age (A):
olderAge = 100
timesOfAge = [0] * olderAge
ageCountSet = set ()
B = []

Page 185
9.5 EXERCÍCIOS ADICIONAIS
185
para i em A:
timesOfAge [i] + = 1
ageCountSet.add (i)
para j em ageCountSet:
count = timesOfAge [j]
enquanto contar> 0:
B.append (j)
contagem - = 1
retorno B
O exemplo abaixo usa a classificação rápida para encontrar os k maiores elementos em um
seqüência:
[classificando / find_k_largest_seq_quicksort.py]
importação aleatória
def swap (A, x, y):
tmp = A [x]
A [x] = A [y]
A [y] = tmp
def qselect (A, k, esquerda = Nenhuma, direita = Nenhuma):
esquerda = esquerda ou 0
direita = direita ou len (A) - 1
pivô = random.randint (esquerda, direita)
pivotVal = A [pivô]
swap (A, pivô, direita)
swapIndex, i = esquerda, esquerda
enquanto eu <= direita - 1:
se A [i] <pivotVal:
swap (A, i, swapIndex)
swapIndex + = 1
i+=1
swap (A, direita, swapIndex)
rank = len (A) - swapIndex
se k == classificação:

Page 186
186
CAPÍTULO 9. CLASSIFICAÇÃO
retornar A [swapIndex]
elif k <rank:
return qselect (A, k, left = swapIndex + 1, right = right)
mais :
retornar qselect (A, k, esquerda = esquerda, direita = swapIndex-1)
def find_k_largest_seq_quickselect (seq, k):
kth_largest = qselect (seq, k)
resultado = []
para item em seq:
se item> = kth_largest:
result.append (item)
resultado de retorno
def test_find_k_largest_seq_quickselect ():
seq = [3, 10, 4, 5, 1, 8, 9, 11, 5]
k=2
afirmação (find_k_largest_seq_quickselect (seq, k) == [10, 11])
se __name__ == '__main__' :
test_find_k_largest_seq_quickselect ()

Page 187

Capítulo 10
Procurando
Os algoritmos de pesquisa mais comuns são a pesquisa seqüencial e o
pesquisa binária. Se uma matriz de entrada não for classificada, ou os elementos de entrada
forem
acomodados por contêineres dinâmicos (como listas vinculadas), a pesquisa é
geralmente seqüencial. Se a entrada for uma matriz classificada, o algoritmo de pesquisa
binária
é a melhor escolha. Se tivermos permissão para usar memória auxiliar, uma tabela de hash
pode ajudar na pesquisa, com a qual um valor pode ser localizado no tempo O (1) com
uma chave.
10.1 Matrizes não ordenadas
10.1.1 Pesquisa sequencial
No exemplo a seguir, ilustramos o tempo de execução de uma pesquisa sequencial por
itens armazenados em uma coleção. Se o item estiver presente, o melhor caso é O (1), o
o caso médio é O (n / 2) e o pior caso é O (n). No entanto, se o item for
não presente, todos os três casos serão O (n):
[search / sequential_search.py]
def sequential_search (seq, n):
para item em seq:
se item == n: return True
retornar falso
187

Page 188
188
CAPÍTULO 10. PESQUISA
def test_sequential_search (module_name = 'este módulo' ):
seq = [1, 5, 6, 8, 3]
n1 = 5
n2 = 7
assert (sequential_search (seq, n1) == Verdadeiro)
afirmação (sequential_search (seq, n2) == False)
s = 'Testes em {name} têm {con}!'
print ( formato . s (nome = nome do módulo, con = 'passado' ))
se __name__ == '__main__' :
test_sequential_search ()
Agora, se classificarmos a sequência primeiro, podemos melhorar a pesquisa sequencial
no caso em que o item não estiver presente, tenha os mesmos tempos de execução de quando
o item está presente:
[search / orders_sequential_search.py]
def orders_sequential_search (seq, n):
item = 0
para item em seq:
se item> n: retornar Falso
se item == n: return True
retornar falso
def test_ordered_sequential_search (module_name = 'este módulo' ):
seq = [1, 2, 4, 5, 6, 8, 10]
n1 = 10
n2 = 7
assert (busca_sequencial_comprometida (seq, n1) == Verdadeiro)
assert (busca_sequencial_compartilhada (seq, n2) == Falso)
s = 'Testes em {name} têm {con}!'
print ( formato . s (nome = nome do módulo, con = 'passado' ))
se __name__ == '__main__' :
test_ordered_sequential_search ()

Page 189
10.1 ARRAYS NÃO SORTIDOS
189
10.1.2 Estatísticas rápidas de seleção e pedido
Podemos usar uma versão adaptada de classificação rápida para encontrar o k-menor
número em uma lista. Esse número é chamado de estatística de quinta ordem. este
inclui os casos de encontrar os elementos mínimo, máximo e mediano.
Esse algoritmo é O (n) na pior das hipóteses, porque olhamos apenas para um lado do
a matriz em cada iteração: O (n) = O (n) + O (n / 2) + O (n / 4) ...
o desempenho é possível para dados estruturados: podemos obter O (1) para uma matriz
de dados classificados.
[search / quick_select.py]
importação aleatória
'' 'A maneira mais simples ...' ''
def quickSelect (seq, k):
# esta parte é igual à classificação rápida
len_seq = len (seq)
se len_seq <2: retornar seq
# poderíamos usar uma escolha aleatória aqui fazendo
#pivot = random.choice (seq)
ipivot = len_seq // 2
pivot = seq [ipivot]
# Em)
smallerList = [X para I, X em enumerar (SEQ) se x <= pivô e i
! = ipivot]
largeList = [x para i, x em enumerar (seq) se x> pivot e i! =
ipivot]
# aqui começa a parte diferente
m = len (lista menor)
se k == m:
retorno pivô
elif k <m:
retornar quickSelect (lista menor, k)
mais :
return quickSelect (lista maior, km-1)

Page 190
190
CAPÍTULO 10. PESQUISA
'' 'Se você não quiser usar o recurso pythons e
selecione também pivô aleatoriamente '' '
def swap (seq, x, y):
tmp = seq [x]
seq [x] = seq [y]
seq [y] = tmp
def quickSelectHard (seq, k, esquerda = Nenhuma, direita = Nenhuma):
esquerda = esquerda ou 0
right = right ou len (seq) - 1
#ipivot = random.randint (esquerda, direita)
ipivot = len (seq) // 2
pivot = seq [ipivot]
# Mover o pivô para fora do intervalo de classificação
swap (seq, ipivot, direita)
swapIndex, i = esquerda, esquerda
enquanto eu <certo:
se seq [i] <pivô:
swap (seq, i, swapIndex)
swapIndex + = 1
i+=1
# Mova o pivô para a posição final
swap (seq, direita, swapIndex)
# Verifique se o pivô corresponde, caso contrário, recorra na metade correta
rank = len (seq) - swapIndex
se k == classificação:
retorno seq [swapIndex]
elif k <rank:
retornar quickSelectHard (seq, k, swapIndex + 1, à direita)
mais :
retornar quickSelectHard (seq, k, left, swapIndex-1)

Page 191
10.2 ARRAYS SORTED
191
se __name__ == '__main__' :
# Verificando a resposta
seq = [10, 60, 100, 50, 60, 75, 31, 50, 30, 20, 120, 170, 200]
#seq = [3, 7, 2, 1, 4, 6, 5, 10, 9, 11]
# queremos o elemento do meio
k = len (seq) // 2
# Observe que isso só funciona para matrizes ímpares, já que a mediana em
# matrizes pares é a média dos dois elementos do meio
print (seleção rápida (seq, k))
print (quickSelectHard (seq, k))
importar numpy
print numpy.median (seq)
Em geral, podemos definir a mediana como o valor que é maior que
metade da matriz. Esse algoritmo é importante no contexto de problemas maiores
itens como encontrar o vizinho mais próximo ou o caminho mais curto.
10.2 Matrizes ordenadas
10.2.1 Pesquisa binária
Uma pesquisa binária encontra a posição de um valor de entrada especificado (a chave) dentro
uma matriz classificada. Em cada etapa, o algoritmo compara o valor da chave de pesquisa
com o valor da chave do elemento do meio da matriz. Se as teclas coincidirem
o índice do item, (posição) é retornado. Caso contrário, se a chave de pesquisa for menor
que a chave do elemento do meio, o algoritmo repete o processo à esquerda
subarray; ou se a chave de pesquisa for maior, no sub-array direito. O algoritmo
roda em O (lnn):
[search / binary_search.py]
def binary_search_rec (seq, chave): # Recursivo

Page 192
192
CAPÍTULO 10. PESQUISA
se não for seq: retornar False
mid = len (seq) // 2
if chave == seq [meio]: retornar True
chave elif <seq [mid]: retorna binary_search_rec (seq [: mid], chave)
else : retornar binary_search_rec (seq [médio + 1:], chave)
def binary_search_iter (seq, chave): # Iterativo
oi, lo = len (seq), 0
enquanto lo <oi:
mid = (hi + lo) // 2
if chave == seq [meio]: retornar True
tecla elif <seq [meio]: hi = meio
else : lo = médio + 1
retornar falso
def test_binary_search ():
seq = [1,2,5,6,7,10,12,12,14,15]
chave = 6
assert (binary_search_iter (seq, key) == 3)
assert (binary_search_rec (seq, chave) == 3)
print ( 'Testes aprovados!' )
se __name__ == '__main__' :
test_binary_search ()
O módulo bisect
O módulo bisect () interno do Python está disponível para pesquisa binária em um
seqüência:
>>> de bisect import bisect
>>> lista = [0, 3, 4, 5]
>>> bisect ( lista , 5)
4
Observe que o módulo retorna o índice após a chave, que é onde você
deve colocar o novo valor. Outras funções disponíveis são bissectas à direita e
bissecção esquerda.

Page 193
10.3 EXERCÍCIOS ADICIONAIS
193
10.3 Exercícios adicionais
Pesquisando em uma matriz
O módulo a seguir pesquisa uma entrada em uma matriz onde as linhas e
colunas são classificadas. Nesse caso, cada linha é cada vez mais classificada da esquerda
para a direita e cada coluna é cada vez mais classificada de cima para baixo. o
o tempo de execução é linear em O (m + n):
[general_problems / numbers / search_entry_matrix.py]
def find_elem_matrix_bool (m1, valor):
encontrado = Falso
row = 0
col = len (m1 [0]) - 1
enquanto a linha < len (m1) e col> = 0:
se m1 [linha] [col] == valor:
found = True
quebrar
elif m1 [linha] [col]> valor:
col- = 1
mais :
linha + = 1
retorno encontrado
def test_find_elem_matrix_bool (module_name = 'este módulo' ):
m1 = [[1,2,8,9], [2,4,9,12], [4,7,10,13], [6,8,11,15]]
assert (find_elem_matrix_bool (m1,8) == Verdadeiro)
assert (find_elem_matrix_bool (m1,3) == False)
m2 = [[0]]
assert (find_elem_matrix_bool (m2,0) == Verdadeiro)
s = 'Testes em {name} têm {con}!'
print ( formato . s (nome = nome do módulo, con = 'passado' ))
se __name__ == '__main__' :
test_find_elem_matrix_bool ()
O problema a seguir pesquisa um elemento em uma matriz onde, em todos os
linha, os valores aumentam da esquerda para a direita, mas o último número em uma linha é

Page 194
194
CAPÍTULO 10. PESQUISA
menor que o primeiro número na próxima linha. A solução ingênua de força bruta
varre todos os números e custos O (mn). No entanto, como os números já estão
classificada, a matriz pode ser vista como uma matriz classificada 1D e podemos usar o
algoritmo de pesquisa binária com eficiência O (lognm):
[search / search_in_a_matrix.py]
importar numpy
def search_in_a_matrix (m1, value):
linhas = len (m1)
cols = len (m1 [0])
lo = 0
oi = linhas * cols
enquanto lo <oi:
mid = (lo + oi) // 2
row = mid // cols
col =% médio de cols
v = m1 [linha] [col]
se v == valor: retornar True
elif v> value: oi = médio
else : lo = médio + 1
retornar falso
def test_searching_in_a_matrix ():
a = [[1,3,5], [7,9,11], [13,15,17]]
b = numpy.array ([(1,2), (3,4)])
afirmar (pesquisar_em_matriz (a, 13) == Verdadeiro)
afirmar (pesquisar_em_matriz (a, 14) == Falso)
afirmar (pesquisar_em_matriz (b, 3) == Verdadeiro)
assert (search_in_a_matrix (b, 5) == False)
print ( 'Testes aprovados!' )
se __name__ == '__main__' :
test_searching_in_a_matrix ()

Page 195
10.3 EXERCÍCIOS ADICIONAIS
195
Matrizes Unimodais
Uma matriz é unimodal se consistir em uma sequência crescente seguida por um
sequência decrescente. O exemplo abaixo mostra como encontrar o “localmente
máximo ”de uma matriz usando pesquisa binária:
[search / find_max_unimodal_array.py]
def find_max_unimodal_array (A):
se len (A) <= 2: retornar Nenhum
esquerda = 0
direita = len (A) -1
enquanto direita> esquerda +1:
meio = (esquerda + direita) // 2
se A [médio]> A [médio-1] e A [médio]> A [médio + 1]:
retornar A [meio]
elif A [médio]> A [médio-1] e A [médio] <A [médio + 1]:
esquerda = média
mais :
direita = média
retornar Nenhum
def test_find_max_unimodal_array ():
seq = [1, 2, 5, 6, 7, 10, 12, 9, 8, 7, 6]
assert (find_max_unimodal_array (seq) == 12)
print ( 'Testes aprovados!' )
se __name__ == '__main__' :
test_find_max_unimodal_array ()
Cálculo de raízes quadradas
O exemplo abaixo implementa a raiz quadrada de um número usando binário
procurar:
[search / find_sqrt_bin_search.py]
def find_sqrt_bin_search (n, erro = 0,001):
reduzir = n <1 e n ou 1

Page 196
196
CAPÍTULO 10. PESQUISA
superior = n <1 e 1 ou n
médio = inferior + (superior - inferior) / 2,0
quadrado = médio * médio
enquanto abs (quadrado - n)> erro:
se quadrado <n:
inferior = médio
mais :
superior = médio
médio = inferior + (superior - inferior) / 2,0
quadrado = médio * médio
retorno no meio
def test_ind_sqrt_bin_search ():
number = 9
assert (find_sqrt_bin_search (number) == 3)
print ( 'Testes aprovados!' )
se __name__ == '__main__' :
test_ind_sqrt_bin_search ()
Frequência de contagem de elementos
O exemplo a seguir localiza quantas vezes um elemento ak aparece em um
Lista. Como a matriz é classificada, a pesquisa binária fornece um tempo de execução O
(logn):
[search / find_time_occurence_list.py]
de binary_search import binary_search
def find_time_occurrence_list (seq, k):
index_some_k = binary_serch (seq, k)
count = 1
sizet = len (seq)
para i no intervalo (index_some_k + 1, sizet): # subir
se seq [i] == k: contagem + = 1
else : break
para i no intervalo (index_some_k-1, -1, -1): # desce
se seq [i] == k: contagem + = 1

Page 197
10.3 EXERCÍCIOS ADICIONAIS
197
else : break
contagem de retorno
def test_find_time_occurrence_list ():
seq = [1,2,2,2,2,2,2,5,6,6,7,8,9]
k=2
afirmar (find_time_occurrence_list (seq, k) == 6)
print ( 'Testes aprovados!' )
se __name__ == '__main__' :
test_find_time_occurrence_list ()
Interseção de matrizes
O trecho abaixo mostra três maneiras de executar a interseção de duas
matrizes. A maneira mais simples é usar conjuntos, no entanto, isso não preservará a
encomenda. O segundo exemplo usa uma adaptação da classificação de mesclagem. o
O terceiro exemplo é adequado quando uma das matrizes é muito maior que a outra.
Nesse caso, a pesquisa binária é a melhor opção:
[search / intersection_two_arrays.py]
def intersection_two_arrays_sets (seq1, seq2):
'' 'encontre a interseção de duas matrizes usando propriedades definidas
'' '
set1 = set (seq1)
set2 = set (seq2)
retornar set1.intersection (set2) #same como lista (set1 e set2
def intersection_two_arrays_ms (seq1, seq2):
'' 'encontre a interseção de duas matrizes usando a classificação de mesclagem' ''
res = []
enquanto seq1 e seq2:
se seq1 [-1] == seq2 [-1]:
res.append (seq1.pop ())
seq2.pop ()
elif seq1 [-1]> seq2 [-1]:

Page 198
198
CAPÍTULO 10. PESQUISA
seq1.pop ()
mais :
seq2.pop ()
res.reverse ()
retornar res
de binary_search import binary_search
def intersection_two_arrays_bs (seq1, seq2):
'' 'usando a pesquisa binária' ''
se len (seq1)> len (seq2): seq, chave = seq1, seq2
else : seq, chave = seq2, seq1
intersec = []
para o item na chave:
se binary_search (seq, item):
intersec.append (item)
retorno intersec
def test_intersection_two_arrays (module_name = 'este módulo' ):
seq1 = [1,2,3,5,7,8]
seq2 = [3,5,6]
assert ( conjunto (intersection_two_arrays_sets (seq1, seq2)) ==
conjunto ([3,5]))
assert (intersection_two_arrays_bs (seq1, seq2) == [3,5])
assert (intersection_two_arrays_ms (seq1, seq2) == [3,5])
s = 'Testes em {name} têm {con}!'
print ( formato . s (nome = nome do módulo, con = 'passado' ))
se __name__ == '__main__' :
test_intersection_two_arrays ()

Page 199

Capítulo 11
Programaçao dinamica
A programação dinâmica é usada para simplificar um problema complicado quebrando
em subproblemas mais simples por meio de recursão. Se houver um problema
uma subestrutura ideal e subproblemas sobrepostos, pode ser resolvida por
programaçao dinamica.
Subestrutura ideal significa que a solução para uma determinada otimização
Esse problema pode ser obtido por uma combinação de soluções ótimas para seus
problemas O primeiro passo para utilizar a programação dinâmica é verificar se
o problema exibe essa subestrutura ideal. O segundo passo é ter
sobreposição de problemas resolvendo subproblemas uma vez e armazenando a solução
recuperação a ser recuperada. É feita uma escolha em cada etapa da programação dinâmica,
e a escolha depende das soluções para os subproblemas, de baixo para cima,
de subproblemas menores para subproblemas maiores.
11.1 Memoização
Resolução dinâmica da série Fibonacci
Linguagens de alto nível, como Python, podem implementar a formulação recursiva
diretamente, armazenando em cache valores de retorno. Memoização é um método em que,
se uma chamada for
feita mais de uma vez com os mesmos argumentos e o resultado é retornado
diretamente do cache.
Por exemplo, podemos resolver dinamicamente a série exponencial de Fibonacci
usando uma função de memorando projetada como um algoritmo que usa escopos aninhados
199

Page 200
200
CAPÍTULO 11. PROGRAMAÇÃO DINÂMICA
para fornecer a memória da função agrupada:
[dynamic_programming / memo.py]
de functools import wraps
nota de definição (func):
cache = {}
@wraps (func)
def wrap (* args):
se args não estiver no cache:
cache [args] = func (* args)
cache de retorno [args]
envoltório de retorno
>>> fibonacci = memorando (fibonacci)
>>> fibonacci (130)
1066340417491710595814572169
Poderíamos até usar o decorador diretamente na função:
@memorando
def fib (i):
se i <2: retornar 1
retornar fib (i-1) + fib (i-2)
>>> fibonacci (130)
1066340417491710595814572169

Page 201
11.2 EXERCÍCIOS ADICIONAIS
201
11.2 Exercícios adicionais
Maior subsequência crescente
Outra aplicação interessante da memorização é o problema de encontrar
a subsequência crescente mais longa1 de uma determinada sequência. Uma ingênua
recorrência
solução definitiva fornece um tempo de execução de O (2 n ). No entanto, a solução iterativa
pode ser
resolvido em tempo loglinear usando programação dinâmica.
No exemplo abaixo, comparamos essas funções com uma matriz com
40 elementos para descobrir que a versão memorizada leva menos de um segundo para
enquanto sem programação dinâmica, a função pode demorar mais de 120
segundos para executar:
[dynamic_programming / memoized_longest_inc_subseq.py]
de combinações de importação de itertools
de bisect importar bisect
do memorando de importação memo
do benchmark de importação do_benchmark
def naive_longest_inc_subseq (seq):
'' 'solução exponencial para a subsequência crescente mais longa
problema '' '
para comprimento no intervalo ( len (seq), 0, -1):
para sub em combinações (seq, comprimento):
if list (sub) == classificado (sub):
retornar len (sub)
def longest_inc_subseq1 (seq):
'' 'solução iterativa para a subsequência crescente mais longa
problema '' '
end = []
para val em seq:
idx = bissecção (final, val)
se idx == len (final): end.append (val)
else : fim [idx] = val
retornar len (final)
1 Veja outras versões deste problema no final do capítulo sobre listas em Python.

Page 202
202
CAPÍTULO 11. PROGRAMAÇÃO DINÂMICA
def longest_inc_subseq2 (seq):
'' 'outro algoritmo iterativo para o aumento mais longo
problema de subsequência '' '
L = [1] * len (seq)
para cur, val em enumerar (seq):
para pré na faixa (cur):
se seq [pre] <= val:
L [cur] = máx (L [cur], 1 + L [pré])
retorno máximo (L)
def memoized_longest_inc_subseq (seq):
'' 'solução recursiva memorizada para o aumento mais longo
problema de subsequência '' '
@memorando
def L (atual):
res = 1
para pré na faixa (cur):
se seq [pre] <= seq [cur]:
res = máx (res, 1 + L (pré))
retornar res
retornar max (L (i) para i no intervalo ( len (seq))))
@benchmark
def test_naive_longest_inc_subseq ():
print (naive_longest_inc_subseq (s1))
referência
def test_longest_inc_subseq1 ():
print (longest_inc_subseq1 (s1))
@benchmark
def test_longest_inc_subseq2 ():
print (longest_inc_subseq2 (s1))
@benchmark
def test_memoized_longest_inc_subseq ():

Page 203
11.2 EXERCÍCIOS ADICIONAIS
203
print (memoized_longest_inc_subseq (s1))
se __name__ == '__main__' :
de importação aleatória randrange
s1 = [intervalo aleatório (100) para i no intervalo (40)]
impressão (s1)
test_naive_longest_inc_subseq ()
test_longest_inc_subseq1 ()
test_longest_inc_subseq2 ()
test_memoized_longest_inc_subseq ()

Page 204
204
CAPÍTULO 11. PROGRAMAÇÃO DINÂMICA

Page 205

Parte III
Escalada é tão na semana passada! Eu
prefere voar, não é?
Hora de iniciar nossos motores
alcançar os objetos mais divertidos
o mundo do algoritmo. Acelerar
para belos gráficos e árvores!
205

Page 206

Page 207

Capítulo 12
Introdução aos gráficos
12.1 Definições básicas
Um gráfico é uma rede abstrata, composta por nós (ou vértices, V) conectados
pelas arestas (ou arcos, E). Um gráfico pode ser definido como um par de conjuntos, G = (V,
E),
onde o conjunto de nós V é qualquer conjunto finito e o conjunto de arestas E é um conjunto
de nós
pares. Por exemplo, algum gráfico pode ser simplesmente representado pelo conjunto de nós
V = {a, b, c, d} e o conjunto de arestas E = {{a, b}, {b, c}, {c, d}, {d, a}}.
Direção de um gráfico
Se um gráfico não tem direção, é referido como não direcionado. Nesse caso, nós
com uma borda entre eles são adjacentes e os nós adjacentes são vizinhos.
Se as arestas tiverem uma direção, o gráfico será direcionado (dígrafo). Nesse caso,
o gráfico tem folhas. As arestas não são mais desordenadas: uma aresta entre
os nós uev agora são uma aresta (u, v) de u para v ou uma aresta (v, u) de
v para você. Podemos dizer que em um dígrafo G, a função E (G) é uma relação sobre
V (G).
Subgráficos
Um subgrafo de G consiste em um subconjunto de V e E. Um subgrafo de abrangência
contém todos os nós do gráfico original.
207

Page 208
208
CAPÍTULO 12. INTRODUÇÃO A GRÁFICOS
Completude de um gráfico
Se todos os nós em um gráfico forem adjacentes aos pares, o gráfico será chamado de
completo.
Grau em um nó
O número de arestas não direcionadas incidentes em um nó é chamado grau. Zero-
gráficos de graus são chamados isolados. Para gráficos direcionados, podemos dividir esse
número
em grau (bordas de entrada / pais) e grau / crianças (número de
bordas).
Caminhos, Passeios e Ciclos
Um caminho em G é um subgráfico em que as bordas conectam os nós em uma sequência,
sem revisitar nenhum nó. Em um gráfico direcionado, um caminho deve seguir o
direções das bordas.
Uma caminhada é uma sequência alternada de nós e arestas que permite que nós
e bordas a serem visitadas várias vezes.
Um ciclo é como um caminho, exceto que a última aresta vincula o último nó ao
primeiro.
Comprimento de um caminho
O comprimento de um caminho ou caminhada é o valor dado por sua contagem de arestas.
Peso de uma aresta
A associação de pesos a cada aresta em G nos fornece um gráfico ponderado. o
o peso de um caminho ou ciclo é a soma de seus pesos de borda. Então, para não ponderado
gráficos, é simplesmente o número de arestas.
Gráficos planares
Um gráfico que pode ser desenhado no plano sem cruzar arestas é chamado
planar. Este gráfico possui regiões, que são áreas delimitadas pelas bordas.
A fórmula de Euler para gráficos planares conectados diz que V − E + F = 2, onde
V, E, F são o número de nós, arestas e regiões, respectivamente.

Page 209
12.2 A FUNÇÃO DO BAIRRO
209
Traversal do gráfico
Um percurso é um passeio por todos os componentes conectados de um gráfico. o
A principal diferença entre as travessias dos gráficos é a ordem da lista de tarefas
entre os nós não visitados que foram descobertos.
Componentes conectados e fortemente conectados
Um gráfico não direcionado será conectado se houver um caminho de cada nó para cada
outro nó. Um gráfico direcionado é conectado se o gráfico não direcionado subjacente
está conectado.
Um componente conectado é um subgráfico máximo que está conectado. Vigarista-
componentes conectados podem ser encontrados usando algoritmos transversais como
(DFS) ou respiração pela primeira vez (BFS), como veremos a seguir
capítulos.
Se houver um caminho de cada nó para outro nó em um gráfico direcionado,
o gráfico é chamado fortemente conectado. Um componente fortemente conectado (SCC)
é um subgráfico máximo fortemente conectado.
Árvores e florestas
Uma floresta é um gráfico sem ciclo. Uma árvore é uma árvore acíclica, conectada e
direcionada
gráfico. Uma floresta consiste em uma ou mais árvores.
Em uma árvore, quaisquer dois dois nós são conectados por exatamente um caminho.
Adicionando
qualquer nova aresta cria um ciclo e a remoção de qualquer aresta produz desconectados
componentes.
12.2 A função de vizinhança
A função vizinha de um gráfico, N (V), é um contêiner (ou objeto iterável) de
todos os vizinhos de V. As estruturas de dados mais conhecidas usadas para representar
eles são listas adjacentes e matrizes adjacentes.
Listas Adjacentes
Para cada nó em uma lista adjacente, temos acesso a uma lista (ou conjunto ou contêiner
ou iterável) do seu vizinho. Supondo que temos n nós, cada um adjacente (ou

Page 210
210.
CAPÍTULO 12. INTRODUÇÃO A GRÁFICOS
vizinho) é apenas uma lista desses números. Colocamos as listas em um
lista de tamanho n, indexável pelos números de nós, em que a ordem geralmente é
arbitrário.
Usando conjuntos como listas adjacentes:
Podemos usar o tipo de conjunto do Python para implementar listas adjacentes:
>>> a, b, c, d, e, f = intervalo (6) # nós
>>> N = [{b, c, d, f}, {a, d, f}, {a, b, d, e}, {a, e}, {a, b, c}, {b , c, d, e}]
>>> b em N [a] # associação
Verdade
>>> b em N [b] # associação
Falso
>>> len (N [f]) # grau
4
Usando listas como listas adjacentes:
Também podemos usar as listas do Python para implementar listas adjacentes, o que permite
que você
itere eficientemente N (V) sobre qualquer nó V. Substituir conjuntos por listas torna
verificação de associação como O (n). Se tudo o que seu algoritmo faz é iterar
sobre os vizinhos, o uso da lista pode ser preferencial. No entanto, se o gráfico for denso
(muitas arestas), conjuntos adjacentes são uma solução melhor:
>>> a, b, c, d, e, f = intervalo (6) # nós
>>> N = [[b, c, d, f], [a, d, f], [a, b, d, e], [a, e], [a, b, c], [b , c, d, e]]
>>> b em N [a] # associação
Verdade
>>> b em N [b] # associação
Falso
>>> len (N [f]) # grau
4
Excluir objetos do meio de uma lista Python é O (n), mas excluir
do final é apenas O (1). Se a ordem dos vizinhos não for importante, você
pode excluir um vizinho arbitrário no tempo O (1) trocando-o pelo último
item na lista e depois chamar pop ().

Page 211
12.2 A FUNÇÃO DO BAIRRO
211
Usando dicionários como listas adjacentes:
Finalmente, podemos usar dicionários como listas adjacentes. Nesse caso, os vizinhos
seriam as chaves, e podemos associar cada uma delas com algumas
valor, como um peso da borda:
>>> a, b, c, d, e, f = intervalo (6) # nós
>>> N = [{b: 2, c: 1, d: 4, f: 1}, {a: 4, d: 1, f: 4}, {a: 1, b: 1, d: 2 , e: 4},
{a: 3, e: 2}, {a: 3, b: 4, c: 1}, {b: 1, c: 2, d: 4, e: 3}]
>>> b em N [a] # associação
Verdade
>>> len (N [f]) # grau
4
>>> N [a] [b] # peso da aresta para (a, b)
2
Uma abordagem mais flexível para rótulos de nós é usar dicionários como principal
somente estrutura. Por exemplo, podemos usar um dicionário com conjuntos de adjacências:
>>> a, b, c, d, e, f = intervalo (6) # nós
>>> N = { 'a' : set ( 'bcdf' ), 'b' : set ( 'adf' ), 'c' : set ( 'abde' ),
'd' : set ( 'ae' ), 'e' : set ( 'abc' ), 'f' : set ( 'bcde' )}
>>> 'b' na associação N [ 'a' ] #
Verdade
>>> len (N [ 'f' ]) # grau
4
Matrizes adjacentes
Em matrizes adjacentes, em vez de listar todos os vizinhos de cada nó, nós
ter uma linha com uma posição para cada possível vizinho, preenchida com True
e valores falsos. A implementação mais simples de matrizes adjacentes é dada
por listas aninhadas. Observe que a diagonal é sempre falsa:
>>> a, b, c, d, e, f = intervalo (6) # nós
>>> N = [[0,1,1,1,0,1], [1,0,0,1,0,1], [1,1,0,1,1,0],
[1,0,0,0,1,0], [1,1,1,0,0,0], [0,1,1,1,1,0]]
>>> N [a] [b] # associação
1
>>> N [a] [e]

Page 212
212
CAPÍTULO 12. INTRODUÇÃO A GRÁFICOS
00
>>> soma (N [f]) # grau
4
Uma matriz adjacente para um gráfico não direcionado sempre será simétrica.
Para adicionar peso às matrizes adjacentes, podemos substituir True e False por
valores. Nesse caso, arestas inexistentes podem ser representadas por pesos infinitos
(valores flutuantes ('inf') ou Nenhum, -1 ou muito grandes):
>>> _ = float ( 'inf' ) # nós
>>> N = [[_, 2,1,4, _, 1], [4, _, _, 1, _, 4], [1,1, _, 2,4, _],
[3, _, _, _, 2, _], [3,4,1, _, _, _], [1,2, _, 4,3, _]]
>>> N [a] [b] <_ # associação
Verdade
>>> soma (1 para w em N [f] se w <_) # grau
4
Observar uma aresta em uma matriz adjacente é O (1) enquanto itera sobre uma
o vizinho do nó é O (n).
12.3 Conexão com árvores
Enquanto em um gráfico, pode haver várias referências a qualquer nó; em um
árvore, cada nó (elemento de dados) é referenciado apenas por no máximo um outro nó,
o nó pai. O nó raiz é o nó que não possui pai. Os nós
referenciados por um nó pai são chamados filhos. Diz-se que uma árvore está cheia e
completo se todas as folhas estiverem na parte inferior e todos os nós que não sejam folhas
tem exatamente dois filhos.
Altura ou profundidade de uma árvore
A altura (ou profundidade) de uma árvore é o comprimento do caminho desde a raiz até a
nó mais profundo da árvore. É igual ao nível máximo de qualquer nó no
a árvore. A profundidade da raiz é zero. Se a altura de uma árvore é representada
como o log do número de folhas, o número inteiro do log pode ser
também chamado de profundidade.
Page 213
12.3 CONEXÃO COM ÁRVORES
213
Nível ou profundidade de um nó
O nível (ou profundidade) de um nó é o comprimento do caminho da raiz até este
nó. O conjunto de todos os nós em uma determinada profundidade em uma árvore também é
chamado de nível
da árvore.
Representando árvores
A maneira mais simples de representar uma árvore é através de listas aninhadas:
>>> T = [ 'a' , [ 'b' , [ 'd' , 'f' ]], [ 'c' , [ 'e' , 'g' ]]]
>>> T [0]
'uma'
>>> T [1] [0]
'b'
>>> T [1] [1] [0]
'd'
>>> T [1] [1] [1]
'f'
>>> T [2] [0]
'c'
>>> T [2] [1] [1]
'g'
No entanto, isso se torna muito difícil de lidar se simplesmente adicionarmos alguns
mais galhos. A única boa maneira de trabalhar com árvores é representá-las
como uma coleção de nós. Vamos começar com um exemplo simples, onde definimos
uma classe de árvore simples com um atributo para valor, outra para filhos (ou
'next') e um método para imprimir o resultado:
[trees / tree.py]
"" "Uma classe para uma árvore simples" ""
classe SimpleTree ( objeto ):
def __init __ (próprio, valor = Nenhum, filhos = Nenhum):
self.value = value
self.children = crianças
se self.children == Nenhum:

Page 214
214
CAPÍTULO 12. INTRODUÇÃO A GRÁFICOS
self.children = []
def __repr __ (auto, nível = 0):
ret = "\ t" * nível + repr (valor próprio) + "\ n"
para criança em criança :
ret + = filho .__ repr __ (nível + 1)
retorno ret
def main ():
"" "
'uma'
'b'
'd'
'e'
'c'
'h'
'g'
"" "
st = SimpleTree ( 'a' , [SimpleTree ( 'b' , [SimpleTree ( 'd' ),
SimpleTree ( 'e' )]), SimpleTree ( 'c' , [SimpleTree ( 'h' ),
SimpleTree ( 'g' )])])
impressão (st)
se __name__ == '__main__' :
a Principal()
No próximo capítulo, aprenderemos como melhorar esta classe, incluindo
muitos recursos e métodos que uma árvore pode conter. Por enquanto, é útil
tenha em mente que, quando estamos criando protótipos de estruturas de dados, como árvores,
deve sempre ser capaz de criar uma classe flexível para especificar
atributos no construtor. O programa a seguir implementa o que é
referida como uma classe de grupo ;, uma ferramenta genérica que é uma especialização do
Classe dict do Python e que permitem criar e definir atributos arbitrários em
o voo:
[trees / bunchclass.py]

Page 215
12.3 CONEXÃO COM ÁRVORES
215
classe BunchClass ( dict ):
def __init __ (próprio, * args, ** kwds):
super (BunchClass, self) .__ init __ (* args, ** kwds)
self .__ dict__ = self
def main ():
'' '{' right ': {' right ':' Xander ',' left ':' Willow '},' left ':
{'right': 'Angel', 'left': 'Buffy'}} '' '
bc = BunchClass
# observe a ausência de ()
árvore = bc (esquerda = bc (esquerda = "Buffy" , direita = "anjo" ), direita =
bc (esquerda = "Salgueiro" , direita = "Xander" ))
impressão (árvore)
se __name__ == '__main__' :
a Principal()
No exemplo acima, os argumentos da função * args e ** kwds podem
mantenha um número arbitrário de argumentos e um número arbitrário de palavras-chave
argumentos, respectivamente.

Page 216
216
CAPÍTULO 12. INTRODUÇÃO A GRÁFICOS

Page 217

Capítulo 13
Árvores binárias
Árvores binárias são estruturas de dados em árvore em que cada nó tem no máximo dois
filhos
nós: a esquerda e a direita. Nós filhos podem conter referências a seus
pais. A raiz de uma árvore (o ancestral de todos os nós) pode existir dentro
ou fora da árvore.
Árvores binárias podem ser vistas como uma maneira de passar um número inicial n de tokens
para baixo, o que significa que, em qualquer ponto da árvore, a soma de todas as
nós serão n. O grau de cada nó é no máximo dois. Supondo que
uma árvore com raiz arbitrária possui m nós internos e cada nó interno possui
exatamente dois filhos, se a árvore tiver n folhas, o grau da árvore é n - 1:
2m = n + m - 1 → m = n - 1,
isto é, uma árvore com n nós tem exatamente n - 1 ramos ou grau.
Representando árvores binárias
A maneira mais simples (e mais absurda) de representar uma árvore binária é usar o Python
listas. O módulo a seguir constrói uma lista com uma raiz e duas vazias
sublista para as crianças. Para adicionar uma subárvore esquerda à raiz de uma árvore,
inserimos
uma nova lista na segunda posição da lista raiz. Observe que esse algoritmo é
não é muito eficiente devido às restrições que as listas do Python têm sobre a inserção
e estalando no meio.
[trees / BT_lists.py]
217

Page 218
218
CAPÍTULO 13. ÁRVORES BINÁRIAS
Figura 13.1: A altura (h) e a largura (número de folhas) de um (perfeitamente
árvore binária balanceada).
def BinaryTreeList (r):
retornar [r, [], []]
def insertLeft (root, newBranch):
t = root.pop (1)
se len (t)> 1:
root.insert (1, [newBranch, t, []])
mais :
root.insert (1, [newBranch, [], []])
raiz de retorno
def insertRight (root, newBranch):
t = root.pop (2)
se len (t)> 1:
root.insert (2, [newBranch, [], t])
mais :
root.insert (2, [newBranch, [], []])
raiz de retorno
def getRootVal (root):
raiz de retorno [0]

Page 219
219
def setRootVal (root, newVal):
raiz [0] = newVal
def getLeftChild (raiz):
raiz de retorno [1]
def getRightChild (raiz):
raiz de retorno [2]
def main ():
'' '
3
[5, [4, [], []], []]
[7, [], [6, [], []]]
'' '
r = BinaryTreeList (3)
insertLeft (r, 4)
insertLeft (r, 5)
insertRight (r, 6)
insertRight (r, 7)
print (getRootVal (r))
print (getLeftChild (r))
print (getRightChild (r))
se __name__ == '__main__' :
a Principal()
No entanto, esse método não é muito prático quando temos muitos ramos
(ou pelo menos precisa de muitas melhorias, por exemplo, como gerencia o
criação de novas listas e como ela exibe ou procura novos elementos).
Uma maneira mais natural de lidar com árvores binárias é representá-la como uma
coleção de nós. Um nó simples em uma árvore binária deve conter atributos
por valor e para filhos esquerdos e direitos, e pode ter um método para identificar
sai:
[trees / binary_tree.py]
'' 'Implementação de uma árvore binária e suas propriedades. Para
exemplo, o seguinte bt:

Page 220
220
CAPÍTULO 13. ÁRVORES BINÁRIAS
1
---> nível 0
2
3
---> nível 1
4
5
---> nível 2
6
7
---> nível 3
8
9
---> nível 4
tem as seguintes propriedades:
- TAMANHO OU NÚMERO DE NÓS: n = 9
- NÚMERO DE FILIAIS OU NÚMEROS INTERNOS: b = n-1 = 8
- VALOR DA RAIZ = 1
- MAX_DEPTH OU ALTURA: h = 4
- É EQUILÍBRIO? NÃO
- É BST? NÃO
'' '
classe NodeBT ( objeto ):
def __init __ (próprio, item = Nenhum, nível = 0):
self.item = item
self.level = level
self.left = Nenhum
self.right = Nenhum
def __repr __ (próprio):
retornar '{}' . formato (self.item)
def _addNextNode (self, valor, level_here = 1):
new_node = NodeBT (valor, nível_aqui)
se não for self.item:
self.item = new_node
elif não self.left:
self.left = new_node
elif não self.right:
self.right = new_node
mais :
self.left = self.left._addNextNode (valor, nível_aqui + 1)

Page 221
221
retornar auto
def _searchForNode (self, value):
se self.item == value:
retornar auto
mais :
encontrado = nenhum
se self.left:
found = self.left._searchForNode (valor)
se self.right:
found = found ou self.right._searchForNode (valor)
retorno encontrado
def _isLeaf (próprio):
return not self.right e not self.left
def _getMaxHeight (self):
'' 'Obtenha a altura máxima no nó, O (n)' ''
levelr, levell = 0, 0
se self.right:
levelr = self.right._getMaxHeight () + 1
se self.left:
levell = self.left._getMaxHeight () + 1
retorno máximo (levelr, levell)
def _getMinHeight (self, nível = 0):
'' 'Obtenha a altura mínima no nó, O (n)' ''
levelr, levell = -1, -1
se self.right:
levelr = self.right._getMinHeight (nível +1)
se self.left:
nível = self.left._getMinHeight (nível +1)
retorno mínimo (levelr, nível) + 1
def _isBalanced (próprio):

Page 222
222
CAPÍTULO 13. ÁRVORES BINÁRIAS
'' 'Descubra se a árvore está equilibrada, calculando
alturas primeiro, O (n2) '' '
if self._getMaxHeight () - self._getMinHeight () <2:
retornar falso
mais :
se self._isLeaf ():
return True
elif self.left e self.right:
return self.left._isBalanced () e
self.right._isBalanced ()
elif não self.left e self.right:
retornar self.right._isBalanced ()
elif não self.right e self.left:
return self.left._isBalanced ()
def _isBST (próprio, mintree = Nenhum, maxtree = Nenhum):
'' 'Descubra se a árvore é um BST, inorder' ''
se self.item:
se não for mintree:
mintree = self.item
se não for maxtree:
maxtree = self.item
se self._isLeaf ():
return True
elif self.left:
if self.left.item <self.item e mintree>
self.left.item:
mintree = self.left.item
return self.left._isBST (mintree, maxtree)
mais :
retornar falso
elif self.right:
if self.right.item> self.item e maxtree <
self.right.item:
maxtree = self.right.item
retornar self.right._isBST (mintree, maxtree)
mais :
retornar falso

Page 223
223
mais :
print ( 'A árvore está vazia' )
classe BinaryTree ( objeto ):
def __init __ (próprio):
self.root = Nenhum
def addNode (auto, valor):
se não for self.root:
self.root = NodeBT (valor)
mais :
self.root._addNextNode (value)
def isLeaf (auto, valor):
node = self.root._searchForNode (value)
return node._isLeaf ()
def getNodeLevel (self, item):
node = self.root._searchForNode (item)
se nó:
return node.level
mais :
gerar exceção ( 'Nó não encontrado' )
def isRoot (auto, valor):
retornar self.root.item == value
def getHeight (self):
retornar self.root._getMaxHeight ()
def isBalanced (self):
retornar self.root._isBalanced ()
Page 224
224
CAPÍTULO 13. ÁRVORES BINÁRIAS
def isBST (próprio):
retornar self.root._isBST ()
se __name__ == '__main__' :
bt = BinaryTree ()
print "Adicionando nós 1 a 10 na árvore ..."
para i na faixa (1, 10):
bt.addNode (i)
print "8 é uma folha?" , bt.isLeaf (8)
print "Qual é o nível do nó 8?" , bt.getNodeLevel (8)
print "O nó 10 é uma raiz?" , bt.isRoot (10)
print "O nó 1 é uma raiz?" , bt.isRoot (1)
print "Qual é a altura da árvore?" , bt.getHeight ()
print "Esta árvore é BST?" , bt.isBST ()
print "Esta árvore está equilibrada?" , bt.isBalanced ()
13.1 Árvores de pesquisa binária
Uma árvore de pesquisa binária (BST) é uma estrutura de dados que mantém os itens
classificados
ordem. Consiste em uma árvore binária. Cada nó tem um ponteiro para dois filhos,
e um ponteiro opcional para seus pais e o valor do nó. Para um BST
para ser válido, o elemento de cada nó é muito maior que todo elemento em seu
subárvore esquerda e menos de todos os elementos em sua subárvore direita. Em resumo, um
O BST possui as seguintes propriedades:
1. A subárvore esquerda de um nó contém apenas nós com chaves menores que o
chave do nó.
2. A subárvore direita de um nó contém apenas nós com chaves maiores que
a chave do nó.
3. As subárvores esquerda e direita também devem ser uma árvore de pesquisa binária.
4. Não deve haver nós duplicados.

Page 225
13.1 ÁRVORES BINÁRIAS DE PESQUISA
225
Se a árvore de pesquisa binária estiver equilibrada, as seguintes operações serão O (lnn):
(i) localizando um nó com um determinado valor (pesquisa), (ii) localizando um nó com
valor mínimo ou mínimo e (iii) inserção ou exclusão de um nó. Do
O BST não é equilibrado, os piores casos são O (n)
Representando árvores de pesquisa binária
O código a seguir implementa uma classe para uma árvore de pesquisa binária usando nosso
classe de árvore binária anterior como uma superclasse. A principal diferença agora é que
só podemos inserir um novo nó nas condições da árvore de pesquisa binária, que
naturalmente nos fornece um método para encontrar um elemento na árvore.
[trees / binary_search_tree.py]
de binary_tree import NodeBT, BinaryTree
classe NodeBST (NodeBT):
def __init __ (próprio, item = Nenhum, nível = 0):
self.item = item
self.level = level
self.left = Nenhum
self.right = Nenhum
def _addNextNode (self, valor, level_here = 1):
new_node = NodeBST (valor, level_here)
se não for self.item:
self.item = new_node
mais :
se valor> self.item:
self.right = self.right e
self.right._addNextNode (valor, level_here + 1) ou
new_node
valor elif <self.item:
self.left = self.left e
self.left._addNextNode (valor, level_here + 1) ou
new_node
mais :

Page 226
226
CAPÍTULO 13. ÁRVORES BINÁRIAS
print ( "BSTs não suportam itens repetidos". )
retornar auto # isso é necessário !!!
def _searchForNode (self, value):
se self.item == value:
retornar auto
elif self.left e value <self.item:
retornar self.left._searchForNode (valor)
elif self.right e value> self.item:
retornar self.right._searchForNode (value)
mais :
retornar falso
classe BinarySearchTree (BinaryTree):
def __init __ (próprio):
self.root = Nenhum
def addNode (auto, valor):
se não for self.root:
self.root = NodeBST (valor)
mais :
self.root._addNextNode (value)
se __name__ == '__main__' :
bst = BinarySearchTree ()
print "Adicionando nós 1 a 10 na árvore ..."
para i na faixa (1, 10):
bst.addNode (i)
print "8 é uma folha?" , bst.isLeaf (8)
print "Qual é o nível do nó 8?" , bst.getNodeLevel (8)
print "O nó 10 é uma raiz?" , bst.isRoot (10)
print "O nó 1 é uma raiz?" , bst.isRoot (1)

Page 227
13.2 AUTO-EQUILÍBRIO BSTS
227
print "Qual é a altura da árvore?" , bst.getHeight ()
print "Esta árvore é BST?" , bst.isBST ()
print "Esta árvore está equilibrada?" , bst.isBalanced ()
13.2 BSTs com auto-equilíbrio
Uma árvore balanceada é uma árvore em que as diferenças de altura de cada subárvore
é no máximo igual a 1. Uma árvore de pesquisa binária com auto balanceamento é qualquer
árvore de pesquisa binária que se mantém automaticamente equilibrada. Aplicando um
condição de equilíbrio, garantimos que o pior caso de execução da árvore comum
as operações serão no máximo O (lnn).
Um fator de equilíbrio pode ser atribuído a cada nó interno em uma árvore,
sendo a diferença entre as alturas das subárvores esquerda e direita. Lá
Existem muitos métodos de balanceamento para árvores, mas geralmente são baseados em
dois
operações:
Spl Divisão de nós (e mesclagem): não é permitido que os nós tenham mais de
dois filhos; portanto, quando um nó fica cheio demais, ele se divide em dois subnós.
Rot Rotações de nós: processo de troca de arestas. Se x é o pai de y, nós
faça y o pai de xex deve assumir o controle de um dos filhos de y.
Árvores AVL
Uma árvore AVL é uma árvore de pesquisa binária com uma condição de auto balanceamento
em que o
A diferença entre a altura das subárvores esquerda e direita não pode ser mais
do que um.
Para implementar uma árvore AVL, podemos começar adicionando um auto-equilíbrio
método para nossas classes BST, chamado toda vez que adicionamos um novo nó ao
árvore. O método funciona verificando continuamente a altura da árvore,
que é adicionado como um novo atributo.
Árvores vermelho-pretas
Árvores preto-vermelho são uma evolução de uma árvore de busca binária que visa manter o
árvore equilibrada sem afetar a complexidade das operações primitivas.

Page 228
228
CAPÍTULO 13. ÁRVORES BINÁRIAS
Isso é feito colorindo cada nó da árvore com vermelho ou preto e
preservando um conjunto de propriedades que garante que o caminho mais profundo do
a árvore não é maior que o dobro da menor.
Árvores vermelho-pretas têm as seguintes propriedades:
⋆ Cada nó é colorido em vermelho ou preto.
⋆ Todos os nós das folhas (nulos) são coloridos em preto; se o filho de um nó estiver ausente
então assumiremos que ele tem um filho nulo naquele lugar e esse nulo
a criança é sempre colorida de preto.
⋆ Os dois filhos de um nó vermelho devem ser nós pretos.
⋆ Todo caminho de um nó n para uma folha descendente tem o mesmo número
de nós pretos (sem contar o nó n). Chamamos esse número de preto
altura de n.
Montes binários
Montes binários são árvores binárias balanceadas completas. A propriedade heap torna
é mais fácil manter a estrutura, ou seja, o equilíbrio da árvore. Há sim
não é necessário modificar uma estrutura da árvore dividindo ou rotando nós em um
heap: a única operação será trocar nós pai e filho.
Em uma pilha binária, a raiz (o elemento menor ou maior) é sempre
encontrado em h [0]. Considerando um nó no índice i:
Index o índice pai é i-1
2
,
⋆ o índice filho esquerdo é 2i + 1,
Index o índice filho certo é 2i + 2.
Page 229

Capítulo 14
Traversals and Problems on
Gráficos e árvores
Traversals são algoritmos usados para visitar os objetos (nós) em alguns
estrutura, como uma árvore ou um gráfico. Problemas transversais podem ser visitas
cada nó ou visitando apenas alguns nós específicos.
14.1 Pesquisa em profundidade
A profundidade pela primeira travessia ou a pesquisa pela profundidade (DFS) são algoritmos
que pesquisam
mais fundo primeiro em um gráfico ou em uma árvore. Sua diferença quando em gráficos ou
árvores é
que, no caso de gráficos, é necessário marcar os nós como visitados (caso contrário,
pode ficar preso em um loop).
Figura 14.1: Percursos em árvore binária: pré-encomenda, inorder, pós-encomenda e
respiração
primeira pesquisa.
229

Page 230
230CAPÍTULO 14. VIAGENS E PROBLEMAS EM GRÁFICOS E ÁRVORES
Os algoritmos DFS são chamados uma vez para cada nó acessível a partir do
nó inicial, olhando para seus sucessores. O tempo de execução é O (número de alcançáveis
nós + número total de arestas de saída desses nós) = O (V + E).
DFSs geralmente são implementados usando a estrutura LIFO, como pilhas para manter
rastrear os nós descobertos, e eles podem ser divididos em três diferentes
estratégias:
Pré-encomenda: Visite um nó antes de percorrer as subárvores (raiz → esquerda → direita):
pré-encomenda def (raiz):
se root! = 0:
rendimento root.value
encomenda (root.left)
pré-encomenda (root.right)
Pós-encomenda: Visite um nó depois de percorrer todas as subárvores (esquerda → direita
→ raiz):
def postorder (root):
se root! = 0:
postorder (root.left)
postorder (root.right)
rendimento root.value
Inorder: visite um nó depois de percorrer sua subárvore esquerda, mas antes da direita
subárvore (esquerda → raiz → direita):
def inorder (raiz):
se root! = 0:
inorder (root.left)
rendimento root.value
inorder (root.right)
Page 231
14.2 PESQUISA PRIMEIRA PESQUISA
231
14.2 Pesquisa pela primeira vez
A travessia de largura pela primeira vez, ou BFS (respiração pela primeira vez), são
algoritmos que produzem
os valores de todos os nós de uma profundidade específica antes de ir para qualquer nó mais
profundo.
Problemas que usam BFS geralmente pedem para encontrar o menor número de etapas
(ou o caminho mais curto) necessário para atingir um certo ponto final desde o início
ponto. Tradicionalmente, os BFSs são implementados usando uma lista para armazenar os
valores
dos nós visitados e, em seguida, uma fila FIFO para armazenar os nós que possuem
ainda a ser visitado. O tempo de execução total também é O (V + E).
14.3 Representando atravessamentos de árvores
Existem muitas maneiras de escrever travessias. Por exemplo, iterativamente:
[trees / transversal_BST_iteratively.py]
das coleções import deque
de binary_search_tree import BinarySearchTree, NodeBST
classe BSTwithTransversalIterative (BinarySearchTree):
def inorder (auto):
current = self.root
nós, pilha = [], []
enquanto pilha ou corrente:
se atual:
stack.append (atual)
current = current.left
mais :
current = stack.pop ()
nodes.append (current.item) # é isso que muda
current = current.right
nós de retorno
pré-encomenda de def (auto):
current = self.root

Page 232
232CAPÍTULO 14. VIAGENS E PROBLEMAS EM GRÁFICOS E ÁRVORES
nós, pilha = [], []
enquanto pilha ou corrente:
se atual:
nodes.append (current.item) # é isso que muda
stack.append (atual)
current = current.left
mais :
current = stack.pop ()
current = current.right
nós de retorno
def preorder2 (self):
nós = []
pilha = [raiz própria]
while stack:
current = stack.pop ()
se atual:
nodes.append (current.item)
stack.append (current.right)
stack.append (current.left)
nós de retorno
def BFT (auto):
current = self.root
nós = []
fila = deque ()
queue.append (atual)
enquanto fila:
current = queue.popleft ()
nodes.append (current.item)
if current.left:
queue.append (current.left) # ESQUERDA PRIMEIRO!
if current.right:
queue.append (current.right)
nós de retorno
se __name__ == '__main__' :

Page 233
14.3 REPRESENTANDO TRAVERSAIS DE ÁRVORES
233
bst = BSTwithTransversalIterative ()
l = [10, 5, 6, 3, 8, 2, 1, 11, 9, 4]
para i em l:
bst.addNode (i)
print "8 é uma folha?" , bst.isLeaf (8)
print "Qual é o nível do nó 8?" , bst.getNodeLevel (8)
print "O nó 10 é uma raiz?" , bst.isRoot (10)
print "O nó 1 é uma raiz?" , bst.isRoot (1)
print "Qual é a altura da árvore?" , bst.getHeight ()
print "Esta árvore é BST?" , bst.isBST ()
print "Esta árvore está equilibrada?" , bst.isBalanced ()
print ( "Pré-encomenda I:" , bst.preorder ())
print ( "Pré-encomenda II:" , bst.preorder2 ())
print ( "Em ordem:" , bst.inorder ())
print ( "BFT:" , bst.BFT ())
Ou recursivamente:
[trees / transversal_BST_recursively.py]
de binary_search_tree import BinarySearchTree, NodeBST
classe BSTwithTransversalRecursively (BinarySearchTree):
def __init __ (próprio):
self.root = Nenhum
self.nodes_BFS = []
self.nodes_pre = []
self.nodes_post = []
self.nodes_in = []
def BFT (auto):
self.root.level = 0
fila = [auto.root]
current_level = self.root.level

Page 234
234CAPÍTULO 14. VIAGENS E PROBLEMAS EM GRÁFICOS E ÁRVORES
enquanto len (fila)> 0:
current_node = queue.pop (0)
se current_node.level> current_level:
current_level + = 1
self.nodes_BFS.append (current_node.item)
if current_node.left:
current_node.left.level = current_level + 1
queue.append (current_node.left)
if current_node.right:
current_node.right.level = current_level + 1
queue.append (current_node.right)
retornar self.nodes_BFS
def inorder (próprio, nó = Nenhum, nível = 0):
se não nó e nível == 0:
node = self.root
se nó:
self.inorder (node.left, nível + 1)
self.nodes_in.append (node.item)
self.inorder (node.right, nível + 1)
retornar self.nodes_in
pré-ordem de def (auto, nó = Nenhum, nível = 0):
se não nó e nível == 0:
node = self.root
se nó:
self.nodes_pre.append (node.item)
self.preorder (node.left, nível + 1)
self.preorder (node.right, nível + 1)
retornar self.nodes_pre
def postorder (self, node = None, level = 0):
se não nó e nível == 0:
node = self.root
se nó:
self.postorder (node.left, nível + 1)
self.postorder (node.right, nível + 1)

Page 235
14.3 REPRESENTANDO TRAVERSAIS DE ÁRVORES
235
self.nodes_post.append (node.item)
retornar self.nodes_post
se __name__ == '__main__' :
bst = BSTwithTransversalRecursively ()
l = [10, 5, 6, 3, 8, 2, 1, 11, 9, 4]
para i em l:
bst.addNode (i)
print "8 é uma folha?" , bst.isLeaf (8)
print "Qual é o nível do nó 8?" , bst.getNodeLevel (8)
print "O nó 10 é uma raiz?" , bst.isRoot (10)
print "O nó 1 é uma raiz?" , bst.isRoot (1)
print "Qual é a altura da árvore?" , bst.getHeight ()
print "Esta árvore é BST?" , bst.isBST ()
print "Esta árvore está equilibrada?" , bst.isBalanced ()
print ( "Pré-encomenda:" , bst.preorder ())
print ( "Pós-encomenda:" , bst.postorder ())
print ( "Em ordem:" , bst.inorder ())
print ( "BFT:" , bst.BFT ())

Page 236
236CAPÍTULO 14. VIAGENS E PROBLEMAS EM GRÁFICOS E ÁRVORES
14.4 Exercícios adicionais
Antepassado em um BST
O exemplo abaixo encontra o ancestral comum de nível mais baixo de dois nós em
uma árvore de pesquisa binária:
[trees / transversal_BST_ancestor.py]
'' 'encontre o ancestral mais baixo em um BST' ''
de transversal_BST_recursivamente importar
BSTwithTransversalRecursively
def find_ancestor (caminho, valor baixo, valor alto):
enquanto caminho:
valor_alterno = caminho [0]
se current_value <low_value:
tente :
caminho = caminho [2:]
exceto :
return current_value
elif current_value> high_value:
tente :
caminho = caminho [1:]
exceto :
return current_value
elif valor_valor <= valor_atual <= valor_alvo:
return current_value
se __name__ == '__main__' :
bst = BSTwithTransversalRecursively ()
l = [10, 5, 15, 1, 6, 11, 50]
para i em l:
bst.addNode (i)
path = bst.preorder ()
print ( "O caminho na ordem:" , caminho)
print ( "O caminho entre 1 e 6 é:" , find_ancestor (caminho, 1,

Page 237
14.4 EXERCÍCIOS ADICIONAIS
237
6))
print ( "O caminho entre 1 e 11 é:" , find_ancestor (caminho, 1,
11))
print ( "O caminho entre 11 e 50 é:" , find_ancestor (caminho,
11, 50))
print ( "O caminho entre 5 e 15 é:" , find_ancestor (caminho, 5,
15))

Page 238

Índice
-O, 73
-c, 73
-v, 110
add (), 52
adjacente
listas, 209
matrizes, 211
algoritmos
vendedor ambulante, 165
aliasing, 94
tudo 72
append (), 39
matriz
unimodal, 195
matrizes, 38
as, 90
afirmar, 12 , 73
análise assintótica, 163
BFS, 124, 209, 231
O grande, 163
bin, 16
pesquisa binária, 191 , 192
bisect (), 192
ramificação, 60
pausa, 78
bytearray, 46
bytes, 46
cache, 199 , 200
classe, 90 , 95, 96, 98
claro (), 53 , 58
fechar (), 84
cmath, 14
coleções
defaultdict, 61 , 81, 172
deque, 125
namedtuple, 93
OrderedDict, 62
compreensões, 43
continuar, 78
count (), 34, 36, 41
cPerfil, 108
daemons, 104
impasse, 104
depuração, 107
decimal, 15, 31
decoradores, 98 , 200
del, 41 , 94
DFS, 119, 209, 229
dicionários, 55 , 211
lambda, 81
diferença (), 52
descartar (), 53
docstrings, 110
doctest, 110
programação dinâmica, 199
enumerar (), 78
eq, 105
238
Page 239
ÍNDICE
239
exceções, 12 , 88
AmbienteErro, 86, 87
KeyError, 53
PickingError, 86
StandardError, 90
unpickingError, 87
ValorErro, 34, 40
extend (), 39 , 172
FIFO, 104, 119, 231
fileno (), 85
filtro (), 79
finalmente 90
find (), 34
formato (), 31, 33, 78
functools, 200
coleta de lixo, 41
geradores, 77 , 108
get (), 33 , 57
gráficos, 207
completo, 208
conectado, 209
grau, 208
direção, 207
isolado, 208
vizinhos, 209
subgrafo, 207
peso, 208 , 211
florestas, 209
gzip, 86
hash, 55 , 105
montão, 126 , 180
binário, 228
heapq, 127
heappop (), 127
altura, 166 , 227
hexadecimal, 16
índice (), 34 , 36, 41
init, 72
input (), 84
inserto (), 40 , 75
interseção (), 52
itens (), 58
join (), 108
teclas (), 58
lambda, 80
LIFO, 115
listas, 38
adjacente, 209
ligado, 131 , 173
little endian, 88
ljust (), 30
mapa, 80
mediana, 189
memorização, 199, 201
nome, 110
espaço para nome, 95
Nenhuma, 71, 76, 87
nariz, 109
NP, 164
NP-rígido, 165
NPC, 164
out, 16
aberto (), 82
os, 87
P, 164
P = NP, 165
embalagem (), 88
embalagem, 31, 36, 42
Palíndromo, 146, 156

Page 240
240
ÍNDICE
peek (), 84
picles, 85
pop (), 38, 40, 53, 58, 210
popitem (), 58
números primos, 21
criação de perfil, 107
propriedades, 100
pyc, 73
pyo, 73
filas, 104, 105, 119, 231
aumento, 89 , 90
intervalo (), 78
bytes brutos, 46
read (), 83
readline (), 83
linhas de leitura (), 83
relações recursivas, 166
remove (), 40, 53
substituir (), 35
reverse (), 42
rjust (), 30
procurar (), 84
Series
fibonacci, 199
conjuntos, 51 , 210
veneziana, 85
singletons, 76
fatiar, 29 , 75
sort (), 42
classificado (), 60 , 173
triagem
contagem, 172
gnomo, 171
pilha, 180
inserção, 169
mesclar, 173
rápido, 178
seleção, 170
rápido, 189
split (), 32 , 33
linhas de divisão (), 32
pilha, 165
pilhas, 71 , 88, 115
cordas, 29 , 33
tira (), 32 , 78
struct, 88
subprocesso, 104
sys, 73
tell (), 84
teste
unit, 109
unittest, 12 , 110
rosqueamento, 103 , 104
timeit, 44 , 109
rastreio, 88
Árvores
vermelho-preto, 227
árvores, 209
AVL, 227
binário, 217
BST, 224
rotação, 227
tente, 84 -87, 90
tuplas, 35
nomeado, 37
unicode, 30 , 88
união, 51
união (), 52
mais unittest, 13
update (), 52
valores (), 58

Page 241
ÍNDICE
241
write (), 83
rendimento, 60 , 77
zip (), 79

Page 242
242
ÍNDICE

Page 243

Bibliografia
Sites:
[Python interativo] http://interactivepython.org
[ G o o g l e Guia de Estilo] http://google-styleguide.code.com/svn/trunk/pyguide.html
[O repositório git deste livro] https://github.com/mariwahl/Python-
e-Algoritmos e Estruturas de Dados
[Folha Big-O] http://bigocheatsheet.com/
Livros:
[Um bom livro para engenheiro de software. Entrevistas] Quebrando a codificação
terview, Gayle Laakmann McDowell, 2013
[Um bom livro sobre Python 3] Programação em Python 3: uma introdução completa
introdução à linguagem Python 3.1, Mark Summerfield, 2011
243

Page 244
244
BIBLIOGRAFIA
[Um bom livro sobre Python] Aprenda Python da maneira mais difícil, Zed A. Shaw, 2010
[Um bom livro de algoritmos] Dominando algoritmos básicos no Python
Língua, Magnus Lie Hetland, 2010
[Outro belo livro de algoritmos] O Manual de Projeto de Algoritmos, SS
Skiena, 2008
[Outro bom livro do Python] Programação orientada a objetos do Python 3,
Dusty Phillips, 2010
[Outro bom guia para o Software Eng. Entrevistas] programação
Pérolas, Jon Bentley, 1986

Você também pode gostar