Subido por proyectoedificiocct4

02 TRADUCIDO-Algorithms for Dummies

Anuncio
Machine Translated by Google
Machine Translated by Google
Machine Translated by Google
Algoritmos
por John Paul Mueller y Luca Massaron
Machine Translated by Google
Algoritmos para principiantes®
Publicado por: John Wiley & Sons, Inc., 111 River Street, Hoboken, Nueva Jersey 07030­5774, www.wiley.com
Copyright © 2017 de John Wiley & Sons, Inc., Hoboken, Nueva Jersey
Copyright de compilación de software y medios © 2017 de John Wiley & Sons, Inc. Todos los derechos reservados.
Publicado simultáneamente en Canadá
Ninguna parte de esta publicación puede reproducirse, almacenarse en un sistema de recuperación ni transmitirse de ninguna
forma ni por ningún medio, electrónico, mecánico, fotocopia, grabación, escaneo o de otro tipo, excepto según lo permitido en
las Secciones 107 o 108 de la Ley de Derechos de Autor de los Estados Unidos de 1976. Actuar, sin el permiso previo
por escrito del Editor. Las solicitudes de permiso al editor deben dirigirse al Departamento de Permisos, John Wiley & Sons, Inc.,
111 River Street, Hoboken, NJ 07030, (201) 748­6011, fax (201) 748­6008, o en línea en http //www.wiley.com/go/permissions.
Marcas comerciales: Wiley, For Dummies, el logotipo de Dummies Man, Dummies.com, Making Everything Easier y la imagen comercial
relacionada son marcas comerciales o marcas comerciales registradas de John Wiley & Sons, Inc. y no pueden usarse sin permiso por escrito.
Todas las demás marcas comerciales son propiedad de sus respectivos dueños. John Wiley & Sons, Inc. no está asociado con ningún
producto o proveedor mencionado en este libro.
LÍMITE DE RESPONSABILIDAD/RENUNCIA DE GARANTÍA: EL EDITOR Y EL AUTOR NO REALIZAN
DECLARACIONES O GARANTÍAS CON RESPECTO A LA EXACTITUD O INTEGRIDAD DEL CONTENIDO
DE ESTE TRABAJO Y RENUNCIA ESPECÍFICAMENTE A TODAS LAS GARANTÍAS, INCLUYENDO, SIN LIMITACIÓN, LAS GARANTÍAS DE IDONEIDAD PARA
UN PROPÓSITO PARTICULAR. NO SE PUEDE CREAR O AMPLIAR NINGUNA GARANTÍA POR VENTAS O
MATERIALES PROMOCIONALES. LOS CONSEJOS Y ESTRATEGIAS CONTENIDOS AQUÍ PUEDEN NO SER ADECUADOS PARA
CADA SITUACIÓN. ESTA OBRA SE VENDE EN EL ENTENDIMIENTO DE QUE EL EDITOR NO ESTÁ COMPROMETIDO
EN LA PRESTACIÓN DE SERVICIOS LEGALES, CONTABLES U OTROS SERVICIOS PROFESIONALES. SI SE REQUIERE ASISTENCIA PROFESIONAL, SE
DEBEN SOLICITAR LOS SERVICIOS DE UN PROFESIONAL COMPETENTE. NI EL EDITOR NI EL AUTOR SERÁN RESPONSABLES POR LOS DAÑOS QUE
SURJAN DEL PRESENTE. EL HECHO DE QUE UN
EN ESTE TRABAJO SE HACE REFERENCIA A LA ORGANIZACIÓN O SITIO WEB COMO UNA CITA Y/O UNA FUENTE POTENCIAL DE
MÁS INFORMACIÓN NO SIGNIFICA QUE EL AUTOR O EL EDITOR APRUEBA LA INFORMACIÓN
LA ORGANIZACIÓN O SITIO WEB PUEDE PROPORCIONAR O RECOMENDACIONES QUE PUEDE HACER. ADEMÁS, LOS LECTORES DEBEN TENER EN
CUENTA QUE LOS SITIOS WEB DE INTERNET ENUMERADOS EN ESTE TRABAJO PUEDEN HABER CAMBIADO O DESAPARECIDOS
ENTRE CUANDO SE ESCRIBIÓ ESTA OBRA Y CUANDO SE LEYE.
Para obtener información general sobre nuestros otros productos y servicios, comuníquese con nuestro Departamento de Atención al Cliente
dentro de los EE. UU. al 877­762­2974, fuera de los EE. UU. al 317­572­3993 o por fax al 317­572­4002. Para obtener asistencia técnica, visite
https://hub.wiley.com/community/support/dummies.
Wiley publica en una variedad de formatos impresos y electrónicos y mediante impresión bajo demanda. Es posible que parte del material
incluido en las versiones impresas estándar de este libro no esté incluido en libros electrónicos o en impresión bajo demanda. Si este libro
hace referencia a medios como un CD o DVD que no están incluidos en la versión que compró, puede descargar este material en http://
booksupport.wiley.com. Para obtener más información sobre los productos Wiley, visite www.wiley.com.
Número de control de la Biblioteca del Congreso: 2017936606
ISBN 978­1­119­33049­3 (pbk); ISBN 978­1­119­33053­0 (ebk); ISBN 978­1­119­33052­3 (ebk)
Fabricado en los Estados Unidos de América.
10 9 8 7 6 5 4 3 2 1
Machine Translated by Google
Contenido de un vistazo
Introducción
1
........................................................
............................................
Parte 1: Primeros pasos
7
CAPÍTULO 1: Introducción a los algoritmos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9. . . . . .
CAPÍTULO 2: Consideración del diseño de algoritmos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
CAPÍTULO 3: Uso de Python para trabajar con algoritmos. . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
CAPÍTULO 4: Introducción a Python para la programación de algoritmos. . . . . . . . . . . . . . . . . . . 67
CAPÍTULO 5: Realizar manipulaciones de datos esenciales utilizando Python. . . . . . . . . . . . . 91
Parte 2: Comprender la necesidad de ordenar y buscar
.....
CAPÍTULO 6: Estructuración de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CAPÍTULO 7: Organización y búsqueda de datos
.................................
113
115
133
Parte 3: Explorando el mundo de los gráficos . . . . . . . . . . . . . . . . . . . . . .
153
CAPÍTULO 8: Comprender los conceptos básicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
155
de Graph CAPÍTULO 9: Reconectar los
........................................
puntos CAPÍTULO 10: Descubrir los secretos
....................................
173
197
de Graph CAPÍTULO 11: Obtener la página web correcta. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
Parte 4: Luchando con Big Data CAPÍTULO 12: Gestión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
de Big Data CAPÍTULO 13:
...........................................
223
225
Paralelización de operaciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
249
CAPÍTULO 14: Compresión de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
265
Parte 5: Desafiando problemas difíciles CAPÍTULO . . . . . . . . . . . . . . . . . . . . . .
15: Trabajar con algoritmos codiciosos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
281
283
CAPÍTULO 16: Confiar en la programación dinámica. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
299
CAPÍTULO 17: Uso de algoritmos aleatorios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
321
CAPÍTULO 18: Realización de búsqueda
......................................
339
local CAPÍTULO 19: Empleo de programación lineal. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
357
CAPÍTULO 20: Considerando la heurística . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
371
Parte 6: La parte de decenas
389
........................................
CAPÍTULO 21: Diez algoritmos que están cambiando el mundo
CAPÍTULO 22: Diez problemas algorítmicos aún por resolver
Índice
....................
..........................
..............................................................
391
399
405
Machine Translated by Google
Machine Translated by Google
Tabla de contenido
INTRODUCCIÓN
Sobre este libro
...................................................
..............................................
1
1
Suposiciones tontas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Iconos utilizados en este libro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
.............................................
Más allá del libro
Adónde ir desde aquí
.......................................
4
PARTE 1: PRIMEROS PASOS
......................................
3
5
7
CAPÍTULO 1: Introducción a los algoritmos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Describir algoritmos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
...................................
El algoritmo de definición
10
11
utiliza algoritmos de búsqueda en todas partes. . . . . . . . . . . . . . . . . . . . . . . . . . . .14
.
15
Usar computadoras para resolver problemas . . . . . . . . . . . . . . . . . . . . . . . . . . .
Aprovechar las CPU y GPU modernas
........................
dieciséis
Trabajar con chips de propósito especial. . . . . . . . . . . . . . . . . . . . . . . . .
......................................
Aprovechar las redes
17
..................................
18
Aprovechar los datos
....................
disponibles Distinguir entre Problemas y Soluciones
Ser correcto y eficiente Descubrir. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
que no hay nada gratis Adaptar la
..........................
18
19
19
20
estrategia al problema. . . . . . . . . . . . . . . . . . . . . . .
.....................
Describir algoritmos en una lengua franca
20
Enfrentar problemas difíciles. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
..........................
21
Estructurar datos para obtener una solución
Comprender el punto de vista de una computadora . . . . . . . . . . . . . . . . . . .
........................
Organizar los datos marca la diferencia
20
21
22
22
CAPÍTULO 2: Consideración del diseño de algoritmos . . . . . . . . . . . . . . . . . . . . . . . . 23
Comenzar a resolver un problema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
24
Modelar problemas del mundo real. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25
Encontrar soluciones y contraejemplos. . . . . . . . . . . . . . . . . . . . .
26
De pie sobre los hombros de gigantes. . . . . . . . . . . . . . . . . . . . . . . . .
27
Dividiendo y Conquistando. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.............................
Evitar soluciones de fuerza bruta
Empezando por hacerlo más sencillo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
...................
Generalmente es mejor resolver un problema.
Aprender que la avaricia puede ser buena. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Aplicar razonamiento codicioso................................31
Llegar a una buena solución 32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Tabla de contenidos v
28
29
29
30
31
Machine Translated by Google
......................
33
Representar el problema como un espacio. . . . . . . . . . . . . . . . . . . . . . .
....................
Ir al azar y ser bendecido por la suerte Usar una
33
Costos de computación y seguimiento de heurísticas
34
........................
35
Evaluación de algoritmos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.........................
Simulando usando máquinas abstractas
35
función heurística y de costos
................................
Cada vez más abstracto
Trabajar con funciones
....................................
CAPÍTULO 3: Uso de Python para trabajar con algoritmos . . . . . . . . . . . . . .
Considerando los beneficios de Python. . . . . . . . . . . . . . . . . . . . . . . . . . . .
Comprender por qué este libro utiliza Python. . . . . . . . . . . . . . . . . . .
....................................
Trabajar con MATLAB
............
Considerar otros entornos de prueba de algoritmos
............................
Observando las distribuciones de Python
obteniendo Analytics Anaconda
..............................
36
37
38
43
45
45
47
48
48
49
considerando Enthinkt Canopy Express. . . . . . . . . . . . . . . . . . . . .
50
Teniendo en cuenta Pythonxy. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
50
Considerando WinPython. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
....................................
51
...................................
52
.................................
54
Instalación de Python en Linux
Instalación de Python en MacOS
Instalación de Python en Windows
...................
Descarga de conjuntos de datos y código de ejemplo
...................................
Uso de Jupyter Notebook
Definición del repositorio de código. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Comprender los conjuntos de datos utilizados en este libro . . . . . . . . . . . . . . . .
51
58
58
59
sesenta y cinco
CAPÍTULO 4: Introducción a Python para la programación de
algoritmos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
67
Trabajar con números y lógica. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
68
70
Comparar datos usando expresiones booleanas. . . . . . . . . . . . . . .
72
Creación y uso de cadenas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Interactuar con fechas Crear . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
y utilizar funciones
74
76
.................................
77
................................
77
Creando funciones reutilizables
Funciones de llamada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Uso de declaraciones condicionales y de bucle
........................
78
81
Tomar decisiones usando la sentencia if Elegir entre . . . . . . . . . . . . . . . . . . . . .
....
múltiples opciones usando decisiones anidadas Realizar tareas repetitivas
81
usando el bucle for. . . . . . . . . . . . . . . .
.................................
83
Usando la instrucción while
Almacenamiento de datos usando conjuntos, listas y tuplas. . . . . . . . . . . . . . . . . . . . . . .
vi Algoritmos para principiantes
69
Realización de asignaciones de variables. . . . . . . . . . . . . . . . . . . . . . . . . . .
.........................................
Hacer aritmética
82
84
85
Machine Translated by Google
Crear conjuntos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.............................................
Crear listas
85
86
Crear y usar tuplas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.....................................
Definición de iteradores útiles
88
Indexación de datos mediante diccionarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
90
89
CAPÍTULO 5: Realizar manipulaciones de datos esenciales
91
Usando Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Realizar cálculos usando vectores y matrices 92
.............
Comprender las operaciones escalares y vectoriales. . . . . . . . . . . . . . . . . 93
Realizar multiplicación de vectores. . . . . . . . . . . . . . . . . . . . . . . . . . . 95
Crear una matriz es la forma correcta de comenzar 95 . . . . . . . . . . . . . . . . . . . .
......................................
Multiplicar matrices 97
Definición de operaciones matriciales avanzadas. . . . . . . . . . . . . . . . . . . . . . . 98
Creando combinaciones de la manera correcta. . . . . . . . . . . . . . . . . . . . . . . . . 100
Distinguir permutaciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
Combinaciones aleatorias 101 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Frente a repeticiones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
...................
Obtener los resultados deseados utilizando la recursividad 103
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...............
Explicando la recursividad
Eliminando la recursividad de
..............................
103
106
llamadas finales Realizando tareas más rápidamente. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
.
107
Considerando dividir y conquistar. . . . . . . . . . . . . . . . . . . . . . . . . . .
Distinguir entre diferentes soluciones posibles
..........
110
PARTE 2: ENTENDIENDO LA NECESIDAD
........................................
ORDENAR Y BUSCAR
113
CAPÍTULO 6: Estructuración de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
115
Determinar la necesidad de estructura................................116
.........................
Hacer que sea más fácil ver el contenido
116
.......................
117
la necesidad de remediación Apilar y apilar datos . . . . . . . . . . . . . . . . . . . . . .
.............................
en orden Ordenar en pilas Usar
118
Emparejar datos de varias fuentes Considerar
.......................................
colas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
............................
Encontrar datos usando diccionarios
Trabajar con árboles
.........................................
.........................
Comprender los conceptos básicos de los
.........................................
árboles Construir un.árbol
121
121
123
124
125
125
126
Representar relaciones en un gráfico. . . . . . . . . . . . . . . . . . . . . . . . . . . .
Más allá de los árboles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
128
Construyendo gráficos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
130
Tabla de contenidos vii
128
Machine Translated by Google
CAPÍTULO 7: Organización y búsqueda de datos Clasificación de
......................
datos mediante Mergesort y Quicksort. . . . . . . . . . . . . . . . . . .
133
134
Definir por qué es importante ordenar los datos. . . . . . . . . . . . . . . . . . . . .
134
Ordenar datos ingenuamente. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
135
Emplear mejores técnicas de clasificación. . . . . . . . . . . . . . . . . . . . . . . . . .
137
Uso de árboles de búsqueda y el montón. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
142
Considerando la necesidad de realizar una búsqueda eficaz. . . . . . . . . . . . . . . . . . 143
145
Construyendo un árbol de búsqueda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
binario Realizando búsquedas especializadas usando un montón binario. . . . . . . . . 146
Confiando en Hashing. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
............................
Poner todo en cubos
Evitar colisiones
.......................................
Creando tu propia función hash
..........................
147
148
149
150
PARTE 3: EXPLORANDO EL MUNDO DE LOS GRÁFICOS . . . . . . . . . . . .
153
CAPÍTULO 8: Comprensión de los conceptos básicos de gráficos . . . . . . . . . . . . . . . . . . . . . . . .
155
Explicando la importancia de las redes................................156
Considerando la esencia de un gráfico. . . . . . . . . . . . . . . . . . . . . . . . 156
Encontrar gráficos por todas partes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
Mostrando el lado social de los gráficos. . . . . . . . . . . . . . . . . . . . . . . . . . 159
Comprender los subgrafos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
Definición de cómo dibujar un gráfico. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
..........................
Distinguir los atributos clave 162
Dibujando el gráfico. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Medición de la funcionalidad del gráfico. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
.............................
Contando aristas y vértices 164
Centralidad informática................................166
...........................
169
...............................
170
Poner un gráfico en formato numérico
Agregar un gráfico a una matriz
Usar representaciones dispersas. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
171
Usar una lista para contener un gráfico. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
CAPÍTULO 9: Reconectar los puntos atravesando un
...............................
gráfico de manera eficiente. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
174
Creando el gráfico. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.............................
Aplicar la búsqueda en amplitud
175
...............................
177
Aplicar la búsqueda en
profundidad Determinar qué aplicación usar
......................
176
179
Ordenar los elementos del gráfico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
180
Trabajando en gráficos acíclicos dirigidos (DAG). . . . . . . . . . . . . . . . .
181
Confiando en la clasificación topológica. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
........................
Reducir a un árbol de expansión mínimo
viii Algoritmos para principiantes
173
182
183
Machine Translated by Google
Descubrir los algoritmos correctos a utilizar.
...................
Introducir colas prioritarias. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
185
186
Aprovechando el algoritmo de Prim. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
187
Probando el algoritmo de Kruskal. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
...................
Determinar qué algoritmo funciona mejor
189
Encontrar la ruta más corta
..................................
191
192
Definiendo lo que significa encontrar el camino más corto. . . . . . . . . . . . .
192
Explicando el algoritmo de Dijkstra. . . . . . . . . . . . . . . . . . . . . . . . . . . .
194
CAPÍTULO 10: Descubriendo los secretos de los gráficos . . . . . . . . . . . . . . . . . . . . . . . . . . .
197
Visualizando las redes sociales como gráficos. . . . . . . . . . . . . . . . . . . . . . . .
198
Agrupación de redes en grupos. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Descubriendo comunidades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
201
Navegando por un gráfico. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
198
203
Contando los grados de separación. . . . . . . . . . . . . . . . . . . . . . . .
204
Recorrer un gráfico al azar. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
206
CAPÍTULO 11: Obtener la página web adecuada . . . . . . . . . . . . . . . . . . . . . . . . .
Encontrar el mundo en un motor de búsqueda. . . . . . . . . . . . . . . . . . . . . . . . . .
Buscar datos en Internet Considerar . . . . . . . . . . . . . . . . . . . . . . . . . . . .
cómo encontrar los datos correctos Explicar
.....................
el algoritmo PageRank. . . . . . . . . . . . . . . . . . . . . . . . . . .
207
208
208
208
210
Comprender el razonamiento detrás del algoritmo
PageRank. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
210
Explicando los aspectos prácticos del PageRank. . . . . . . . . . . . . . . . . .
212
Implementación de PageRank. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
212
Implementación de un script en Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
216
Luchando con una implementación ingenua. . . . . . . . . . . . . . . . . . . .
Introduciendo el aburrimiento y el teletransporte. . . . . . . . . . . . . . . . . . . . . . 219
Mirando dentro de la vida de un motor de búsqueda. . . . . . . . . . . . . . . . . . . 220
Considerando otros usos del PageRank. . . . . . . . . . . . . . . . . . . . . . .
"Ir más allá del paradigma del PageRank". . . . . . . . . . . . . . . . . . . . . . . .
Introducción de consultas semánticas. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Uso de IA para clasificar los resultados de búsqueda . . . . . . . . . . . . . . . . . . . . . . . . .
PARTE 4: LUCHA CON BIG DATA
CAPÍTULO 12: Gestión de Big Data
Transformar el poder en datos
.....................
....................................
...............................
221
221
222
222
223
225
226
Comprender las implicaciones de Moore. . . . . . . . . . . . . . . . . . . . . . .
226
Encontrar datos en todas partes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
..........................
Incorporar algoritmos a los negocios
231
228
Flujos de datos en streaming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
232
Analizar flujos con la receta adecuada. . . . . . . . . . . . . . . . . . . . .
Reservar los datos correctos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
234
Tabla de contenidos ix
235
Machine Translated by Google
.......................
239
.........................
239
...........................
242
Dibujar una respuesta a partir de datos de flujo
Filtrar elementos de flujo de memoria
Demostrando el filtro Bloom
Encontrar el número de elementos distintos.
....................
245
Aprender a contar objetos en una secuencia
......................
247
CAPÍTULO 13: Operaciones de Paralelización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Gestión de inmensas cantidades de datos
.........................
249
250
Comprensión del paradigma paralelo. . . . . . . . . . . . . . . . . . . . . .
250
Distribución de archivos y operaciones. . . . . . . . . . . . . . . . . . . . . . . . . . .
........................
Empleando la solución MapReduce
254
252
Elaboración de algoritmos para MapReduce. . . . . . . . . . . . . . . . . . . . . . .
........................
Configurar una simulación de MapReduce
258
Consultar mediante mapeo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
261
CAPÍTULO 14: Compresión de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Hacer que los datos sean más pequeños .
.......................................
259
265
266
Comprender la codificación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
266
Considerando los efectos de la compresión. . . . . . . . . . . . . . . . . . . .
267
Elegir un tipo particular de compresión. . . . . . . . . . . . . . . . . .
269
Elegir sabiamente su codificación. . . . . . . . . . . . . . . . . . . . . . . . . . . .
271
Codificación mediante compresión Huffman. . . . . . . . . . . . . . . . . . . . .
........................
Recordar secuencias con LZW
273
PARTE 5: ENFRENTAR PROBLEMAS DIFÍCILES
...........
CAPÍTULO 15: Trabajar con algoritmos codiciosos . . . . . . . . . . . . . . . . . . .
Decidir cuándo es mejor ser codicioso. . . . . . . . . . . . . . . . . . . . . . .
Comprender por qué la avaricia es buena. . . . . . . . . . . . . . . . . . . . . . . .
...................
Mantener bajo control los algoritmos codiciosos
275
281
283
284
285
286
Considerando problemas completos de NP. . . . . . . . . . . . . . . . . . . . . . . .
Descubrir lo útil que puede ser Greedy Organizar . . . . . . . . . . . . . . . . . . . . . . .
288
datos informáticos almacenados en caché . . . . . . . . . . . . . . . . . . . . . . . . . .
Competir por recursos Revisar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
290
la codificación de Huffman. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
294
CAPÍTULO 16: Confiar en la programación dinámica . . . . . . . . . . . . . . . . .
290
291
299
300
Explicando la programación dinámica. . . . . . . . . . . . . . . . . . . . . . . . . . . .
Obtención de una base histórica. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
300
Dinamización de los problemas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
301
Lanzamiento de recursividad dinámicamente. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302
305
Aprovechando la memorización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Descubriendo las mejores recetas dinámicas. . . . . . . . . . . . . . . . . . . . . . . .
x Algoritmos para principiantes
308
Machine Translated by Google
Mirando dentro de la mochila. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
....................................
308
Recorriendo ciudades
312
..............................
Aproximación a la búsqueda de cadenas
317
321
CAPÍTULO 17: Uso de algoritmos aleatorios . . . . . . . . . . . . . . . . . . . . . .
..........................
Definir cómo funciona la aleatorización
..................
Considerar por qué es necesaria la aleatorización
......................
Comprender cómo funciona la probabilidad
..............................
Comprender las distribuciones
..............
Simular el uso del método Monte Carlo Introducir la
322
322
323
325
328
330
aleatoriedad en su lógica. . . . . . . . . . . . . . . . . . . . . . . . . .
330
Calcular una mediana usando Quickselect. . . . . . . . . . . . . . . . . . . . .
Hacer simulaciones usando Monte Carlo Realizar . . . . . . . . . . . . . . . . . . . . . .
333
pedidos más rápido con Quicksort. . . . . . . . . . . . . . . . . . . . . . . . . . . .
336
CAPÍTULO 18: Realizar una búsqueda local
.............................
339
.................................
340
Conociendo el barrio. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Presentando trucos de búsqueda local . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
340
Comprender la búsqueda local
342
"Explicando la escalada con n­reinas". . . . . . . . . . . . . . . . . . . . . .
343
Descubriendo el recocido simulado. . . . . . . . . . . . . . . . . . . . . . . . . .
346
Evitar repeticiones usando la búsqueda tabú...................347
........................
Resolver la satisfacibilidad de circuitos booleanos 348
Resolver 2­SAT usando aleatorización................349
............................
Implementando el código Python 350
Darse cuenta de que el punto de partida es importante. . . . . . . . . . . . . . . .
354
357
CAPÍTULO 19: Empleo de programación lineal . . . . . . . . . . . . . . . . . . . .
358
Usar funciones lineales como herramienta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.........................
Comprender las matemáticas básicas que
359
necesita Aprender a simplificar al planificar. . . . . . . . . . . . . . . . . . . . . . . .
361
Trabajar con geometría usando simplex. . . . . . . . . . . . . . . . . . . . . .
Comprender las limitaciones Uso de la . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
363
361
programación lineal en la práctica Configuración . . . . . . . . . . . . . . . . . . . . . . . . .
de PuLP en casa Optimización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
364
.......................
365
de la producción y los ingresos
CAPÍTULO 20: Considerando la heurística . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Heurística diferenciadora
....................................
Considerando los objetivos de la heurística . . . . . . . . . . . . . . . . . . . . . . . . .
Pasando de la genética a la IA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Enrutamiento de robots mediante heurística . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
...........................
Exploración en territorios desconocidos
Usar medidas de distancia como heurísticas
.....................
Tabla de contenidos xi
364
371
372
372
373
374
374
376
Machine Translated by Google
Explicación de los algoritmos de búsqueda de rutas. . . . . . . . . . . . . . . . . . . . . . . . . . .
Creando un laberinto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
380
..........................
384
...................................
389
vueltas heurísticamente por A*
....
CAPÍTULO 21: Diez algoritmos que están cambiando el mundo
Uso de rutinas de
.........................................
clasificación Búsqueda de cosas con rutinas de
búsqueda Cambio de cosas con números aleatorios
.......................
.....................
Realización de compresión de datos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
........................................
Mantener los datos en
..................................
secreto Cambiar el dominio de los
.............................................
datos Analizar
vínculos Detectar patrones
......................................
de datos Tratar con la automatización y las respuestas automáticas. . . . . . . . . . . . .
..................................
Crear identificadores únicos
CAPÍTULO 22: Diez problemas algorítmicos aún por resolver relacionados con
búsquedas de texto 400
Diferenciar palabras 400
377
.........................
Buscando una ruta rápida y mejor Dando
PARTE 6: LA PARTE DE LAS DIEZ
377
............
391
392
393
393
394
394
395
395
396
397
397
399
..................................
.......................................
Determinar si una solicitud finalizará 401
..................
Creación y uso de funciones unidireccionales....................401
Multiplicar números realmente grandes 402 . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Dividir un recurso en partes iguales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402
......................
Reducir el tiempo de cálculo de la distancia de edición.403
Resolver problemas rápidamente. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403
Jugar el juego de la paridad 404. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
................................
Comprender las cuestiones espaciales 404
ÍNDICE
xii Algoritmos para principiantes
.............................................................
405
Machine Translated by Google
Introducción
lo que has probado sobre el tema termina siendo más bien realmente bueno
Necesitaayudas
aprender
sobre
algoritmos
para
escuela
o el trabajo.
embargo,
todos
los libros
para
inducir
el sueño
enlalugar
de textos
para Sin
enseñarle
algo.
Asumiendo
que puedes superar los símbolos arcanos obviamente escritos por un niño demente de
dos años con predilección por los garabatos, terminas sin tener idea de por qué querrías
saber algo sobre ellos. ¡La mayoría de los textos de matemáticas son aburridos! Sin
embargo, Algoritmos para principiantes es diferente. Lo primero que notará es que este
libro definitivamente carece de símbolos extraños (especialmente los garabatos) flotando.
Sí, ves algunos (después de todo, es un libro de matemáticas), pero lo que encuentras en
cambio son instrucciones claras para usar algoritmos que en realidad tienen nombres y un
historial detrás para realizar tareas útiles. Encontrarás técnicas de codificación simples que
realizan cosas asombrosas que intrigarán a tus amigos y ciertamente los pondrán celosos
mientras realizas asombrosas hazañas matemáticas que ellos ni siquiera pueden comenzar a comprender.
Obtendrás todo esto sin tener que forzar tu cerebro, ni siquiera un poco, y ni siquiera te
quedarás dormido (bueno, a menos que realmente quieras hacerlo).
Sobre este libro
Algoritmos para principiantes es el libro de matemáticas que querías en la universidad pero que no obtuviste.
Descubres, por ejemplo, que los algoritmos no son nuevos. Después de todo, los babilonios
utilizaban algoritmos para realizar tareas sencillas ya en el año 1.600 a.C. Si los babilonios
pudieron resolver estas cosas, ¡ciertamente tú también puedes! En realidad, este libro tiene tres
cosas que no encontrarás en la mayoría de los libros de matemáticas:
» Algoritmos que tienen nombres reales y una base histórica para que puedas
recordar el algoritmo y saber por qué alguien se tomó el tiempo para crearlo.
» Explicaciones sencillas de cómo el algoritmo realiza asombrosas hazañas de
manipulación y análisis de datos o predicción de probabilidades.
» Código que muestra cómo usar el algoritmo sin tener que lidiar con símbolos
arcanos que nadie sin un título en matemáticas puede entender
Parte del énfasis de este libro está en el uso de las herramientas adecuadas. Este libro utiliza
Python para realizar diversas tareas. Python tiene características especiales que hacen que trabajar con
Introducción
1
Machine Translated by Google
los algoritmos son mucho más fáciles. Por ejemplo, Python proporciona acceso a una enorme
variedad de paquetes que le permiten hacer casi cualquier cosa que pueda imaginar, y más de unos
pocos que no puede. Sin embargo, a diferencia de muchos textos que usan Python, este no te
entierra en paquetes. Usamos un grupo selecto de paquetes que brindan gran flexibilidad con mucha
funcionalidad, pero no requieren que usted pague nada. Puede leer este libro completo sin
desembolsar ni un centavo del dinero que tanto le costó ganar.
También descubrirás algunas técnicas interesantes en este libro. Lo más importante es que no solo
vea los algoritmos utilizados para realizar las tareas; También obtienes una explicación de cómo
funcionan los algoritmos. A diferencia de muchos otros libros, Algoritmos para principiantes te permite
comprender completamente lo que estás haciendo, pero sin necesidad de tener un doctorado en
matemáticas. Cada uno de los ejemplos muestra el resultado esperado y le dice por qué ese
resultado es importante. No te quedas con la sensación de que falta algo.
Por supuesto, es posible que todavía estés preocupado por todo el tema del entorno de programación,
y este libro tampoco te deja en la oscuridad. Al principio, encontrará instrucciones de instalación
completas para Anaconda, que es el entorno de desarrollo integrado (IDE) del lenguaje Python
utilizado para este libro. Además, los manuales rápidos (con referencias) le ayudarán a comprender
la programación básica de Python que necesita realizar. El énfasis está en ponerlo en
funcionamiento lo más rápido posible y en hacer que los ejemplos sean directos y simples para que
el código no se convierta en un obstáculo para el aprendizaje.
Para ayudarle a absorber los conceptos, este libro utiliza las siguientes convenciones:
» El texto que debes escribir tal como aparece en el libro está en negrita. La excepción es cuando estás
trabajando en una lista de pasos: debido a que cada paso está en negrita, el texto a escribir no está
en negrita.
» Las palabras que queremos que escriba y que también están en cursiva se utilizan como marcadores
de posición, lo que significa que debe reemplazarlas con algo que funcione para usted. Por ejemplo,
si ve "Escriba su nombre y presione Entrar", deberá reemplazar Su nombre con su nombre real.
» También utilizamos cursiva para los términos que definimos. Esto significa que no tienes que confiar
Consulte otras fuentes para proporcionarle las definiciones que necesita.
» Las direcciones web y el código de programación aparecen en monofont. Si está leyendo
una versión digital de este libro en un dispositivo conectado a Internet, puede hacer
clic en el enlace en vivo para visitar ese sitio web, como este: http://www.dummies.com.
» Cuando necesite hacer clic en secuencias de comandos, las verá separadas por una flecha
especial, como esta: Archivo
en Nuevo archivo.
2 algoritmos para tontos
Nuevo archivo, que le indica que haga clic en Archivo y luego
Machine Translated by Google
Suposiciones tontas
Puede que le resulte difícil creer que hayamos asumido algo sobre usted; después de
todo, ¡ni siquiera le conocemos todavía! Aunque la mayoría de las suposiciones son
realmente tontas, hicimos ciertas suposiciones para proporcionar un punto de partida para el libro.
La primera suposición es que está familiarizado con la plataforma que desea utilizar,
porque el libro no proporciona ninguna orientación al respecto. (Sin embargo, el Capítulo 3
le indica cómo instalar Anaconda; el Capítulo 4 proporciona una descripción general
básica del lenguaje Python; y el Capítulo 5 le ayuda a comprender cómo realizar las
manipulaciones de datos esenciales utilizando Python). Para brindarle la máxima
información sobre Python. Con respecto a los algoritmos, este libro no analiza ningún
problema específico de la plataforma. Realmente necesita saber cómo instalar aplicaciones,
usarlas y, en general, trabajar con la plataforma elegida antes de comenzar a trabajar con este libro.
Este libro no es un manual de matemáticas. Sí, ves muchos ejemplos de matemáticas
complejas, pero el énfasis está en ayudarte a usar Python para realizar tareas comunes
usando algoritmos en lugar de aprender teoría matemática. Sin embargo, obtendrá
explicaciones de muchos de los algoritmos utilizados en el libro para que pueda comprender
cómo funcionan. Los capítulos 1 y 2 le guiarán a través de una mejor comprensión de
precisamente lo que necesita saber para utilizar este libro con éxito.
Este libro también asume que usted puede acceder a elementos en Internet. Esparcidas
por todas partes hay numerosas referencias a material en línea que mejorarán su
experiencia de aprendizaje. Sin embargo, estas fuentes agregadas sólo son útiles si
realmente las encuentra y utiliza.
Iconos utilizados en este libro
Al leer este libro, encontrará íconos en los márgenes que indican material de interés (o no,
según sea el caso). Esto es lo que significan los íconos:
Los consejos son buenos porque te ayudan a ahorrar tiempo o realizar alguna tarea sin
mucho trabajo adicional. Los consejos de este libro son técnicas para ahorrar tiempo o
sugerencias de recursos que debe probar para poder obtener el máximo beneficio de
Python o al realizar tareas relacionadas con algoritmos o análisis de datos.
No queremos parecer padres enojados o algún tipo de maníaco, pero debes evitar hacer
cualquier cosa que esté marcada con un ícono de Advertencia. De lo contrario, es posible
que su aplicación no funcione como se esperaba, obtenga respuestas incorrectas de
algoritmos aparentemente a prueba de balas o (en el peor de los casos) pierda datos.
Introducción 3
Machine Translated by Google
Siempre que veas este ícono, piensa en un consejo o técnica avanzada. Es posible que estos
fragmentos de información útil le resulten demasiado aburridos para describirlos con palabras, o
podrían contener la solución que necesita para ejecutar un programa. Omita estos fragmentos de
información cuando lo desee.
Si no obtienes nada más de un capítulo o sección en particular, recuerda el material marcado con
este ícono. Este texto generalmente contiene un proceso esencial o un poco de información que debe
conocer para trabajar con Python o para realizar con éxito tareas relacionadas con algoritmos o
análisis de datos.
Más allá del libro
Este libro no es el final de su experiencia de aprendizaje de algoritmos o Python; en realidad, es solo
el comienzo. Proporcionamos contenido en línea para que este libro sea más flexible y pueda
satisfacer mejor sus necesidades. De esa manera, cuando recibamos correos electrónicos suyos,
podremos responder preguntas y decirle cómo las actualizaciones de Python o sus complementos
asociados afectan el contenido del libro. De hecho, obtienes acceso a todas estas interesantes incorporaciones:
» Hoja de trucos: Recuerdas haber usado notas de cuna en la escuela para obtener mejores
notas en un examen, ¿no? ¿Tú haces? Bueno, una hoja de trucos es algo así. Le
proporciona algunas notas especiales sobre tareas que puede realizar con
Python, Anaconda y algoritmos que no todas las personas conocen. Para encontrar
la hoja de referencia de este libro, visite www.dummies.com y busque la hoja de
referencia de algoritmos para principiantes. Contiene información realmente interesante,
como encontrar los algoritmos que normalmente se necesitan para realizar tareas específicas.
» Actualizaciones: A veces ocurren cambios. Por ejemplo, es posible que no hayamos
visto un cambio próximo cuando miramos nuestra bola de cristal mientras
escribíamos este libro. En el pasado, esta posibilidad simplemente significaba que
el libro quedaba obsoleto y menos útil, pero ahora puede encontrar
actualizaciones del libro en www.dummies.com/go/algorithmsfd.
Además de estas actualizaciones, consulte las publicaciones del blog con respuestas a las
preguntas de los lectores y demostraciones de técnicas útiles relacionadas con libros en http://
blog.johnmuellerbooks.com/.
» Archivos complementarios: ¡ Oye! ¿Quién realmente quiere escribir todo el código del libro
y reconstruir todas esas tramas manualmente? La mayoría de los lectores prefieren
dedicar su tiempo a trabajar con Python, realizar tareas utilizando algoritmos y ver las
cosas interesantes que pueden hacer, en lugar de escribir. Afortunadamente para usted,
los ejemplos utilizados en el libro están disponibles para descargar, por lo que todo lo que
necesita hacer es leer el libro para aprender técnicas de uso de algoritmos. Puede
encontrar estos archivos en www.dummies.com/go/algorithmsfd.
4 algoritmos para principiantes
Machine Translated by Google
A dónde ir desde aquí
¡Es hora de comenzar tu aventura de aprendizaje de algoritmos! Si es completamente
nuevo en algoritmos, debe comenzar con el Capítulo 1 y avanzar en el libro a un ritmo que
le permita absorber la mayor cantidad de material posible. Asegúrese de leer sobre Python
porque el libro utiliza este lenguaje según sea necesario para los ejemplos.
Si es un novato y tiene absoluta prisa por ponerse manos a la obra con los algoritmos lo
más rápido posible, puede pasar al Capítulo 3 sabiendo que es posible que algunos temas
le resulten un poco confusos más adelante. Si ya tiene instalado Anaconda, puede leer el
Capítulo 3. Para utilizar este libro, debe instalar Python versión 3.4. Los ejemplos no
funcionarán con la versión 2.x de Python porque esta versión no admite algunos de los
paquetes que utilizamos.
Los lectores que tengan cierta exposición a Python y tengan instaladas las versiones de
lenguaje apropiadas, pueden ahorrar tiempo de lectura yendo directamente al Capítulo 6.
Siempre pueden volver a capítulos anteriores según sea necesario cuando tengan
preguntas. Sin embargo, es necesario comprender cómo funciona cada técnica antes de
pasar a la siguiente. Cada técnica, ejemplo de codificación y procedimiento tiene lecciones
importantes para usted, y podría perderse contenido vital si comienza a omitir demasiada información.
Introducción 5
Machine Translated by Google
Machine Translated by Google
1 Primeros pasos
Machine Translated by Google
EN ESTA PARTE . . .
Descubra cómo puede utilizar algoritmos para realizar
tareas prácticas.
Comprender cómo se combinan los algoritmos.
Instale y configure Python para trabajar con algoritmos.
Utilice Python para trabajar con algoritmos.
Realice manipulaciones de algoritmos básicos utilizando Python.
Machine Translated by Google
EN ESTE CAPÍTULO
» Definición de lo que se entiende por algoritmo
» Depender de las computadoras para usar
algoritmos para proporcionar soluciones
» Determinar en qué se diferencian los problemas de
las soluciones
» Realizar manipulación de datos para que pueda
encontrar una solución
Capítulo 1
Introduciendo algoritmos
Si eres
parte de tu
la mayoría
las algoritmos
personas, probablemente
te sientas
confundido
al abrirteeste
libro.
y comienza
aventuradecon
porque la mayoría
de los
textos nunca
lo dicen
qué es un algoritmo, y mucho menos por qué querrías utilizar uno. La mayoría de los
textos asumen que ya sabes algo sobre algoritmos y que estás leyendo sobre ellos para
perfeccionar y mejorar tus conocimientos. Curiosamente, algunos libros proporcionan una
definición confusa de algoritmo que, después de todo, en realidad no lo define y, a veces,
incluso lo equipara con alguna otra forma de expresión abstracta, numérica o simbólica.
La primera sección de este capítulo está dedicada a ayudarle a comprender con precisión
qué significa el término algoritmo y por qué le beneficia saber cómo utilizarlos. Lejos de ser
arcanos, los algoritmos en realidad se utilizan en todas partes, y probablemente usted los
haya utilizado o haya recibido ayuda durante años sin saberlo realmente. En verdad, los
algoritmos se están convirtiendo en la columna vertebral que sustenta y regula lo importante
en una sociedad cada vez más compleja y tecnológica como la nuestra.
Este capítulo también analiza cómo se usan las computadoras para crear soluciones a
problemas usando algoritmos, cómo distinguir entre problemas y soluciones, y qué se
necesita hacer para manipular datos para descubrir una solución. El objetivo de este
capítulo es ayudarle a diferenciar entre algoritmos y otras tareas que la gente realiza y
que confunden con algoritmos. En resumen, descubre por qué realmente desea saber
acerca de los algoritmos y cómo aplicarlos a los datos.
CAPÍTULO 1 Introducción a los algoritmos 9
Machine Translated by Google
Describiendo algoritmos
Aunque la gente ha resuelto algoritmos manualmente durante literalmente miles de años, hacerlo puede
consumir enormes cantidades de tiempo y requerir muchos cálculos numéricos, dependiendo de la
complejidad del problema que se desea resolver.
Los algoritmos tienen como objetivo encontrar soluciones y cuanto más rápido y fácil, mejor.
Existe una enorme brecha entre los algoritmos matemáticos creados históricamente por genios de su
época, como Euclides, Newton o Gauss, y los algoritmos modernos creados en universidades y
laboratorios privados de investigación y desarrollo.
La principal razón de esta brecha es el uso de computadoras. El uso de computadoras para resolver
problemas empleando el algoritmo apropiado acelera significativamente la tarea, razón por la cual el
desarrollo de nuevos algoritmos ha progresado tan rápidamente desde la aparición de potentes
sistemas informáticos. De hecho, es posible que haya notado que hoy en día aparecen cada vez más
soluciones a los problemas, en parte porque la potencia de las computadoras es barata y aumenta
constantemente. Dada su capacidad para resolver problemas utilizando algoritmos, las computadoras
(a veces en forma de hardware especial) se están volviendo omnipresentes.
Cuando se trabaja con algoritmos, se consideran las entradas, las salidas deseadas y el proceso (una
secuencia de acciones) utilizado para obtener el resultado deseado a partir de una entrada determinada.
Sin embargo, puedes equivocarte con la terminología y ver los algoritmos de manera incorrecta porque
no has considerado realmente cómo funcionan en un entorno del mundo real. La tercera sección del
capítulo analiza los algoritmos desde el punto de vista del mundo real, es decir, analizando las
terminologías utilizadas para comprender los algoritmos y presentarlos de una manera que muestre que
el mundo real a menudo no es perfecto. Comprender cómo describir un algoritmo de manera realista
también permite moderar las expectativas para reflejar las realidades de lo que un algoritmo realmente
puede hacer.
Este libro ve los algoritmos de muchas maneras. Sin embargo, debido a que proporciona una visión
general de cómo los algoritmos están cambiando y enriqueciendo la vida de las personas, la atención
se centra en los algoritmos utilizados para manipular datos con una computadora que proporciona el
procesamiento requerido. Teniendo esto en cuenta, los algoritmos con los que trabajará en este libro
requieren la entrada de datos en una forma específica, lo que a veces significa cambiar los datos para
que coincidan con los requisitos del algoritmo. La manipulación de datos no cambia el contenido de los
datos. Lo que hace es cambiar la presentación y la forma de los datos para que un algoritmo pueda
ayudarle a ver nuevos patrones que no eran evidentes antes (pero que en realidad estuvieron presentes
en los datos todo el tiempo).
Las fuentes de información sobre algoritmos a menudo los presentan de una manera que resulta confusa
porque son demasiado sofisticadas o completamente incorrectas. Aunque es posible que encuentre
otras definiciones, este libro utiliza las siguientes definiciones para términos que la gente suele confundir
con algoritmos (pero que no lo son):
» Ecuación: Números y símbolos que, tomados en su conjunto, equivalen a un
valor específico. Una ecuación siempre contiene un signo igual para que sepas
10 PARTE 1 Primeros pasos
Machine Translated by Google
que los números y símbolos representan el valor específico al otro lado del signo igual.
Las ecuaciones generalmente contienen información variable presentada como un
símbolo, pero no es necesario que utilicen variables.
» Fórmula: Combinación de números y símbolos utilizados para expresar información o
ideas. Las fórmulas normalmente presentan conceptos matemáticos o lógicos, como
la definición del máximo común divisor (MCD) de dos números enteros (el vídeo
en https://www.khanacademy.org/math/in­sixth­grade­math/
números­de­juego/factor­común­más­alto/v/mayor­divisor­común
explica cómo funciona esto). Generalmente, muestran la relación entre dos o más
variables. La mayoría de la gente ve una fórmula como un tipo especial de ecuación.
» Algoritmo: Secuencia de pasos utilizados para resolver un problema. La secuencia
presenta un método único para abordar un problema proporcionando una solución
particular. Un algoritmo no necesita representar conceptos matemáticos o lógicos,
aunque las presentaciones de este libro a menudo caen en esa categoría porque la
gente suele utilizar los algoritmos de esta manera. Algunas fórmulas especiales también
son algoritmos, como la fórmula cuadrática. Para que un proceso represente un
algoritmo, debe ser
• Finito: El algoritmo debe eventualmente resolver el problema. Este libro analiza
problemas con una solución conocida para que pueda evaluar si un algoritmo
resuelve el problema correctamente.
• Bien definido: La serie de pasos debe ser precisa y presentar pasos que sean
comprensibles. Especialmente porque las computadoras están involucradas
en el uso de algoritmos, la computadora debe poder comprender los pasos
para crear un algoritmo utilizable.
• Eficaz: Un algoritmo debe resolver todos los casos del problema para el cual
alguien lo definió. Un algoritmo siempre debe resolver el problema que tiene que
resolver. Aunque se deben anticipar algunas fallas, la incidencia de fallas es rara y
ocurre solo en situaciones que son aceptables para el uso previsto del algoritmo.
Con estas definiciones en mente, las siguientes secciones ayudan a aclarar la naturaleza
precisa de los algoritmos. El objetivo no es proporcionar una definición precisa de algoritmos,
sino más bien ayudarle a comprender cómo encajan los algoritmos en el gran esquema de
las cosas para que pueda desarrollar su propia comprensión de qué son los algoritmos y por
qué son tan importantes.
Definición de usos de algoritmos
Un algoritmo siempre presenta una serie de pasos y no necesariamente realiza estos pasos
para resolver una fórmula matemática. El alcance de los algoritmos es increíblemente grande.
Puede encontrar algoritmos que resuelven problemas en ciencia, medicina, finanzas,
producción y suministro industrial y comunicación. Los algoritmos brindan soporte para
CAPÍTULO 1 Introducción a los algoritmos 11
Machine Translated by Google
todos los aspectos de la vida diaria de una persona. Cada vez que una secuencia de acciones
para lograr algo en nuestra vida es finita, bien definida y efectiva, puedes verla como un
algoritmo. Por ejemplo, puedes convertir incluso algo tan trivial y simple como hacer una
tostada en un algoritmo. De hecho, el procedimiento para hacer tostadas aparece a menudo
en las clases de informática, como se explica en http://brianaspinall.com/
ahora­así­cómo­se­hacen­tostadas­usando­algoritmos­computadores/.
Desafortunadamente, el algoritmo del sitio es defectuoso. El instructor nunca saca la tostada
del envoltorio y nunca enchufa la tostadora, por lo que el resultado es pan simple dañado
todavía en su envoltorio metido en una tostadora que no funciona (consulte la discusión en
http://blog.johnmuellerbooks.com/2013/03 /04/procedimientos­en­ redacción­técnica/ para
detalles). Aun así, la idea es la correcta, aunque requiere algunos ajustes leves, pero
esenciales, para que el algoritmo sea finito y eficaz.
Uno de los usos más comunes de los algoritmos es como medio para resolver fórmulas. Por
ejemplo, cuando trabaja con el MCD de dos valores enteros, puede realizar la tarea
manualmente enumerando cada uno de los factores para los dos números enteros y luego
seleccionando el mayor factor que sea común a ambos. Por ejemplo, MCD(20, 25) es 5
porque 5 es el número más grande que divide a 20 y 25. Sin embargo, procesar cada MCD
manualmente (que en realidad es una especie de algoritmo) requiere mucho tiempo y es
propenso a errores, por lo que el griego matemático Euclides (https://en.wikipedia.org/
wiki/Euclides) Creó un algoritmo para realizar la tarea. Puede ver la demostración del método
euclidiano en https://www.khanacademy.org/computing/computer­science/cryptography/
modarithmetic/a/the­euclidean­algorithm .
Sin embargo, una única fórmula, que es una presentación de símbolos y números utilizados
para expresar información o ideas, puede tener múltiples soluciones, cada una de las cuales
es un algoritmo. En el caso de GCD, otro algoritmo común es uno creado por Lehmer (ver
https://www.imsc.res.in/~kapil/crypto/notes/node11.html
y https://en.wikipedia.org/wiki/Lehmer%27s_GCD_algorithm para detalles).
Como cualquier fórmula se puede resolver de varias maneras, la gente suele dedicar mucho
tiempo a comparar algoritmos para determinar cuál funciona mejor en una situación
determinada. (Vea una comparación de Euclides con Lehmer en http://citeseerx.ist.psu.
edu/viewdoc/download?doi=10.1.1.31.693&rep=rep1&type=pdf.)
Debido a que nuestra sociedad y la tecnología que la acompaña están ganando impulso y
funcionando cada vez más rápido, necesitamos algoritmos que puedan mantener el ritmo.
Logros científicos como la secuenciación del genoma humano fueron posibles en nuestra
época porque los científicos encontraron algoritmos que se ejecutan lo suficientemente rápido
como para completar la tarea. Medir qué algoritmo es mejor en una situación determinada, o
en una situación de uso promedio, es algo realmente serio y un tema de discusión entre los informáticos.
Cuando se trata de informática, el mismo algoritmo puede ver múltiples presentaciones. Por
ejemplo, puede presentar el algoritmo euclidiano de forma recursiva e iterativa, como se explica
en http://cs.stackexchange.com/questions/
1447/qué­es­más­eficiente­para­gcd. En resumen, los algoritmos presentan un método
12 PARTE 1 Primeros pasos
Machine Translated by Google
de resolver fórmulas, pero sería un error decir que sólo existe un algoritmo aceptable
para cualquier fórmula dada o que sólo hay una presentación aceptable de un
algoritmo. El uso de algoritmos para resolver problemas de diversos tipos tiene una
larga historia; no es algo que acaba de suceder.
Incluso si limitas tu mirada a la informática, la ciencia de datos, la inteligencia artificial
y otras áreas técnicas, encontrarás muchos tipos de algoritmos, demasiados para un
solo libro. Por ejemplo, The Art of Computer Programming, de Donald E. Knuth
(Addison­Wesley), abarca 3.168 páginas en cuatro volúmenes (ver http://www.
amazon.com/exec/obidos/ASIN/0321751043/datacservip0f­20/) y todavía no logra
cubrir el tema (el autor tenía la intención de escribir más volúmenes).
Sin embargo, aquí te presentamos algunos usos interesantes que debes considerar:
» Búsqueda: Localizar información o verificar que la información que ves es la información
que deseas es una tarea esencial. Sin esta capacidad, no sería posible realizar muchas
tareas en línea, como encontrar el sitio web en Internet que venda la cafetera perfecta
para su oficina.
» Ordenar: Determinar qué orden utilizar para presentar la información es importante porque
hoy en día la mayoría de las personas sufren de sobrecarga de información, y poner
la información en orden es una forma de reducir la avalancha de datos.
Probablemente aprendiste cuando eras niño que cuando colocas tus juguetes en
orden, es más fácil encontrar y jugar con un juguete que te interese, en comparación
con tener juguetes esparcidos al azar por todas partes. Imagínese ir a Amazon y descubrir
que hay más de mil cafeteras a la venta allí y, sin embargo, no poder ordenarlas por
precio o reseña más positiva. Además, muchos algoritmos complejos requieren datos
en el orden adecuado para funcionar de manera confiable, por lo que ordenarlos
es un requisito importante para resolver más problemas.
» Transformación: convertir un tipo de datos en otro tipo de datos es fundamental para
comprender y utilizar los datos de manera eficaz. Por ejemplo, es posible que
entiendas bien los pesos imperiales, pero todas tus fuentes utilizan el sistema
métrico. La conversión entre los dos sistemas le ayuda a comprender los datos.
Del mismo modo, la Transformada Rápida de Fourier (FFT) convierte señales entre el
dominio del tiempo y el dominio de la frecuencia para que sea posible hacer que cosas
como su enrutador Wi­Fi funcionen.
» Programación: hacer que el uso de los recursos sea justo para todos los interesados es otra
forma en que los algoritmos dan a conocer su presencia a lo grande. Por ejemplo,
cronometrar las luces en las intersecciones ya no son simples dispositivos que cuentan los
segundos entre cambios de luz. Los dispositivos modernos consideran todo tipo de
cuestiones, como la hora del día, las condiciones climáticas y el flujo del tráfico. Sin
embargo, la programación se presenta de muchas formas. Por ejemplo, considere
cómo su computadora ejecuta múltiples tareas al mismo tiempo. Sin un algoritmo de
programación, el sistema operativo podría apoderarse de todos los recursos
disponibles e impedir que su aplicación realice algún trabajo útil.
CAPÍTULO 1 Introducción a los algoritmos 13
Machine Translated by Google
» Análisis de gráficos: decidir cuál es la línea más corta entre dos puntos encuentra
todo tipo de usos. Por ejemplo, en un problema de ruta, su GPS no podría funcionar
sin este algoritmo particular porque nunca podría dirigirlo por las calles de la ciudad
usando la ruta más corta desde el punto A al punto B.
» Criptografía: Mantener los datos seguros es una batalla constante en la que los piratas
informáticos atacan constantemente las fuentes de datos. Los algoritmos permiten
analizar datos, darles otra forma y luego devolverlos a su forma original.
» Generación de números pseudoaleatorios: imagina jugar juegos que nunca varían.
Empiezas en el mismo lugar; Realice los mismos pasos, de la misma manera,
cada vez que juegue. Sin la capacidad de generar números aparentemente
aleatorios, muchas tareas informáticas se vuelven imposibles.
Esta lista presenta una descripción increíblemente breve. La gente usa algoritmos para
muchas tareas diferentes y de muchas maneras diferentes, y constantemente crea nuevos
algoritmos para resolver tanto problemas existentes como nuevos. La cuestión más importante
a considerar cuando se trabaja con algoritmos es que, dada una entrada particular, se debe
esperar una salida específica. Las cuestiones secundarias incluyen cuántos recursos requiere
el algoritmo para realizar su tarea y cuánto tiempo lleva completarla.
Dependiendo del tipo de problema y del tipo de algoritmo utilizado, es posible que también
deba considerar cuestiones de precisión y coherencia.
Encontrar algoritmos en todas partes
La sección anterior menciona el algoritmo brindis por una razón específica. Por alguna razón,
hacer tostadas es probablemente el algoritmo más popular jamás creado. Muchos niños de
primaria escriben su equivalente del algoritmo de la tostada mucho antes de que puedan
resolver las matemáticas más básicas. No es difícil imaginar cuántas variaciones del algoritmo
brindis existen y cuál es el resultado preciso de cada una de ellas. Es probable que los
resultados varíen según el individuo y el nivel de creatividad empleado. En resumen, los
algoritmos aparecen en una gran variedad y, a menudo, en lugares inesperados.
Cada tarea que realiza en una computadora implica algoritmos. Algunos algoritmos aparecen
como parte del hardware de la computadora. (Están integrados, por eso se habla de
microprocesadores integrados). El mismo acto de arrancar una computadora implica el uso
de un algoritmo. También encontrará algoritmos en sistemas operativos, aplicaciones y
cualquier otro software. Incluso los usuarios confían en algoritmos. Los scripts ayudan a dirigir
a los usuarios a realizar tareas de una manera específica, pero esos mismos pasos pueden
aparecer como instrucciones escritas o como parte de una declaración de política organizacional.
Las rutinas diarias a menudo se convierten en algoritmos. Piensa en cómo pasas el día. Si
eres como la mayoría de las personas, realizas esencialmente las mismas tareas todos los
días en el mismo orden, lo que convierte tu día en un algoritmo que resuelve el problema de
cómo vivir con éxito gastando la menor cantidad de energía posible. Después de todo, eso es
lo que hace una rutina; nos hace eficientes.
14 PARTE 1 Primeros pasos
Machine Translated by Google
Los procedimientos de emergencia suelen depender de algoritmos. Sacas la tarjeta de emergencia del
paquete que tienes delante en el avión. En él hay una serie de pictogramas que muestran cómo abrir la
puerta de emergencia y extender el tobogán. En algunos casos, es posible que ni siquiera veas las
palabras, pero las imágenes transmiten el procedimiento necesario para realizar la tarea y resolver el
problema de salir rápidamente del avión. A lo largo de este libro, verá los mismos tres elementos para
cada algoritmo:
1. Describe el problema.
2. Cree una serie de pasos para resolver el problema (bien definidos).
3. Realice los pasos para obtener el resultado deseado (finito y efectivo).
Usar computadoras para resolver problemas
El término computadora suena bastante técnico y posiblemente un poco abrumador para algunas
personas, pero hoy en día la gente está metida hasta el cuello (posiblemente incluso más) en las computadoras.
Usas al menos una computadora, tu teléfono inteligente, la mayor parte del tiempo. Si tienes algún tipo
de dispositivo especial, como un marcapasos, también incluye un ordenador. Su televisor inteligente
contiene al menos una computadora, al igual que su electrodoméstico inteligente. Un automóvil puede
contener hasta 30 computadoras en forma de microprocesadores integrados que regulan el consumo de
combustible, la combustión del motor, la transmisión, la dirección y la estabilidad (según un artículo del
New York Times en http://www.nytimes.com/
2010/02/05/tecnología/05electronics.html) y más líneas de código que un avión de
combate. Los automóviles automatizados que aparecerán en el mercado requerirán aún
más microprocesadores integrados y algoritmos de mayor complejidad. Una computadora
existe para resolver problemas rápidamente y con menos esfuerzo que resolverlos manualmente.
En consecuencia, no debería sorprenderle que este libro utilice aún más computadoras para ayudarle a
comprender mejor los algoritmos.
Las computadoras varían de varias maneras. La computadora de tu reloj es bastante pequeña; el de tu
escritorio es bastante grande. Las supercomputadoras son inmensas y contienen muchas computadoras
más pequeñas, todas ellas encargadas de trabajar juntas para resolver problemas complejos, como el
clima de mañana. Los algoritmos más complejos se basan en una funcionalidad informática especial
para obtener soluciones a los problemas para los que las personas los diseñan. Sí, podría utilizar menos
recursos para realizar la tarea, pero la contrapartida es esperar mucho más tiempo para obtener una
respuesta o recibir una respuesta que carece de la precisión suficiente para proporcionar una solución
útil. En algunos casos, se espera tanto que la respuesta ya no es importante. Teniendo en cuenta la
necesidad de velocidad y precisión, las siguientes secciones analizan algunas características especiales
de la computadora que pueden afectar los algoritmos.
CAPÍTULO 1 Introducción a los algoritmos 15
Machine Translated by Google
Aprovechando las CPU y GPU modernas
Los procesadores de uso general, las CPU, comenzaron como un medio para resolver
problemas mediante algoritmos. Sin embargo, su naturaleza de propósito general también
significa que una CPU puede realizar muchas otras tareas, como mover datos o interactuar con
dispositivos externos. Un procesador de propósito general hace muchas cosas bien, lo que
significa que puede realizar los pasos necesarios para completar un algoritmo, pero no
necesariamente rápido. De hecho, los propietarios de los primeros procesadores de uso general
podían agregar coprocesadores matemáticos (chips especiales específicos para matemáticas)
a sus sistemas para obtener una ventaja en velocidad (consulte http://www.computerhope.com/
jargon/m/mathcopr.htm para detalles). Hoy en día, los procesadores de uso general tienen el
coprocesador matemático integrado, por lo que cuando obtienes un procesador Intel i7, en
realidad obtienes varios procesadores en un solo paquete.
Curiosamente, Intel todavía comercializa complementos de procesadores especiales, como el
procesador Xeon Phi utilizado con los chips Xeon (consulte http://www.intel.com/
contenido/www/us/en/processors/xeon/xeon­phi­detail.html y https://
es.wiki2.org/wiki/Intel_Xeon_Phi para detalles). Se utiliza el chip Xeon Phi junto con un chip Xeon
cuando se realizan tareas de computación intensiva, como el aprendizaje automático (consulte
Machine Learning For Dummies, de John Mueller y Luca Massaron, para obtener detalles sobre cómo
el aprendizaje automático utiliza algoritmos para determinar cómo realizar diversas tareas que ayudarle
a utilizar datos para predecir lo desconocido y organizar la información de manera significativa).
Quizás se pregunte por qué esta sección menciona las unidades de procesamiento de gráficos (GPU).
Después de todo, se supone que las GPU toman datos, los manipulan de una manera especial y luego
muestran una imagen bonita en pantalla. Cualquier hardware informático puede servir para más de un
propósito. Resulta que las GPU son particularmente hábiles para realizar transformaciones de datos,
lo cual es una tarea clave para resolver algoritmos en muchos casos. Una GPU es un procesador de
propósito especial, pero con capacidades que se prestan a una ejecución de algoritmos más rápida.
No debería sorprenderle descubrir que las personas que crean algoritmos pasan mucho tiempo
pensando fuera de lo común, lo que significa que a menudo ven métodos para resolver problemas en
enfoques no tradicionales.
La cuestión es que las CPU y las GPU constituyen los chips más utilizados para realizar tareas
relacionadas con algoritmos. El primero realiza bastante bien tareas de propósito general y el segundo
se especializa en brindar soporte para tareas intensivas en matemáticas, especialmente aquellas que
involucran transformaciones de datos. El uso de múltiples núcleos hace posible el procesamiento
paralelo (realizar más de un paso algorítmico a la vez). Agregar varios chips aumenta la cantidad de
núcleos disponibles. Tener más núcleos aumenta la velocidad, pero una serie de factores mantienen
la ganancia de velocidad al mínimo.
El uso de dos chips i7 no producirá el doble de velocidad que un solo chip i7.
16 PARTE 1 Primeros pasos
Machine Translated by Google
Trabajar con chips de propósito especial
Un coprocesador matemático y una GPU son dos ejemplos de chips comunes de propósito
especial que no se utilizan para realizar tareas como arrancar el sistema. Sin embargo, los
algoritmos a menudo requieren el uso de chips especiales poco comunes para resolver
problemas. Este no es un libro sobre hardware, pero dedicar un poco de tiempo a explorar
puede mostrarle todo tipo de chips interesantes, como las nuevas neuronas artificiales en
las que IBM está trabajando (consulte la historia en http://www.computerworld.com/ artículo/3103294/
procesadores­computadores/ibm­creates­artificial­neurons­from­phase­change­ memory­
for­cognitive­computing.html). Imagine realizar un procesamiento algorítmico utilizando
una memoria que simule el cerebro humano. Crearía un entorno interesante para realizar
tareas que de otro modo no serían posibles hoy en día.
Las redes neuronales, una tecnología que se utiliza para simular el pensamiento humano y
hacer posibles técnicas de aprendizaje profundo para escenarios de aprendizaje
automático, ahora se benefician del uso de chips especializados, como el Tesla P100 de
NVidia (ver la historia en https: // /www.technologyreview.com/s/601195/a­2­billion­chip­to­
accelerate­artificial­intelligence/ para detalles). Este tipo de chips no sólo realizan un
procesamiento algorítmico extremadamente rápido, sino que también aprenden a medida
que realizan las tareas, haciéndolas aún más rápidas con cada iteración. Los aprendices
de computadoras eventualmente impulsarán robots que puedan moverse (en cierto modo)
por sí solos, similares a los robots que se ven en la película I Robot (vea uno de esos robots descrito en http
www.cbsnews.com/news/this­creepy­robot­is­powered­by­a­neural­network/ ). También
hay chips especiales que realizan tareas como el reconocimiento visual (consulte https://
www.technologyreview.com/s/537211/a­better­way­to­build­brain­inspired­chips/ para
detalles).
No importa cómo funcionen, los procesadores especializados eventualmente impulsarán
todo tipo de algoritmos que tendrán consecuencias en el mundo real. Ya puedes encontrar
muchas de estas aplicaciones del mundo real en una forma relativamente sencilla. Por
ejemplo, imaginemos las tareas que tendría que resolver un robot que hace pizzas: las
variables que tendría que considerar en tiempo real. Este tipo de robot ya existe (este es
sólo un ejemplo de los muchos robots industriales utilizados para producir bienes materiales
mediante el empleo de algoritmos), y puede apostar que se basa en algoritmos para
describir qué hacer, así como en chips especiales para garantizar que las tareas se realicen
rápidamente (consulte la historia en http://www.bloomberg.com/news/articles/2016­06­24/
dentro­silicon­valley­s­robot­pizzería).
Con el tiempo, incluso podría ser posible utilizar la mente humana como procesador y
generar la información a través de una interfaz especial. Algunas empresas ahora están
experimentando con la instalación de procesadores directamente en el cerebro humano para
mejorar su capacidad de procesar información (consulte la historia en https://www.washingtonpost.
com/news/the­switch/wp/2016/08/15/poner­una­computadora­en­tu­cerebro­ya ­no­ya­no­
es­ciencia­ficción/ para detalles). Imagine un sistema en el que los humanos puedan
resolver algoritmos a la velocidad de las computadoras, pero con el potencial creativo de
"qué pasaría si" de los humanos.
CAPÍTULO 1 Introducción a los algoritmos 17
Machine Translated by Google
Aprovechando las redes
A menos que tenga fondos ilimitados, es posible que no sea posible utilizar algunos algoritmos
de manera efectiva, incluso con chips especializados. En ese caso, pueden conectar
computadoras en red. Usando un software especial, una computadora, una maestra, puede
usar los procesadores de todas las computadoras esclavas que ejecutan un agente (una
especie de aplicación en segundo plano en memoria que hace que el procesador esté
disponible). Con este enfoque, puede resolver problemas increíblemente complejos
descargando partes del problema a varias computadoras esclavas. A medida que cada
computadora en la red resuelve su parte del problema, envía los resultados al maestro, quien
junta las piezas para crear una respuesta consolidada, una técnica llamada computación en clúster.
Para que no pienses que esto es cosa de ciencia ficción, la gente ya está utilizando técnicas
de computación en clústeres de muchas formas interesantes. Por ejemplo, el artículo en http://
www.zdnet.com/article/build­your­own­supercomputer­out­of­raspberry­pi­boards/ detalla
cómo puedes construir tu propia supercomputadora combinando varias Raspberry Pi (https://
www.raspberrypi.org/) tableros en un solo grupo.
También es popular la computación distribuida, otra versión de la computación en clúster (pero
con una organización más flexible). De hecho, puede encontrar una lista de proyectos de
informática distribuida en http://www.distributedcomputing.info/projects.html. La lista de
proyectos incluye algunos esfuerzos importantes, como la Búsqueda de Inteligencia
Extraterrestre (SETI). También puede donar la potencia de procesamiento adicional de su
computadora para trabajar en una cura para el cáncer. La lista de proyectos potenciales es asombrosa.
Las redes también le permiten acceder al poder de procesamiento de otras personas de forma
independiente. Por ejemplo, Amazon Web Services (AWS) y otros proveedores proporcionan
los medios para utilizar sus computadoras para realizar su trabajo. Una conexión de red puede
hacer que las computadoras remotas se sientan como parte de su propia red. El punto es que
puedes usar las redes de muchas maneras para crear conexiones entre computadoras para
resolver una variedad de algoritmos que serían demasiado complicados de resolver usando
solo tu sistema.
Aprovechar los datos disponibles
Parte de la resolución de un algoritmo no tiene nada que ver con la potencia de procesamiento,
el pensamiento creativo innovador ni nada de naturaleza física. Para crear una solución a la
mayoría de los problemas, también se necesitan datos en los que basar una conclusión. Por
ejemplo, en el algoritmo para hacer tostadas, es necesario conocer la disponibilidad de pan,
una tostadora, la electricidad para alimentar la tostadora, etc., antes de poder resolver el
problema de hacer tostadas. Los datos se vuelven importantes porque no se puede finalizar
el algoritmo cuando falta incluso un elemento de la solución requerida. Por supuesto, es posible
que también necesite datos de entrada adicionales. Por ejemplo, la persona
18 PARTE 1 Primeros pasos
Machine Translated by Google
A los que quieran las tostadas quizá no les guste el de centeno. Si este es el caso y todo lo que
tienes es pan de centeno, la presencia de pan aún no dará un resultado exitoso.
Los datos provienen de todo tipo de fuentes y en todo tipo de formas. Puede transmitir datos
desde una fuente como un monitor en tiempo real, acceder a una fuente de datos pública, confiar
en datos privados en una base de datos, extraer datos de sitios web u obtenerlos de muchas
otras formas, demasiado numerosas para mencionarlas aquí. Los datos pueden ser estáticos
(sin cambios) o dinámicos (en constante cambio). Es posible que los datos estén completos o
que falten elementos. Es posible que los datos no aparezcan en la forma correcta (como
cuando obtienes unidades imperiales y necesitas unidades métricas para resolver un problema
de peso). Los datos pueden aparecer en formato tabular cuando los necesite de alguna otra
forma. Puede residir de forma no estructurada (por ejemplo, en una base de datos NoSQL o
simplemente en un montón de archivos de datos diferentes) cuando necesite el formato formal
de una base de datos relacional. En resumen, necesita saber todo tipo de cosas sobre los
datos utilizados con su algoritmo para poder resolver problemas con él.
Debido a que los datos vienen en tantas formas y es necesario trabajar con ellos de muchas
maneras, este libro les presta mucha atención. A partir del Capítulo 6, descubrirá cómo entra en
juego la estructura de datos. Pasando al Capítulo 7, comenzará a ver cómo buscar en los datos
para encontrar lo que necesita. Los capítulos 12 al 14 le ayudarán a trabajar con big data. Sin
embargo, puede encontrar algún tipo de información específica de datos en casi todos los
capítulos del libro porque sin datos, un algoritmo no puede resolver ningún problema.
Distinguir entre problemas y
soluciones
Este libro analiza dos partes de la visión algorítmica del mundo real. Por un lado, tienes
problemas, que son problemas que necesitas resolver. Un problema puede describir el resultado
deseado de un algoritmo o puede describir un obstáculo que debe superar para obtener el
resultado deseado. Las soluciones son los métodos o pasos utilizados para abordar los
problemas. Una solución puede estar relacionada con solo uno o muchos pasos dentro del
algoritmo. De hecho, el resultado de un algoritmo, la respuesta al último paso, es una solución.
Las siguientes secciones le ayudarán a comprender algunos de los aspectos importantes de los
problemas y las soluciones.
Ser correcto y eficiente
Usar algoritmos se trata de obtener una respuesta aceptable. La razón por la que se busca una
respuesta aceptable es que algunos algoritmos generan más de una respuesta en respuesta a
datos de entrada difusos. La vida a menudo hace que sea imposible encontrar respuestas precisas.
CAPÍTULO 1 Introducción a los algoritmos 19
Machine Translated by Google
conseguir. Por supuesto, el objetivo siempre es obtener una respuesta precisa, pero a menudo se termina con una
respuesta aceptable.
Obtener la respuesta más precisa posible puede llevar demasiado tiempo. Cuando obtienes una respuesta precisa
pero esa respuesta llega demasiado tarde para usarla, la información se vuelve inútil y has perdido el tiempo. Elegir
entre dos algoritmos que abordan el mismo problema puede reducirse a una elección entre velocidad y precisión.
Es posible que un algoritmo rápido no genere una respuesta precisa, pero la respuesta aún puede funcionar lo
suficientemente bien como para proporcionar resultados útiles.
Las respuestas incorrectas pueden ser un problema. Crear muchas respuestas incorrectas rápidamente es tan malo
como crear lentamente muchas respuestas correctas y precisas. Parte del objetivo de este libro es ayudarle a encontrar
el punto medio entre demasiado rápido y demasiado lento, y entre impreciso y demasiado preciso. Aunque tu profesor
de matemáticas enfatizó la necesidad de dar la respuesta correcta en la forma expresada en el libro que usaste en ese
momento, las matemáticas del mundo real a menudo implican sopesar opciones y tomar decisiones intermedias que
te afectan de maneras que quizás no creías posibles. .
Descubriendo que no hay almuerzo gratis
Es posible que haya escuchado el mito común de que se puede tener todo lo relacionado con la salida de una
computadora sin tener que esforzarse mucho en encontrar la solución. Desafortunadamente, no existe una solución
absoluta para ningún problema, y mejores respuestas suelen ser bastante costosas. Al trabajar con algoritmos,
descubre rápidamente la necesidad de proporcionar recursos adicionales cuando necesita respuestas precisas
rápidamente. El tamaño y la complejidad de las fuentes de datos que utiliza también afectan en gran medida la
resolución de la solución. A medida que aumentan el tamaño y la complejidad, también aumenta la necesidad de
agregar recursos.
Adaptar la estrategia al problema
La parte 5 de este libro analiza las estrategias que puede utilizar para reducir el costo de trabajar con algoritmos. Los
mejores matemáticos utilizan trucos para obtener más resultados con menos computación. Por ejemplo, puede crear
un algoritmo definitivo para resolver un problema, o puede utilizar una serie de algoritmos más simples para resolver
el mismo problema, pero utilizando varios procesadores. La gran cantidad de algoritmos simples generalmente
funcionarán más rápido y mejor que el algoritmo único y complejo, aunque este enfoque parezca contradictorio.
Describir algoritmos en una lengua franca
Los algoritmos proporcionan una base para la comunicación entre personas, incluso cuando esos individuos tienen
diferentes perspectivas y hablan diferentes idiomas. Por ejemplo, el teorema de Bayes (la probabilidad de que ocurra
un evento dadas ciertas
20 PARTE 1 Primeros pasos
Machine Translated by Google
instalaciones; consulte https://betterexplained.com/articles/an­intuitive­and­short­explanation­of­
bayes­theorem/ para una explicación rápida de este sorprendente teorema)
P(B|E) = P(E|B)*P(B)/P(E)
aparece igual ya sea que hables inglés, español, chino, alemán, francés o cualquier otro idioma.
Independientemente del idioma que hable, el algoritmo tiene el mismo aspecto y actúa de la misma
manera con los mismos datos. Los algoritmos ayudan a cruzar todo tipo de divisiones que sirven
para separar a los humanos entre sí al expresar ideas de una forma que cualquiera pueda probar.
A medida que avanza en este libro, descubre la belleza y la magia que los algoritmos pueden
proporcionar para comunicar incluso pensamientos sutiles a los demás.
Además de las notaciones matemáticas universales, los algoritmos aprovechan los lenguajes de
programación como medio para explicar y comunicar las fórmulas que resuelven. Puede encontrar
todo tipo de algoritmos en C, C++, Java, Fortran, Python (como en este libro) y otros lenguajes.
Algunos escritores confían en el pseudocódigo para superar el hecho de que se puede proponer
un algoritmo en un lenguaje de programación que no conoce. El pseudocódigo es una forma de
describir las operaciones de la computadora mediante el uso de palabras comunes en inglés.
Enfrentando problemas difíciles
Una consideración importante al trabajar con algoritmos es que puede utilizarlos para resolver
problemas de cualquier complejidad. El algoritmo no piensa, no tiene emociones ni le importa
cómo lo usas (ni siquiera abusas de él). Puede utilizar algoritmos de cualquier forma necesaria
para resolver un problema. Por ejemplo, el mismo grupo de algoritmos utilizados para realizar
reconocimiento facial como alternativa a las contraseñas informáticas (por motivos de seguridad)
puede encontrar terroristas acechando en un aeropuerto o reconocer a un niño perdido
deambulando por las calles. Un mismo algoritmo tiene diferentes usos; cómo usarlo depende de
los intereses del usuario. Parte de la razón por la que desea leer este libro detenidamente es para
ayudarle a resolver esos problemas difíciles que pueden requerir sólo un algoritmo simple para abordar.
Estructurar datos para obtener una solución
Los humanos piensan en los datos de maneras no específicas y aplican varias reglas a los mismos
datos para comprenderlos de una manera que las computadoras nunca podrán entender. La visión
de los datos que tiene una computadora es estructurada, simple, intransigente y, definitivamente,
nada creativa. Cuando los humanos preparan datos para que los use una computadora, los
datos a menudo interactúan con los algoritmos de maneras inesperadas y producen resultados no
deseados. El problema es que el ser humano no aprecia la visión limitada de los datos que tiene
una computadora. Las siguientes secciones describen dos aspectos de los datos que verá
ilustrados en muchos de los capítulos siguientes.
CAPÍTULO 1 Introducción a los algoritmos 21
Machine Translated by Google
Comprender el punto de vista de una computadora
Una computadora tiene una visión simple de los datos, pero también es una visión que los
humanos normalmente no comprenden. Por un lado, todo es un número para una computadora
porque las computadoras no están diseñadas para funcionar con ningún otro tipo de datos.
Los humanos ven caracteres en la pantalla de la computadora y suponen que la computadora
interactúa con los datos de esa manera, pero la computadora no comprende los datos ni sus
implicaciones. La letra A es simplemente el número 65 para la computadora. De hecho, ni
siquiera es realmente el número 65. La computadora ve una serie de impulsos eléctricos que
equivalen a un valor binario de 0100 0001.
Las computadoras tampoco entienden todo el concepto de mayúsculas y minúsculas.
Para un humano, la a minúscula es simplemente otra forma de la A mayúscula, pero para una
computadora son dos letras diferentes. Una a minúscula aparece como el número 97 en la
computadora (un valor binario de 0110 0001).
Si estos tipos simples de comparaciones de una sola letra pueden causar tales problemas entre
humanos y computadoras, no es difícil imaginar qué sucede cuando los humanos comienzan a
asumir demasiado sobre otros tipos de datos. Por ejemplo, una computadora no puede oír ni
apreciar la música. Sin embargo, la música sale de los parlantes de la computadora. Lo mismo
ocurre con los gráficos. Una computadora ve una serie de 0 y 1, no un gráfico que contenga
una bonita escena del campo.
Es importante considerar los datos desde la perspectiva de la computadora cuando se utilizan
algoritmos. La computadora sólo ve 0 y 1, nada más. En consecuencia, cuando empiece a
trabajar en las necesidades del algoritmo, debe ver los datos de esa manera. De hecho, puede
que le resulte beneficioso saber que la visión que tiene la computadora de los datos hace que
algunas soluciones sean más fáciles de encontrar, no más difíciles. Descubrirás más sobre esta
rareza al ver los datos a medida que avanza el libro.
Organizar los datos marca la diferencia
Las computadoras también tienen una idea estricta sobre la forma y estructura de los datos.
Cuando comienzas a trabajar con algoritmos, descubres que una gran parte del trabajo implica
hacer que los datos aparezcan en una forma que la computadora pueda usar al usar el algoritmo
para encontrar una solución a un problema. Aunque un ser humano puede ver mentalmente
patrones en datos que no están ordenados correctamente, las computadoras realmente
necesitan precisión para encontrar el mismo patrón. El beneficio de esta precisión es que las
computadoras a menudo pueden hacer visibles nuevos patrones. De hecho, esa es una de las
razones principales para usar algoritmos con computadoras: ayudar a localizar nuevos patrones
y luego usarlos para realizar otras tareas. Por ejemplo, una computadora puede reconocer el
patrón de gasto de un cliente para poder utilizar la información para generar más ventas automáticamente.
22 PARTE 1 Primeros pasos
Machine Translated by Google
EN ESTE CAPÍTULO
» Considerar cómo resolver un problema
» Utilizar un enfoque de divide y vencerás
para resolver problemas
» Comprender el enfoque codicioso
para resolver problemas
» Determinar los costos de las soluciones a
los problemas.
» Realización de mediciones algorítmicas
Capitulo 2
Considerando el algoritmo
Diseño
resolver un problema. En la mayoría de los casos, los datos de entrada proporcionan la base para resolver el problema.
Como se problema
indicó en elyCapítulo
un algoritmo
consta de
una
serie de solución
pasos utilizados
para
a veces1,ofrece
restricciones
que
cualquier
debe considerar
antes de que alguien vea que el algoritmo es efectivo. La primera sección de este capítulo le
ayuda a considerar la solución del problema (la solución al problema que está intentando
resolver). Le ayuda a comprender la necesidad de crear algoritmos que sean flexibles (en el
sentido de que puedan manejar una amplia gama de entradas de datos) y efectivos (en el
sentido de que produzcan el resultado deseado).
Algunos problemas son bastante complejos. De hecho, al principio los miras y puedes decidir
que son demasiado complicados de resolver. Sentirse abrumado por un problema es común.
La forma más común de resolver el problema es dividirlo en partes más pequeñas, cada una
de las cuales sea manejable por sí sola. El enfoque de divide y vencerás para la resolución de
problemas, que se analiza en la segunda sección de este capítulo, originalmente se refería a la
guerra (ver http://classroom.synonym.com/civilization­invented­divide­conquistar­
estrategia­12746.html para una historia de este enfoque). Sin embargo, la gente utiliza las
mismas ideas para reducir los problemas de todo tipo.
CAPÍTULO 2 Consideración del diseño de algoritmos 23
Machine Translated by Google
La tercera sección del capítulo se refiere al enfoque codicioso de la resolución de problemas.
La codicia normalmente tiene una connotación negativa, pero no en este caso. Un algoritmo codicioso
es aquel que hace una elección óptima en cada etapa de resolución de problemas. Al hacerlo, espera
obtener una solución óptima general para resolver el problema. Desafortunadamente, esta estrategia no
siempre funciona, pero siempre vale la pena intentarla. A menudo produce una solución suficientemente
buena , lo que la convierte en una buena base de referencia.
No importa qué enfoque de resolución de problemas elija, cada algoritmo tiene sus costos. Al ser buenos
compradores, las personas que dependen en gran medida de los algoritmos quieren la mejor oferta
posible, lo que significa realizar un análisis de costo/beneficio. Por supuesto, conseguir el mejor trato
también supone que una persona que utiliza el algoritmo tiene alguna idea de qué tipo de solución es lo
suficientemente buena. Obtener una solución que sea demasiado precisa o que ofrezca demasiados
resultados suele ser un desperdicio, por lo que parte de mantener los costos bajo control es obtener lo
que necesita como resultado y nada más.
Para saber lo que tienes con un algoritmo, necesitas saber cómo medirlo de varias maneras. Las
mediciones crean en su mente una imagen de usabilidad, tamaño, uso de recursos y costo. Más
importante aún, las mediciones ofrecen los medios para hacer comparaciones. No se pueden comparar
algoritmos sin mediciones. Hasta que no puedas comparar los algoritmos, no podrás elegir el mejor para
una tarea.
Comenzando a resolver un problema
Antes de poder resolver cualquier problema, debes comprenderlo. Tampoco se trata sólo de evaluar el
problema. Saber que tiene ciertas entradas y requiere ciertas salidas es un comienzo, pero eso no es
suficiente para crear una solución. Parte del proceso de solución es
» Descubra cómo otras personas han creado nuevas soluciones a problemas.
» Sepa qué recursos tiene a mano
» Determinar los tipos de soluciones que funcionaron para problemas similares en el pasado.
» Considere qué tipos de soluciones no han producido un resultado deseable.
Las siguientes secciones le ayudarán a comprender estas fases de la resolución de un problema.
Tenga en cuenta que no necesariamente realizará estas fases en orden y que a veces volverá a visitar
una fase después de obtener más información. El proceso de iniciar la solución de un problema es
iterativo; Continúe así hasta que comprenda bien el problema en cuestión.
24 PARTE 1 Primeros pasos
Machine Translated by Google
Modelar problemas del mundo real
Los problemas del mundo real difieren de los que se encuentran en los libros de texto. Al
crear un libro de texto, el autor suele crear un ejemplo sencillo para ayudar al lector a
comprender los principios básicos en funcionamiento. El ejemplo modela sólo un aspecto de
un problema más complejo. Un problema del mundo real puede requerir que se combinen
varias técnicas para crear una solución completa. Por ejemplo, para localizar la mejor
respuesta a un problema, puede:
1. Necesidad de ordenar la respuesta establecida según un criterio específico.
2. Realizar algún tipo de filtrado y transformación.
3. Busque el resultado.
Sin esta secuencia de pasos, comparar cada una de las respuestas adecuadamente puede
resultar imposible y terminar con un resultado que no es óptimo. Una serie de algoritmos
utilizados juntos para crear un resultado deseado es un conjunto. Puede leer sobre su uso en
el aprendizaje automático en Machine Learning For Dummies, de John Paul Mueller y Luca
Massaron (Wiley). El artículo en https://www.toptal.com/machine­learning/ensemble­methods­
machine­learning le ofrece una descripción general rápida de cómo funcionan los conjuntos.
Sin embargo, los problemas del mundo real son incluso más complejos que simplemente
mirar datos estáticos o iterarlos una sola vez. Por ejemplo, cualquier cosa que se mueva,
como un automóvil, un avión o un robot, recibe información constante. Cada entrada
actualizada incluye información de error que una solución del mundo real deberá incorporar
al resultado para que estas máquinas sigan funcionando correctamente. Además de otros
algoritmos, los cálculos de constantes requieren el algoritmo de derivada integral proporcional
(PID) (consulte http://www.ni.com/white­paper/3782/en/ para obtener una explicación detallada
de este algoritmo) para controlar la máquina mediante un circuito de retroalimentación. Cada
cálculo enfoca mejor la solución utilizada para controlar la máquina, razón por la cual las
máquinas a menudo pasan por una etapa de asentamiento cuando se encienden por primera
vez. (Si trabaja regularmente con computadoras, es posible que esté acostumbrado a la
idea de iteraciones. Los PID son para sistemas continuos; por lo tanto, no hay iteraciones).
Encontrar la solución correcta se llama tiempo de establecimiento: el tiempo durante el cual
el algoritmo que controla la máquina aún no ha encontrado la respuesta correcta.
Al modelar un problema del mundo real, también se deben considerar cuestiones no obvias
que surjan. Una solución obvia, incluso una basada en importantes aportaciones matemáticas
y una teoría sólida, puede no funcionar. Por ejemplo, durante la Segunda Guerra Mundial, los
aliados tuvieron un grave problema con las pérdidas de bombarderos. Por lo tanto, los
ingenieros analizaron cada agujero de bala en cada avión que regresó. Después del análisis,
los ingenieros utilizaron su solución para blindar más los aviones aliados para garantizar que
más de ellos regresaran. No funcionó. Entra Abraham Wald. Este matemático sugirió una
solución no obvia: poner blindaje en todos los lugares que carecían de agujeros de bala
(porque las áreas con agujeros de bala ya son lo suficientemente fuertes;
CAPÍTULO 2 Consideración del diseño de algoritmos 25
Machine Translated by Google
de lo contrario el avión no habría regresado). La solución resultante funcionó y ahora se utiliza como
base para el sesgo de los sobrevivientes (el hecho de que los sobrevivientes de un incidente a menudo
no muestran lo que realmente causó una pérdida) al trabajar con algoritmos. Puede leer más sobre esta
fascinante parte de la historia en http://www.macgetit.com/
resolviendo­problemas­de­bombarderos­de­la­guerra­mundial/. La cuestión es que los sesgos y otros
problemas en el modelado de problemas pueden crear soluciones que no funcionan.
Los modelos del mundo real también pueden incluir la adición de lo que los científicos normalmente
consideran rasgos indeseables. Por ejemplo, los científicos suelen considerar que el ruido es indeseable
porque oculta los datos subyacentes. Considere un audífono, que elimina el ruido para permitir que
alguien escuche mejor (consulte la discusión en http://www.ncbi.
nlm.nih.gov/pmc/articles/PMC4111515/ para detalles). Existen muchos métodos para
eliminar el ruido, algunos de los cuales puede encontrar en este libro a partir del
Capítulo 9 como parte de otras discusiones temáticas. Sin embargo, por muy
contradictorio que parezca, agregar ruido también requiere un algoritmo que
proporcione resultados útiles. Por ejemplo, Ken Perlin quiso deshacerse del aspecto
de máquina de los gráficos generados por computadora en 1983 y creó un algoritmo
para lograrlo. El resultado es ruido Perlin (ver http://paulbourke.net/texture_color/
perlin/ para detalles). El efecto es tan útil que Ken ganó un Premio de la Academia por su trabajo (ver h
edu/~perlin/doc/oscar.html para detalles). Otras personas, como Steven Worley, han creado otros tipos
de ruido que afectan los gráficos de otras maneras (consulte la discusión en http://procworld.blogspot.com/
2011/05/hello­worley.html, que compara el ruido Perlin con el ruido Worley). El punto es que si necesita
eliminar o agregar ruido depende del dominio del problema que desee resolver. Un escenario del mundo
real a menudo requiere opciones que pueden no ser obvias cuando se trabaja en el laboratorio o
durante el proceso de aprendizaje.
La esencia principal de esta sección es que las soluciones a menudo requieren varias iteraciones para
crearse, es posible que tenga que dedicar mucho tiempo a perfeccionarlas y es posible que las soluciones
obvias no funcionen en absoluto. Al modelar un problema del mundo real, se comienza con las soluciones
que se encuentran en los libros de texto, pero luego hay que ir más allá de la teoría para encontrar la
solución real a su problema. A medida que avanza este libro, estará expuesto a una amplia variedad de
algoritmos, todos los cuales le ayudarán a encontrar soluciones. Lo importante que debe recordar es que
es posible que necesite combinar estos ejemplos de varias maneras y descubrir métodos para interactuar
con los datos de modo que se presten a encontrar patrones que coincidan con el resultado que necesita.
Encontrar soluciones y contraejemplos
La sección anterior le presenta los caprichos de descubrir soluciones del mundo real que consideran
problemas que las soluciones encontradas en el laboratorio no pueden considerar.
Sin embargo, simplemente encontrar una solución, incluso una buena, no es suficiente porque incluso
las buenas soluciones fallan en ocasiones. Hacer de abogado del diablo localizando contraejemplos es
una parte importante para empezar a resolver un problema. El propósito de los contraejemplos es
26 PARTE 1 Primeros pasos
Machine Translated by Google
» Potencialmente refutar la solución
» Proporcionar límites que definan mejor la solución
» Considere situaciones en las que la hipótesis utilizada como base para la solución
permanece sin probar
» Ayudarle a comprender los límites de la solución
Un escenario común que ilustra una solución y un contraejemplo es la afirmación de que
todos los números primos son impares. (Los números primos son números enteros que sólo
pueden dividirse entre sí mismos y 1 para producir un resultado entero). Por supuesto, el
número 2 es primo, pero también es par, lo que hace que la afirmación original sea falsa.
Alguien que haga la afirmación podría entonces matizarla diciendo que todos los números
primos son impares excepto 2. La solución parcial al problema de encontrar todos los números
primos es que necesitas encontrar los números impares, excepto en el caso de 2, que es par. .
En este segundo caso, ya no es posible refutar la solución, pero agregar algo a la afirmación
original proporciona un límite.
Al poner en duda la afirmación original, también se pueden considerar situaciones en las que la
hipótesis de que todos los números primos excepto 2 son impares puede no ser cierta. Por ejemplo, 1
es un número impar pero no se considera primo (consulte la discusión en https://primes.utm.edu/notes/
faq/one.html para detalles). Así que ahora el enunciado original tiene dos límites y debes reformularlo
de la siguiente manera: Los números primos son mayores que 1 y normalmente son impares, excepto
2, que es par. Los límites de los números primos se definen mejor localizando y considerando
contraejemplos.
En caso de que se lo esté preguntando, el 0 tampoco se considera un número primo, por los
motivos que se analizan en http://math.stackexchange.com/questions/539174/
es­cero­un­número­primo.
A medida que crece la complejidad de un problema, también crece la posibilidad de encontrar
contraejemplos. Una regla esencial a considerar es que, al igual que con la confiabilidad, tener
más puntos de falla significa una mayor posibilidad de que ocurra una falla. Es importante
pensar en los algoritmos de esta manera. Los conjuntos de algoritmos simples pueden producir
mejores resultados con menos contraejemplos potenciales que un único algoritmo complejo.
De pie sobre los hombros de gigantes
Un mito que desafía toda explicación es que las técnicas que se utilizan actualmente para procesar enormes
cantidades de datos son de algún modo nuevas. Sí, aparecen nuevos algoritmos todo el tiempo, pero la base
de estos algoritmos son todos los algoritmos anteriores. De hecho, cuando piensas en Sir Isaac Newton,
podrías pensar en alguien que inventó algo nuevo, pero incluso él afirmó (usando la ortografía correcta para
su época): “Si he visto más lejos es estando sobre los hombros de Gigantes”. (ver https://en.wikiquote.org/wiki/
Isaac_Newton para citas e ideas adicionales).
CAPÍTULO 2 Consideración del diseño de algoritmos 27
Machine Translated by Google
El hecho es que los algoritmos que se utilizan hoy ni siquiera eran nuevos en la época de
Aristóteles (ver http://plato.stanford.edu/entries/aristotle­mathematics/
para una discusión sobre cómo Aristóteles usó las matemáticas) y Platón (ver http://www.story
ofmathematics.com/greek_plato.html para una discusión sobre cómo Platón usó las matemáticas).
Los orígenes de los algoritmos que se utilizan hoy en día están tan ocultos en la historia que lo
mejor que se puede decir es que las matemáticas se basan en adaptaciones del conocimiento de
la antigüedad. El uso de algoritmos desde la antigüedad debería brindarle una cierta sensación
de comodidad porque los algoritmos que se utilizan hoy en día se basan en conocimientos
probados durante miles de años.
Esto no quiere decir que algunos matemáticos no hayan cambiado la situación a lo largo de
los años. Por ejemplo, la teoría de John Nash, el Equilibrio de Nash, cambió significativamente
la forma en que se considera la economía hoy (ver https://www.khanacademy.
org/economics­finance­domain/microeconomics/nash­equilibrium­ tutorial para un tutorial básico
sobre esta teoría). Por supuesto, el reconocimiento de ese trabajo llega lentamente (y a veces ni
siquiera). Nash tuvo que esperar mucho tiempo antes de recibir mucho reconocimiento profesional
(vea la historia en https://www.princeton.edu/main/news/archive/S42/72/29C63/index.xml) a pesar
de haber ganado un premio Nobel de economía por sus aportaciones. En caso de que esté
interesado, la historia de John Nash se describe en la película A Beautiful Mind.
que contiene algunas escenas muy debatidas, incluida una que contiene una afirmación de que el
equilibrio de Nash de alguna manera anula parte del trabajo de Adam Smith, otro contribuyente a
las teorías económicas. (Vea una de esas discusiones en https://
www.quora.com/Was­Adam­Smith­wrong­as­claimed­by­John­Nash­in­the­movie­A­Beautiful­
Mind ).
Dividiendo y conquistando
Si resolver problemas fuera fácil, todos lo harían. Sin embargo, el mundo todavía está lleno de
problemas sin resolver y no es probable que la situación cambie pronto, por una simple razón: los
problemas a menudo parecen tan grandes que no es posible imaginar ninguna solución. Los
antiguos guerreros se enfrentaban a un problema similar. Un ejército enemigo parecería tan
grande y sus fuerzas tan pequeñas que harían que el problema de ganar una guerra fuera
inimaginablemente difícil, tal vez imposible. Sin embargo, al dividir el ejército contrario en pequeñas
partes y atacarlo poco a poco, un ejército pequeño podría potencialmente derrotar a un oponente
mucho más grande. (Los antiguos griegos, romanos y Napoleón Bonaparte fueron grandes
usuarios de la estrategia de divide y vencerás; véase Napoleon For Dummies, de J. David
Markham [Wiley], para más detalles.)
Te enfrentas al mismo problema que esos antiguos guerreros. A menudo, los recursos a su
disposición parecen bastante pequeños e inadecuados. Sin embargo, al dividir un problema
enorme en partes pequeñas para que puedas entender cada parte, eventualmente podrás crear
una solución que funcione para el problema en su conjunto. Los algoritmos tienen esta premisa en
28 PARTE 1 Primeros pasos
Machine Translated by Google
su núcleo: utilizar pasos para resolver problemas, una pequeña parte a la vez. Las siguientes
secciones le ayudarán a comprender con más detalle el enfoque de divide y vencerás para la
resolución de problemas.
Evitar soluciones de fuerza bruta
Una solución de fuerza bruta es aquella en la que se prueba cada respuesta posible, una a la
vez, para encontrar la mejor respuesta posible. Es minucioso, eso es seguro, pero también
desperdicia tiempo y recursos en la mayoría de los casos. Probar cada respuesta, incluso
cuando es fácil demostrar que una respuesta en particular no tiene posibilidades de éxito,
desperdicia el tiempo que un algoritmo puede utilizar en respuestas que tienen más posibilidades
de éxito. Además, probar las distintas respuestas utilizando este enfoque generalmente
desperdicia recursos, como la memoria. Piénselo de esta manera: desea romper la combinación
de una cerradura, por lo que comienza en 0, 0, 0, aunque sepa que esta combinación en
particular no tiene posibilidades de éxito dadas las características físicas de las cerraduras de
combinación. Una solución de fuerza bruta procedería a probar 0, 0, 0 de todos modos y luego
pasaría al igualmente ridículo 0, 0, 1.
Es importante comprender que cada tipo de solución tiene ventajas, a veces bastante pequeñas.
Una solución de fuerza bruta tiene una de esas ventajas. Debido a que de todos modos prueba
cada respuesta, no necesita realizar ningún tipo de preprocesamiento cuando trabaja con una
solución de fuerza bruta. Sin embargo, es poco probable que el tiempo ahorrado al omitir el
preprocesamiento recupere el tiempo perdido al intentar cada respuesta. Sin embargo, puede
que encuentres la ocasión de utilizar una solución de fuerza bruta cuando
» Encontrar una solución, si existe, es esencial.
» El tamaño del problema es limitado.
» Puede utilizar heurísticas para reducir el tamaño del conjunto de soluciones.
» La simplicidad de la implementación es más importante que la velocidad.
Empezando por hacerlo más sencillo
La solución de fuerza bruta tiene un serio inconveniente. Analiza todo el problema de una sola
vez. Es como entrar en un paquete y buscar libro por libro entre los estantes sin siquiera
considerar ningún método para simplificar la búsqueda.
El enfoque de divide y vencerás para la búsqueda de paquetes es diferente. En este caso, se
empieza dividiendo el paquete en secciones para niños y para adultos. Después de eso, divides
la sección de adultos en categorías. Finalmente, busca solo la parte de la categoría que contiene
el libro de interés. Este es el propósito de sistemas de clasificación como el Sistema Decimal
Dewey (ver https://en.wikipedia.org/
wiki/List_of_Dewey_Decimal_classes para obtener una lista de clases, divisiones jerárquicas,
CAPÍTULO 2 Consideración del diseño de algoritmos 29
Machine Translated by Google
y secciones). La cuestión es que dividir y conquistar simplifica el problema. Hace las cosas más rápidas y
fáciles al reducir la cantidad de candidatos para libros.
La parte dividida de divide y vencerás también es una forma esencial de comprender mejor un problema.
Intentar comprender el diseño de un paquete completo puede resultar complicado. Sin embargo, saber que
el libro sobre psicología comparada que desea encontrar aparece como parte de la Clase 100 en la División
150 de la Sección 156 facilita su trabajo. Puede comprender este problema menor porque sabe que cada
libro de la Sección 156 contendrá algo sobre el tema que desea saber.
Los algoritmos funcionan de la misma manera. Al simplificar el problema, puede crear un conjunto de pasos
más simples para encontrar una solución al problema, lo que reduce el tiempo para encontrar la solución,
reduce la cantidad de recursos utilizados y mejora sus posibilidades de encontrar precisamente la solución
que necesita.
Generalmente es mejor resolver un problema
Después de haber dividido un problema en partes manejables, es necesario conquistar la parte en cuestión.
Esto significa crear una definición precisa del problema. No querrás cualquier libro sobre psicología
comparada; quieres uno escrito por George Romanes. Saber que el libro que desea aparece en la Sección
156 del Sistema Decimal Dewey es un buen comienzo, pero no resuelve el problema. Ahora necesita un
proceso para revisar cada libro de la Sección 156 para el libro específico que necesita. El proceso podría ir
más allá y buscar libros con contenidos específicos. Para que este proceso sea viable, debe desglosar el
problema por completo, definir con precisión lo que necesita y luego, después de comprender el problema
a fondo, utilizar el conjunto correcto de pasos (algoritmo) para encontrar lo que necesita.
LOS ALGORITMOS NO TIENEN ABSOLUTOS
Quizás pienses que puedes crear un escenario en el que puedas decir que siempre usas un tipo
particular de algoritmo para resolver un tipo particular de problema. Sin embargo, este no es el caso.
Por ejemplo, puede encontrar discusiones sobre los méritos relativos del uso de técnicas de fuerza
bruta contra ciertos problemas en comparación con dividir y conquistar. No debería sorprenderle
descubrir que dividir y conquistar no gana en todas las situaciones. Por ejemplo, cuando se busca
el valor máximo en una matriz, un enfoque de fuerza bruta puede ganar el día en que la matriz no esté
ordenada. Puede leer una discusión sobre este tema en particular en http://stackoverflow.com/questions/
11043226/why­do­divide­and­conquer­algorithms­often­run­faster­than­brute­force . Lo interesante
es que el enfoque de fuerza bruta también utiliza menos recursos en este caso particular.
Recuerde siempre que las reglas tienen excepciones y conocer las excepciones puede ahorrarle
tiempo y esfuerzo en el futuro.
30 PARTE 1 Primeros pasos
Machine Translated by Google
Aprender que la codicia puede ser buena
En algunos casos, no se puede ver el final de un proceso de solución ni siquiera saber si se está ganando la
guerra. Lo único que realmente puedes hacer es asegurarte de ganar las batallas individuales para crear una
solución al problema con la esperanza de ganar también la guerra.
Un método codicioso para la resolución de problemas utiliza este enfoque. Busca una solución global de
modo que elija el mejor resultado posible en cada etapa de solución del problema.
Parece que ganar cada batalla significaría necesariamente ganar también la guerra, pero a veces el mundo
real no funciona así. Una victoria pírrica es aquella en la que alguien gana todas las batallas pero termina
perdiendo la guerra porque el costo de la victoria excede las ganancias de ganar por un margen tan grande.
Puede leer sobre cinco victorias pírricas en http://www.history.com/news/history­lists/
5­famosas­victorias­pírricas. La lección importante de estas historias es que un algoritmo codicioso a menudo
funciona, pero no siempre, por lo que es necesario considerar la mejor solución general a un problema en
lugar de quedar cegado por victorias provisionales.
Las siguientes secciones describen cómo evitar la victoria pírrica cuando se trabaja con algoritmos.
Aplicando razonamiento codicioso
El razonamiento codicioso se utiliza a menudo como parte de un proceso de optimización. El algoritmo ve el
problema paso a paso y se centra únicamente en el paso en cuestión. Todo algoritmo codicioso hace dos
suposiciones:
» Puede realizar una única elección óptima en un paso determinado.
» Al elegir la selección óptima en cada paso, puede encontrar una solución óptima para el
problema general.
Puede encontrar muchos algoritmos codiciosos, cada uno de ellos optimizado para realizar tareas particulares.
A continuación se muestran algunos ejemplos comunes de algoritmos codiciosos utilizados para el análisis de gráficos
(consulte el Capítulo 9 para obtener más información sobre gráficos) y la compresión de datos (consulte el Capítulo 14
para obtener más información sobre la compresión de datos) y la razón por la que es posible que desee utilizarlos:
» Árbol de expansión mínima (MST) de Kruskal: este algoritmo en realidad demuestra
estratega uno de los principios de los algoritmos codiciosos en el que la gente quizás no
piense de inmediato. En este caso, el algoritmo elige el borde entre dos nodos con el
valor más pequeño, no el valor más grande, como podría transmitir inicialmente la palabra
codicioso . Este tipo de algoritmo podría ayudarle a encontrar el camino más corto
entre dos ubicaciones en un mapa o realizar otras tareas relacionadas con los gráficos.
» MST de Prim: este algoritmo divide un gráfico no dirigido (uno en el que no se considera la
dirección) por la mitad. Luego selecciona el borde que conecta las dos mitades de
manera que el peso total de las dos mitades sea el más pequeño posible. Tú
CAPÍTULO 2 Consideración del diseño de algoritmos 31
Machine Translated by Google
Podríamos encontrar que este algoritmo se utiliza en un juego de laberinto para localizar la
distancia más corta entre el inicio y el final del laberinto.
» Codificación Huffman: Este algoritmo es bastante famoso en las computadoras porque
constituye la base de muchas técnicas de compresión de datos. El algoritmo asigna un código
a cada entrada de datos única en un flujo de entradas, de modo que la entrada de datos
más utilizada recibe el código más corto. Por ejemplo, la letra E normalmente recibiría el
código más corto al comprimir texto en inglés, porque la usas con más frecuencia que
cualquier otra letra del alfabeto. Al cambiar la técnica de codificación, puedes
comprimir el texto y hacerlo considerablemente más pequeño, reduciendo el tiempo de
transmisión.
Llegar a una buena solución
Los científicos y matemáticos utilizan algoritmos codiciosos con tanta frecuencia que el Capítulo 15 los cubre
en profundidad. Sin embargo, es importante darse cuenta de que lo que realmente desea es una buena
solución, no sólo una solución particular. En la mayoría de los casos, una buena solución proporciona
resultados óptimos del tipo que se pueden medir, pero la palabra bueno puede incluir muchos significados,
dependiendo del dominio del problema. Debe preguntar qué problema desea resolver y qué solución resuelve
el problema de la manera que mejor satisfaga sus necesidades. Por ejemplo, cuando trabaja en ingeniería, es
posible que necesite sopesar soluciones que consideren el peso, el tamaño, el costo u otras consideraciones,
o tal vez alguna combinación de todos estos resultados que cumplan con un requisito específico.
Para poner este problema en contexto, digamos que usted construye una máquina de monedas que genera
cambio para determinadas cantidades monetarias utilizando la menor cantidad de monedas posible (quizás
como parte de una caja automática en una tienda). La razón para utilizar la menor cantidad de monedas
posible es reducir el desgaste del equipo, el peso de las monedas necesarias y el tiempo necesario para hacer
el cambio (después de todo, sus clientes siempre tienen prisa). Una solución codiciosa resuelve el problema
utilizando las monedas más grandes posibles. Por ejemplo, para generar $0,16 de cambio, utiliza una moneda
de diez centavos ($0,10), una moneda de cinco centavos ($0,05) y una moneda de un centavo ($0,01).
Se produce un problema cuando no se pueden utilizar todos los tipos de monedas para crear una solución.
Por ejemplo, es posible que la máquina de cambio se haya quedado sin monedas de cinco centavos. Para proporcionar 0,40 dólares de
cambio, una solución codiciosa comenzaría con una moneda de veinticinco centavos (0,25 dólares) y una moneda de diez centavos (0,10 dólares).
Desafortunadamente, no hay monedas de cinco centavos, por lo que la máquina de monedas
produce cinco centavos (5 × $0,01) para un total de siete monedas. La solución óptima en
este caso es utilizar cuatro monedas de diez centavos (4 × $0,10). Como resultado, el
algoritmo codicioso proporciona una solución particular, pero no una buena (óptima)
solución en este caso. El problema del cambio recibe considerable atención porque es muy
difícil de resolver. Puede encontrar información adicional en debates como “La combinatoria
del problema de la creación de cambios”, de Anna Adamaszeka y Michal Adamaszek (consulte http://www.
sciencedirect.com/science/article/pii/S0195669809001292 para detalles).
32 PARTE 1 Primeros pasos
Machine Translated by Google
Costos de computación y seguimiento de heurísticas
Incluso cuando encuentre una buena solución, una que sea a la vez eficiente y efectiva, aún necesita
saber exactamente cuánto cuesta la solución. Es posible que descubra que el coste de utilizar una
solución concreta sigue siendo demasiado alto, incluso teniendo en cuenta todo lo demás. Quizás la
respuesta llegue casi a tiempo, pero no del todo, o utilice demasiados recursos informáticos. La
búsqueda de una buena solución implica crear un entorno en el que se pueda probar completamente
el algoritmo, los estados que crea, los operadores que utiliza para cambiar esos estados y el tiempo
necesario para derivar una solución.
A menudo, se descubre que un enfoque heurístico, uno que se basa en el autodescubrimiento y
produce resultados suficientemente útiles (no necesariamente óptimos, pero sí suficientemente
buenos) es el método que realmente se necesita para resolver un problema. Lograr que el algoritmo
realice parte del trabajo requerido por usted ahorra tiempo y esfuerzo porque puede crear algoritmos
que ven patrones mejor que los humanos. En consecuencia, el autodescubrimiento es el proceso que
permite que el algoritmo le muestre un camino potencialmente útil hacia una solución (pero aún debe
contar con la intuición y la comprensión humanas para saber si la solución es la correcta). Las
siguientes secciones describen técnicas que puede utilizar para calcular el costo de un algoritmo
utilizando heurísticas como método para descubrir la utilidad real de cualquier solución determinada.
Representar el problema como un espacio.
Un espacio problemático es un entorno en el que se lleva a cabo la búsqueda de una solución.
Un conjunto de estados y los operadores utilizados para cambiar esos estados representan el
espacio del problema. Por ejemplo, considere un juego de fichas que tiene ocho fichas en un marco de 3 x 3.
Cada mosaico muestra una parte de una imagen y los mosaicos comienzan en un orden
aleatorio para que la imagen esté desordenada. El objetivo es mover una ficha a la vez para
colocar todas las fichas en el orden correcto y revelar la imagen. Puedes ver un ejemplo de
este tipo de rompecabezas en http://mypuzzle.org/sliding.
La combinación del estado inicial, las fichas aleatorias y el estado objetivo (las fichas en un orden
particular) es el caso del problema. Podrías representar el rompecabezas gráficamente usando una
gráfica del espacio del problema. Cada nodo del gráfico del espacio del problema presenta un estado
(las ocho fichas en una posición particular). Los bordes representan operaciones, como mover la ficha
número ocho hacia arriba. Cuando mueves el mosaico ocho hacia arriba, la imagen cambia: pasa a
otro estado.
Ganar el juego pasando del estado inicial al estado objetivo no es la única consideración. Para resolver
el juego de manera eficiente, debes realizar la tarea en el menor número de movimientos posible, lo
que significa utilizar el menor número de operadores. El número mínimo de movimientos utilizados
para resolver el rompecabezas es la profundidad del problema.
CAPÍTULO 2 Consideración del diseño de algoritmos 33
Machine Translated by Google
Debes considerar varios factores a la hora de representar un problema como espacio. Por
ejemplo, debe considerar el número máximo de nodos que caben en la memoria, lo que
representa la complejidad del espacio. Cuando no se pueden colocar todos los nodos en la
memoria al mismo tiempo, la computadora debe almacenar algunos nodos en otras
ubicaciones, como el disco duro, lo que puede ralentizar considerablemente el algoritmo.
Para determinar si los nodos caben en la memoria, se debe considerar la complejidad del tiempo,
que es el número máximo de nodos creados para resolver el problema. Además, es
importante considerar el factor de ramificación, que es el número promedio de nodos creados
en el gráfico del espacio del problema para resolver un problema.
Ir al azar y ser bendecido por la suerte.
Es posible resolver un problema de búsqueda utilizando técnicas de fuerza bruta (descritas
en “Evitar técnicas de fuerza bruta”, anteriormente en este capítulo). La ventaja de este
enfoque es que no necesita ningún conocimiento específico del dominio para utilizar uno de
estos algoritmos. Un algoritmo de fuerza bruta tiende a utilizar el enfoque más simple posible
para resolver el problema. La desventaja es que un enfoque de fuerza bruta funciona bien
sólo para una pequeña cantidad de nodos. Estos son algunos de los algoritmos de
búsqueda de fuerza bruta más comunes:
» Búsqueda en amplitud: esta técnica comienza en el nodo raíz, explora cada uno de los nodos
secundarios primero y solo luego baja al siguiente nivel. Avanza nivel a nivel hasta encontrar
una solución. La desventaja de este algoritmo es que debe almacenar cada nodo en la
memoria, lo que significa que utiliza una cantidad considerable de memoria para una gran
cantidad de nodos. Esta técnica puede buscar nodos duplicados, lo que ahorra tiempo y
siempre genera una solución.
» Búsqueda en profundidad: esta técnica comienza en el nodo raíz y explora un conjunto de
nodos secundarios conectados hasta llegar a un nodo hoja. Avanza rama por rama hasta
encontrar una solución. La desventaja de este algoritmo es que no puede comprobar si hay
nodos duplicados, lo que significa que puede atravesar las mismas rutas de nodo más de
una vez. De hecho, es posible que este algoritmo no encuentre ninguna solución, lo
que significa que debe definir un punto de corte para evitar que el algoritmo busque
infinitamente. Una ventaja de este enfoque es que es eficiente en cuanto a memoria.
» Búsqueda bidireccional: esta técnica busca simultáneamente desde el nodo raíz y el nodo
objetivo hasta que las dos rutas de búsqueda se encuentran en el medio. Una ventaja
de este enfoque es que ahorra tiempo porque encuentra la solución más rápido
que muchas otras soluciones de fuerza bruta. Además, utiliza la memoria de manera
más eficiente que otros enfoques y siempre encuentra una solución.
La principal desventaja es la complejidad de la implementación, lo que se traduce en
un ciclo de desarrollo más largo.
34 PARTE 1 Primeros pasos
Machine Translated by Google
Usando una heurística y una función de costo
Para algunas personas, la palabra heurística suena complicada. Sería igual de fácil decir que
el algoritmo hace una suposición fundamentada y luego vuelve a intentarlo cuando falla. A
diferencia de los métodos de fuerza bruta, los algoritmos heurísticos aprenden. También
utilizan funciones de costos para tomar mejores decisiones. En consecuencia, los algoritmos
heurísticos son más complejos, pero tienen una clara ventaja a la hora de resolver problemas
complejos. Al igual que con los algoritmos de fuerza bruta, existen muchos algoritmos
heurísticos y cada uno tiene su propio conjunto de ventajas, desventajas y requisitos
especiales. La siguiente lista describe algunos de los algoritmos heurísticos más comunes:
» Búsqueda heurística pura: El algoritmo expande los nodos en orden de coste. Mantiene dos listas. La
lista cerrada contiene los nodos que ya ha explorado; la lista abierta contiene los nodos que aún
debe explorar. En cada iteración, el algoritmo expande el nodo con el menor coste posible.
Todos sus nodos secundarios se colocan en la lista cerrada y se calculan los costos de los nodos
secundarios individuales.
El algoritmo envía los nodos secundarios con un costo bajo de regreso a la lista abierta y elimina los
nodos secundarios con un costo alto. En consecuencia, el algoritmo realiza una búsqueda
inteligente de la solución basada en costes.
» Una búsqueda *: el algoritmo rastrea el costo de los nodos a medida que los explora usando la
ecuación: f(n) = g(n) + h(n), donde
• n es el identificador del nodo.
• g(n) es el coste de llegar al nodo hasta el momento.
• h(n) es el coste estimado para alcanzar la meta desde el nodo.
• f(n) es el coste estimado del camino desde n hasta la meta.
La idea es buscar primero los caminos más prometedores y evitar los costosos.
» Búsqueda codiciosa de lo mejor primero: el algoritmo siempre elige la ruta que es
más cercano a la meta usando la ecuación: f(n) = h(n). Este algoritmo en particular puede encontrar
soluciones con bastante rapidez, pero también puede quedarse atrapado en bucles, por lo que
muchas personas no lo consideran un enfoque óptimo para encontrar una solución.
Evaluación de algoritmos
Obtener información sobre cómo funcionan exactamente los algoritmos es importante
porque, de lo contrario, no se puede determinar si un algoritmo realmente funciona de la
manera que se necesita. Además, sin buenas mediciones, no se pueden realizar
comparaciones precisas para saber si realmente es necesario descubrir un nuevo método
para resolver un problema cuando una solución anterior funciona demasiado lentamente o utiliza demasiados
CAPÍTULO 2 Consideración del diseño de algoritmos 35
Machine Translated by Google
recursos. La realidad es que utilizarás algoritmos creados por otros la mayor parte del tiempo,
y potencialmente idearás algunos propios. Conocer la base a utilizar para comparar diferentes
soluciones y decidir entre ellas es una habilidad esencial cuando se trata de algoritmos.
La cuestión de la eficiencia ha sido parte del descubrimiento y diseño de nuevos algoritmos
desde que surgió el concepto de algoritmos, razón por la cual se ven tantos algoritmos
diferentes compitiendo para resolver el mismo problema (a veces una verdadera vergüenza
de riquezas). El concepto de medir el tamaño de las funciones dentro de un algoritmo y
analizar cómo funciona el algoritmo no es nuevo; Tanto Ada Lovelace como Charles Babbage
consideraron los problemas de la eficiencia de los algoritmos en referencia a las computadoras
ya en 1843 (ver una breve historia del motor Babbage en http://www.computerhistory.org/
babbage/adalovelace/).
Donald Knuth (http://www­cs­faculty.stanford.edu/~uno/), Científico informático, matemático,
profesor emérito de la Universidad de Stanford y autor del importante libro de varios
volúmenes The Art of Computer Programming (Addison­Wesley), dedicó gran parte de sus
investigaciones y estudios a comparar algoritmos. Se esforzó por formalizar cómo estimar las
necesidades de recursos de los algoritmos de forma matemática y permitir una comparación
correcta entre soluciones alternativas. Acuñó el término análisis de algoritmos, que es la rama
de la informática dedicada a comprender cómo funcionan los algoritmos de forma formal. El
análisis mide los recursos necesarios en términos de la cantidad de operaciones que requiere
un algoritmo para alcanzar una solución o por su espacio ocupado (como el almacenamiento
que requiere un algoritmo en la memoria de la computadora).
El análisis de algoritmos requiere cierta comprensión matemática y algunos cálculos, pero es
extremadamente beneficioso en su viaje para descubrir, apreciar y utilizar algoritmos de
manera efectiva. Este tema es considerablemente más abstracto que otros temas de este
libro. Para hacer la discusión menos teórica, los capítulos posteriores presentan más aspectos
prácticos de dicha medición examinando los algoritmos en detalle. Las siguientes secciones
le proporcionan los conceptos básicos.
Simulando usando máquinas abstractas
Cuantas más operaciones requiera un algoritmo, más complejo será. La complejidad es una
medida de la eficiencia del algoritmo en términos de uso del tiempo porque cada operación
lleva algún tiempo. Ante el mismo problema, los algoritmos complejos son generalmente
menos favorables que los algoritmos simples porque los algoritmos complejos requieren más tiempo.
Piense en esos momentos en los que la velocidad de ejecución marca la diferencia, como en
el sector médico o financiero, o cuando se vuela con piloto automático en un avión o cohete
espacial. Medir la complejidad de los algoritmos es una tarea desafiante, aunque
36 PARTE 1 Primeros pasos
Machine Translated by Google
uno necesario si desea emplear la solución adecuada. La primera técnica de medición utiliza
máquinas abstractas como la Máquina de Acceso Aleatorio (RAM).
RAM también significa memoria de acceso aleatorio, que es la memoria interna que utiliza
su computadora cuando ejecuta programas. Aunque utiliza el mismo acrónimo, una
máquina de acceso aleatorio es algo completamente diferente.
Las máquinas abstractas no son ordenadores reales, sino teóricos, ordenadores imaginados
en su funcionamiento. Se utilizan máquinas abstractas para considerar qué tan bien
funcionaría un algoritmo en una computadora sin probarlo en una computadora real, pero
sujeto al tipo de hardware que usaría. Una computadora RAM realiza operaciones
aritméticas básicas e interactúa con la información en la memoria, eso es todo. Cada vez
que una computadora RAM hace algo, toma un paso de tiempo (una unidad de tiempo).
Cuando evalúa un algoritmo en una simulación de RAM, cuenta los pasos de tiempo
utilizando el siguiente procedimiento:
1. Cuente cada operación simple (aritmética) como un paso de tiempo.
2. Divida operaciones complejas en operaciones aritméticas simples y cuente el tiempo.
pasos definidos en el Paso 1.
3. Cuente cada acceso a datos desde la memoria como un paso de tiempo.
Para realizar esta contabilidad, escribe una versión en pseudocódigo de su algoritmo (como
se menciona en el Capítulo 1) y realiza estos pasos usando papel y lápiz. Al final, es un
enfoque simple basado en una idea básica de cómo funcionan las computadoras, una
aproximación útil que puedes usar para comparar soluciones sin importar la potencia y
velocidad de tu hardware o el lenguaje de programación que uses.
Usar una simulación es diferente a ejecutar el algoritmo en una computadora porque usa
una entrada estándar y predefinida. Las mediciones reales por computadora requieren que
ejecute el código y verifique el tiempo requerido para ejecutarlo. Ejecutar código en una
computadora es en realidad un punto de referencia, otra forma de medición de la eficiencia,
en la que también se tiene en cuenta el entorno de la aplicación (como el tipo de hardware
utilizado y la implementación del software). Un punto de referencia es útil pero carece de
generalización. Considere, por ejemplo, cómo el hardware más nuevo puede ejecutar
rápidamente un algoritmo que llevaba mucho tiempo en su computadora anterior.
Cada vez más abstracto
Medir una serie de pasos ideados para lograr una solución a un problema plantea bastantes
desafíos. La sección anterior analiza el conteo de pasos de tiempo (número de operaciones),
pero a veces también es necesario calcular el espacio (como la memoria que consume un
algoritmo). Consideras el espacio cuando tu problema es codiciar
CAPÍTULO 2 Consideración del diseño de algoritmos 37
Machine Translated by Google
recursos. Dependiendo del problema, es posible que considere mejor un algoritmo cuando funcione
de manera eficiente con respecto a uno de estos aspectos del consumo de recursos:
" Tiempo de ejecución
» Requisitos de memoria de la computadora
» Uso del disco duro
" El consumo de energía
» Velocidad de transmisión de datos en una red
Algunos de estos aspectos se relacionan con otros de manera inversa, por lo que si, por ejemplo,
desea un tiempo de ejecución más rápido, puede aumentar la memoria o el consumo de energía
para conseguirlo. No sólo puede tener diferentes configuraciones de eficiencia al ejecutar un
algoritmo, sino que también puede cambiar las características del hardware y la implementación del
software para lograr sus objetivos. En términos de hardware, usar una supercomputadora o una
computadora de uso general sí importa, y el software o lenguaje utilizado para escribir el algoritmo
definitivamente cambia las reglas del juego. Además, la cantidad y el tipo de datos que alimenta al
algoritmo podrían dar como resultado mejores o peores mediciones de rendimiento.
Las simulaciones de RAM cuentan el tiempo porque cuando se puede emplear una solución en tantos
entornos y su uso de recursos depende de tantos factores, hay que encontrar una manera de
simplificar las comparaciones para que se conviertan en estándar. De lo contrario, no podrá comparar
posibles alternativas. La solución es, como suele ocurrir con muchos otros problemas, utilizar una
única medida y decir que hay talla única. En este caso, la medida es el tiempo, que se iguala al
número de operaciones, es decir, a la complejidad del algoritmo.
Una simulación de RAM coloca al algoritmo en una situación que es independiente del lenguaje y de
la máquina (es independiente del lenguaje de programación y del tipo de computadora).
Sin embargo, explicar a otros cómo funciona una simulación de RAM requiere un gran esfuerzo. El
análisis de algoritmos propone utilizar la cantidad de operaciones que se obtienen de una simulación
RAM y convertirlas en una función matemática que expresa cómo se comporta su algoritmo en
términos de tiempo, que es una cuantificación de los pasos u operaciones requeridas cuando se
ingresa la cantidad de datos. crece. Por ejemplo, si su algoritmo ordena objetos, puede expresar la
complejidad utilizando una función que informa cuántas operaciones necesita dependiendo de la
cantidad de objetos que recibe.
Trabajar con funciones
Una función en matemáticas es simplemente una forma de asignar algunas entradas a una respuesta.
Expresado de otra manera, una función es una transformación (basada en matemáticas
38 PARTE 1 Primeros pasos
Machine Translated by Google
operaciones) que transforma (mapea) su entrada en una respuesta. Para ciertos valores
de entrada (normalmente indicados por las letras x o n), tiene una respuesta correspondiente
utilizando las matemáticas que definen la función. Por ejemplo, una función como f(n) = 2n
te dice que cuando tu entrada es un número n, tu respuesta es el número n multiplicado
por 2.
Usar el tamaño de la entrada tiene sentido dado que estamos en una época en la que el
tiempo es crítico y la vida de las personas está abarrotada de una cantidad creciente de
datos. Convertir todo en una función matemática es un poco menos intuitivo, pero una
función que describe cómo un algoritmo relaciona su solución con la cantidad de datos
que recibe es algo que se puede analizar sin soporte específico de hardware o software.
También es fácil de comparar con otras soluciones, dado el tamaño de su problema. El
análisis de algoritmos es realmente un concepto alucinante porque reduce una serie
compleja de pasos a una fórmula matemática.
Además, la mayoría de las veces, un análisis de algoritmos ni siquiera está interesado en
definir la función exactamente. Lo que realmente quieres hacer es comparar una función
objetivo con otra función. Estas funciones de comparación aparecen dentro de un conjunto
de funciones propuestas que funcionan mal en comparación con el algoritmo objetivo. De
esta manera, no es necesario introducir números en funciones de mayor o menor
complejidad; en cambio, se trata de funciones simples, prediseñadas y bien conocidas.
Puede sonar tosco, pero es más efectivo y es similar a clasificar el desempeño de los
algoritmos en categorías, en lugar de obtener una medición exacta del desempeño.
El conjunto de funciones generalizadas se llama notación O grande y, en este libro, a
menudo encontrará este pequeño conjunto de funciones (entre paréntesis y precedidas por
una O mayúscula) que se utilizan para representar el rendimiento de los algoritmos. La
Figura 2­1 muestra el análisis de un algoritmo. Un sistema de coordenadas cartesianas
puede representar su función medida por simulación RAM, donde la abscisa (la coordenada
x) es el tamaño de la entrada y la ordenada (la coordenada y) es el número resultante de
operaciones. Puedes ver tres curvas representadas. El tamaño de la entrada importa. Sin
embargo, la calidad también importa (por ejemplo, al ordenar problemas, es más rápido
pedir un insumo que ya está casi ordenado). En consecuencia, el análisis muestra un peor
caso, f1 (n), un caso promedio, f2(n), y un mejor caso, f3(n). Aunque el caso promedio
puede darle una idea general, lo que realmente le importa es el peor de los casos, porque
pueden surgir problemas cuando su algoritmo lucha por encontrar una solución. La función
Big O es aquella que, después de un cierto valor n0 (el umbral para considerar una entrada
grande), siempre resulta en un mayor número de operaciones dada la misma entrada que
la función f1 en el peor de los casos . Por lo tanto, la función Big O es incluso más pesimista
que la que representa su algoritmo, de modo que no importa la calidad de la entrada,
puede estar seguro de que las cosas no pueden empeorar más que eso.
CAPÍTULO 2 Consideración del diseño de algoritmos 39
Machine Translated by Google
FIGURA 2­1:
Complejidad de un
algoritmo en caso
de mejor, promedio,
y lo peor
caso de entrada.
Muchas funciones posibles pueden dar peores resultados, pero la elección de funciones
ofrecidas por la notación O grande que puede utilizar está restringida porque su propósito
es simplificar la medición de la complejidad proponiendo un estándar. En consecuencia,
esta sección contiene sólo las pocas funciones que forman parte de la notación O
grande. La siguiente lista los describe en orden creciente de complejidad:
» Complejidad constante O(1): al mismo tiempo, sin importar cuánta entrada realices.
proporcionar. Al final, es un número constante de operaciones, sin importar la longitud de los
datos de entrada. Este nivel de complejidad es bastante raro en la práctica.
» Complejidad logarítmica O(log n): el número de operaciones crece a un ritmo más lento que
la entrada, lo que hace que el algoritmo sea menos eficiente con entradas pequeñas y
más eficiente con entradas más grandes. Un algoritmo típico de esta clase es la búsqueda
binaria, como se describe en el Capítulo 7 sobre organización y búsqueda de datos.
» Complejidad lineal O(n): Las operaciones crecen con el insumo en una proporción de 1:1. A
El algoritmo típico es la iteración, que es cuando escanea la entrada una vez y aplica una
operación a cada elemento de la misma. El capítulo 5 analiza las iteraciones.
» Complejidad lineal O(n log n): La complejidad es una mezcla entre logaritmos­
Complejidad micro y lineal. Es típico de algunos algoritmos inteligentes que se utilizan para
ordenar datos, como Mergesort, Heapsort y Quicksort. El capítulo 7 le informa sobre la
mayoría de ellos.
40 PARTE 1 Primeros pasos
Machine Translated by Google
» Complejidad cuadrática O(n2): Las operaciones crecen como un cuadrado del número de
entradas. Cuando tienes una iteración dentro de otra iteración (iteraciones anidadas,
en informática), tienes complejidad cuadrática. Por ejemplo, tienes una lista de nombres y,
para encontrar los más similares, comparas cada nombre con todos los demás. Algunos
algoritmos de ordenación menos eficientes presentan tal complejidad: ordenación por
burbujas, ordenación por selección y ordenación por inserción.
Este nivel de complejidad significa que sus algoritmos pueden funcionar durante horas
o incluso días antes de llegar a una solución.
» Complejidad cúbica O(n3): las operaciones crecen incluso más rápido que la
complejidad cuadrática porque ahora hay múltiples iteraciones anidadas. Cuando
un algoritmo tiene este orden de complejidad y necesita procesar una cantidad modesta
de datos (100.000 elementos), su algoritmo puede funcionar durante años. Cuando tiene
una cantidad de operaciones que es una potencia de la entrada, es común referirse al
algoritmo como si se ejecutara en tiempo polinómico.
» Complejidad exponencial O(2n): El algoritmo toma el doble de operaciones anteriores por
cada nuevo elemento agregado. Cuando un algoritmo tiene esta complejidad, incluso los
problemas más pequeños pueden tardar una eternidad. Muchos algoritmos que
realizan búsquedas exhaustivas tienen una complejidad exponencial. Sin embargo, el
ejemplo clásico de este nivel de complejidad es el cálculo de los números de Fibonacci
(que, al ser un algoritmo recursivo, se trata en el Capítulo 5).
» Complejidad factorial O(n!): Una auténtica pesadilla de complejidad por la gran cantidad de
combinaciones posibles entre los elementos. Imagínese: si su entrada es de 100 objetos
y una operación en su computadora requiere de 10 a 6
segundos (una velocidad razonable para cualquier computadora hoy en día), necesitará
alrededor de 10140 años para completar la tarea con éxito (una cantidad de tiempo
imposible ya que la edad del universo se estima en 1014 años). Un famoso problema de
complejidad factorial es el problema del viajante de comercio, en el que un vendedor
tiene que encontrar la ruta más corta para visitar muchas ciudades y regresar a la ciudad
de partida (presentado en el Capítulo 18).
CAPÍTULO 2 Consideración del diseño de algoritmos 41
Machine Translated by Google
Machine Translated by Google
EN ESTE CAPÍTULO
» Usando Python para descubrir cómo
funcionan los algoritmos
» Considerando las diversas distribuciones
de Python
» Realizar una instalación de Python en
linux
» Realizar una instalación de Python en
OSX
» Realizar una instalación de Python en
ventanas
» Obtención e instalación de los conjuntos de datos
utilizados en este libro
Capítulo 3
Usar Python para
trabajar con algoritmos
Descubra las maravillas de los algoritmos. Por ejemplo, aparte de Python, muchos
Tiene muchas
cuando
se trata
de utilizar
asistencia
informática
la gentebuenas
confía opciones
en MATLAB
y muchos
otros
usan R.
De hecho,
algunas para
personas usan los tres y luego comparan los tipos de resultados que obtienen (vea
una de esas comparaciones en https://www.r­bloggers.com/evaluating­optimization­
algorithms ­en­ matlab­python­y­r/). Si solo tuvieras las tres opciones, todavía
necesitarías pensar en ellas por un tiempo y podrías elegir aprender más de un
idioma, pero en realidad tienes más de tres opciones, y este libro no puede
comenzar a cubrirlas todas. . Si profundiza en el mundo de los algoritmos,
descubrirá que puede utilizar todos los lenguajes de programación para escribir
algoritmos y que algunos son apreciados porque reducen todo a operaciones
simples, como la simulación de RAM descrita en el Capítulo 2. Por ejemplo , Donald
Knuth, ganador del Premio Turing, escribió ejemplos en lenguaje ensamblador en
su libro The Art of Computer Programming (Addison­Wesley). El lenguaje ensamblador es un leng
CAPÍTULO 3 Uso de Python para trabajar con algoritmos 43
Machine Translated by Google
que se asemeja al código de máquina, el lenguaje utilizado de forma nativa por las computadoras (pero no
comprensible para la mayoría de los humanos).
Este libro utiliza Python por varias buenas razones, incluido el apoyo de la comunidad del que disfruta y el
hecho de que tiene todas las funciones y es fácil de aprender. Python también es un lenguaje detallado, que
se asemeja a cómo un humano crea instrucciones en lugar de cómo las interpreta una computadora. La
primera sección de este capítulo detalla por qué este libro utiliza Python para los ejemplos, pero también le
explica por qué otras opciones son útiles y por qué es posible que deba considerarlas a medida que avanza
su viaje.
Cuando hablas un lenguaje humano, agregas matices de significado empleando combinaciones de palabras
específicas que otros miembros de tu comunidad entienden. El uso de significados matizados es algo natural
y representa un dialecto. En algunos casos, los dialectos también se forman porque un grupo quiere
demostrar una diferencia con otro grupo. Por ejemplo, Noah Webster escribió y publicó A Grammatical
Institute of the English Language, en parte para eliminar la influencia de la aristocracia británica en el público
estadounidense (ver http://connecticuthistory.org/noah­webster­and­the­dream­ de­un­lenguaje­común/ para
detalles). Del mismo modo, los lenguajes informáticos a menudo vienen con variantes, y los proveedores
agregan intencionalmente extensiones que hacen que su producto sea único para brindar una razón para
comprar su producto en lugar de otra oferta.
La segunda sección del capítulo le presenta varias distribuciones de Python, cada una de las cuales
proporciona un dialecto de Python. Este libro utiliza Analytics Anaconda, que es el producto que debe utilizar
para obtener los mejores resultados de su experiencia de aprendizaje. Usar otro producto, esencialmente
otro dialecto, puede causar problemas a la hora de hacer que los ejemplos funcionen; el mismo tipo de cosas
que sucede a veces cuando alguien que habla inglés británico habla con alguien que habla inglés americano.
Sin embargo, conocer otras distribuciones puede resultar útil cuando necesite obtener acceso a funciones
que Anaconda quizás no proporcione.
Las siguientes tres secciones de este capítulo le ayudarán a instalar Anaconda en su plataforma. Los
ejemplos de este libro se prueban en las plataformas Linux, Mac OS X y Windows. También pueden funcionar
con otras plataformas, pero los ejemplos no se prueban en estas plataformas, por lo que no tienes garantía
de que funcionen. Al instalar Anaconda utilizando los procedimientos que se encuentran en este capítulo,
reduce la posibilidad de realizar una instalación que no funcione con el código de ejemplo. Para utilizar los
ejemplos de este libro, debe instalar Anaconda 4.2.0 con soporte para Python 3.5.
Es posible que otras versiones de Anaconda y Python no funcionen con el código de ejemplo porque, al igual
que con los dialectos del lenguaje humano, podrían malinterpretar las instrucciones que proporciona el
código.
Los algoritmos trabajan con datos de maneras específicas. Para ver un resultado particular de un algoritmo,
necesita datos consistentes. Afortunadamente, la comunidad Python está ocupada.
44 PARTE 1 Primeros pasos
Machine Translated by Google
crear conjuntos de datos que cualquiera pueda utilizar con fines de prueba. Esto permite a la
comunidad repetir resultados que otros obtienen sin tener que descargar conjuntos de datos
personalizados de una fuente desconocida. La última sección de este capítulo le ayuda a obtener
e instalar los conjuntos de datos necesarios para los ejemplos.
Considerando los beneficios de Python
Para trabajar con algoritmos en una computadora, necesita algún medio de comunicación con la
computadora. Si esto fuera Star Trek, probablemente podrías simplemente decirle a la computadora
lo que quieres y ella realizaría la tarea diligentemente por ti. De hecho, Scotty parece bastante
confundido acerca de la falta de una interfaz de voz en Star Trek IV (ver http://www.davidalison.com/
2008/07/keyboard­vs­mouse.html para detalles).
El punto es que aún necesitas usar el mouse y el teclado, junto con un lenguaje especial, para
comunicar tus ideas a la computadora porque la computadora no hará ningún esfuerzo por
comunicarse contigo. Python es uno de varios lenguajes que es especialmente hábil para facilitar
que los no desarrolladores comuniquen ideas a la computadora, pero no es la única opción. Los
siguientes párrafos le ayudarán a comprender por qué este libro utiliza Python y cuáles son sus
otras opciones.
Comprender por qué este libro utiliza Python
Todos los lenguajes informáticos disponibles en la actualidad traducen algoritmos a una forma que
la computadora pueda procesar. De hecho, lenguajes como ALGOL (Lenguaje ALGOrítmico) y
FORTRAN (TRADUCCIÓN DE FÓRMULAS) dejan claro este enfoque. Recuerde la definición de
algoritmo del Capítulo 1 como una secuencia de pasos utilizados para resolver un problema. El
método utilizado para realizar esta traducción difiere según el idioma, y las técnicas utilizadas en
algunos idiomas son bastante arcanas y requieren conocimientos especializados incluso para
intentarlo.
Las computadoras hablan solo un idioma, el código de máquina (los 0 y 1 que una computadora
interpreta para realizar tareas), que es tan increíblemente difícil de hablar para los humanos que
los primeros desarrolladores crearon una enorme variedad de alternativas. Los lenguajes
informáticos existen para facilitar la comunicación humana con las computadoras. En consecuencia,
si te encuentras luchando por hacer que algo funcione, tal vez estés usando el lenguaje
equivocado. Siempre es mejor tener más de un idioma a su alcance para poder realizar
comunicaciones por computadora con facilidad. Python resulta ser uno de los lenguajes que
funciona excepcionalmente bien para las personas que trabajan en disciplinas ajenas al desarrollo
de aplicaciones.
Python es la visión de una sola persona, Guido van Rossum (consulte su página de inicio en https://
gvanrossum.github.io/). Te sorprenderá saber que Python
CAPÍTULO 3 Uso de Python para trabajar con algoritmos 45
Machine Translated by Google
ha existido durante mucho tiempo: Guido comenzó a utilizar el idioma en diciembre de 1989
como reemplazo del idioma ABC. No hay mucha información disponible sobre los objetivos
precisos de Python, pero conserva la capacidad de ABC para crear aplicaciones usando
menos código. Sin embargo, supera con creces la capacidad de ABC para crear aplicaciones
de todo tipo y, a diferencia de ABC, cuenta con cuatro estilos de programación. En definitiva,
Guido tomó el ABC como punto de partida, lo encontró limitado y creó un nuevo lenguaje sin
esas limitaciones. Es un ejemplo de creación de un nuevo lenguaje que realmente es mejor
que su predecesor.
Python ha pasado por varias iteraciones y actualmente tiene dos rutas de desarrollo. La ruta
2.x es compatible con versiones anteriores de Python; la ruta 3.x no lo es. El problema de
compatibilidad influye en cómo se utiliza Python para realizar tareas relacionadas con
algoritmos porque al menos algunos de los paquetes no funcionarán con 3.x. Además,
algunas versiones utilizan licencias diferentes porque Guido trabajó en varias empresas
durante el desarrollo de Python. Puede ver una lista de las versiones y sus respectivas
licencias en https://docs.python.
org/3/license.html. La Python Software Foundation (PSF) posee todas las versiones actuales
de Python, por lo que, a menos que utilice una versión anterior, no necesita preocuparse por
el problema de la licencia.
En realidad, Guido inició Python como un proyecto skunkworks (un proyecto desarrollado por
un grupo de personas pequeño y poco estructurado). El concepto central era crear Python lo
más rápido posible y, al mismo tiempo, crear un lenguaje que fuera flexible, se ejecutara en
cualquier plataforma y ofreciera un potencial significativo de extensión. Python proporciona
todas estas funciones y muchas más. Por supuesto, siempre hay obstáculos en el camino,
como determinar qué parte del sistema subyacente exponer. Puede leer más sobre la filosofía
de diseño de Python en http://python­history.blogspot.com/2009/01/pythons­design­
philosophy.html . La historia de Python en http://python­history.
blogspot.com/2009/01/introduction­and­overview.html También proporciona información útil.
Los objetivos de desarrollo (o diseño) originales de Python no coinciden del todo con lo que
ha sucedido con el lenguaje desde entonces. Originalmente, Guido pretendía que Python
fuera un segundo lenguaje para los desarrolladores que necesitaban crear código único pero
que no podían lograr sus objetivos utilizando un lenguaje de secuencias de comandos. El
público objetivo original de Python era el desarrollador de C. Puede leer sobre estos objetivos
originales en la entrevista en http://www.artima.com/intv/pyscale.html.
Puede encontrar varias aplicaciones escritas en Python hoy en día, por lo que la idea de
usarlo únicamente para secuencias de comandos no se hizo realidad. De hecho, puede
encontrar listados de aplicaciones Python en https://www.python.org/about/apps/ y https://
www.python.org/about/success/.
46 PARTE 1 Primeros pasos
Machine Translated by Google
Naturalmente, con todas estas historias de éxito, la gente está entusiasmada con la idea de
agregar Python. Puede encontrar listas de propuestas de mejora de Python (PEP) en http://
legacy.python.org/dev/peps/. Estos PEP pueden ver la luz o no, pero demuestran que Python es
un lenguaje vivo y en crecimiento que continuará brindando características que los desarrolladores
realmente necesitan para crear excelentes aplicaciones de todo tipo.
Trabajando conMATLAB
Python tiene ventajas sobre muchos otros lenguajes al ofrecer múltiples estilos de codificación,
una flexibilidad fantástica y una gran extensibilidad, pero sigue siendo un lenguaje de
programación. Si sinceramente no quieres utilizar un lenguaje de programación, tienes otras
opciones, como MATLAB (https://www.mathworks.com/products/
matlab/), que se centra más en los algoritmos. MATLAB sigue siendo una especie de lenguaje
de secuencias de comandos y, para realizar tareas importantes con él, aún necesita saber un
poco sobre codificación, pero no tanto como con Python.
Uno de los principales problemas al utilizar MATLAB es el precio que se paga. A diferencia de
Python, MATLAB requiere una inversión monetaria de su parte (consulte https://www.
mathworks.com/pricing­licensing/ para los costos de licencia). De hecho, el entorno es más fácil
de usar, pero como ocurre con la mayoría de las cosas, no hay nada gratis y se debe considerar
el diferencial de costos como parte de la determinación de qué producto usar.
Mucha gente siente curiosidad por MATLAB, es decir, por sus fortalezas y debilidades en
comparación con Python. Este libro no tiene espacio para proporcionar una comparación
completa, pero puede encontrar una excelente descripción general en http://www.pyzo.org/python_vs_
matlab.html. Además, puede llamar paquetes de Python desde MATLAB utilizando las técnicas
que se encuentran en https://www.mathworks.com/help/matlab/call­python­librarys.html . De
hecho, MATLAB también funciona con lo siguiente:
» MEX (https://www.mathworks.com/help/matlab/call­mex­file­functions.html )
» C (https://www.mathworks.com/help/matlab/using­c­shared­library­functions­in­
matlab­.html )
» Java (https://www.mathworks.com/help/matlab/using­java­libraries­
en­matlab.html)
» .NET (https://www.mathworks.com/help/matlab/using­net­libraries­
en­matlab.html)
» COM (https://www.mathworks.com/help/matlab/using­com­objects­
en­matlab.html)
CAPÍTULO 3 Uso de Python para trabajar con algoritmos 47
Machine Translated by Google
Por lo tanto, no necesariamente tiene que elegir entre MATLAB y Python (u otro lenguaje), pero
cuantas más funciones de Python utilice, más fácil será trabajar con Python y omitir MATLAB.
Puede descubrir más sobre MATLAB en MATLAB For Dummies, de Jim Sizemore y John Paul
Mueller (Wiley).
Considerando otros entornos
de prueba de algoritmos
Un tercer competidor importante para el trabajo relacionado con algoritmos es R. El lenguaje de
programación R, como Python, es gratuito. También admite una gran cantidad de paquetes y
ofrece una gran flexibilidad. Sin embargo, algunas de las construcciones de programación son
diferentes y algunas personas encuentran que R es más difícil de usar que Python. La mayoría de
las personas ven a R como el ganador cuando se trata de realizar estadísticas, pero consideran
que la naturaleza de propósito general de Python tiene importantes beneficios (consulte los artículos en https://
www.datacamp.com/community/tutorials/r­or­python­for­data­analysis
y http://www.kdnuggets.com/2015/05/r­vs­python­data­science.html).
El mayor apoyo de la comunidad a Python también es una gran ventaja.
Como se mencionó anteriormente, puede utilizar cualquier lenguaje de programación para realizar
trabajos relacionados con algoritmos, pero la mayoría de los lenguajes tienen un propósito
específico en mente. Por ejemplo, puede realizar tareas relacionadas con algoritmos utilizando un
lenguaje como el lenguaje de consulta estructurado (SQL), pero este lenguaje se centra en la
gestión de datos, por lo que algunas tareas relacionadas con algoritmos pueden volverse
complicadas y difíciles de realizar. Una carencia importante de SQL es la capacidad de trazar
datos con facilidad y realizar algunas de las traducciones y transformaciones que requiere el
trabajo específico de algoritmos. En resumen, debes considerar lo que planeas hacer al elegir un
idioma. Este libro utiliza Python porque realmente es el mejor lenguaje general para realizar las
tareas en cuestión, pero es importante darse cuenta de que es posible que necesite otro lenguaje
en algún momento.
Mirando las distribuciones de Python
Es muy posible que pueda obtener una copia genérica de Python y agregarle todos los paquetes
necesarios para trabajar con algoritmos. El proceso puede ser difícil porque debe asegurarse de
tener todos los paquetes necesarios en las versiones correctas para garantizar el éxito. Además,
debe realizar la configuración requerida para asegurarse de que se pueda acceder a los paquetes
cuando los necesite. Afortunadamente, no es necesario realizar el trabajo requerido porque hay
numerosos productos Python que funcionan bien con algoritmos disponibles para su uso. Estos
productos proporcionan todo lo necesario para comenzar con proyectos relacionados con algoritmos.
48 PARTE 1 Primeros pasos
Machine Translated by Google
Puede utilizar cualquiera de los paquetes mencionados en las siguientes secciones para
trabajar con los ejemplos de este libro. Sin embargo, el código fuente del libro y el código
fuente descargable se basan en Continuum Analytics Anaconda 4.2.0 porque este paquete
en particular funciona en todas las plataformas que admite este libro: Linux, Mac OS X y
Windows. El libro no menciona un paquete específico en los capítulos siguientes, pero las
capturas de pantalla reflejan cómo se ven las cosas cuando se usa Anaconda en Windows.
Es posible que necesite modificar el código para usar otro paquete y las pantallas se verán
diferentes si usa Anaconda en alguna otra plataforma.
Windows 10 presenta algunos problemas de instalación graves cuando se trabaja con Python.
Puedes leer sobre estos temas en mi blog (el de John) en http://blog.john.
muellerbooks.com/2015/10/30/python­and­windows­10/. Dado que muchos lectores de
mis otros libros sobre Python han enviado comentarios diciendo que Windows 10 no
proporciona un buen entorno, no puedo recomendar Windows 10 como plataforma
Python para este libro. Si está trabajando con Windows 10, simplemente tenga en
cuenta que el camino hacia una instalación de Python será difícil.
Obtención de Anaconda analítica
El paquete básico de Anaconda es una descarga gratuita que se obtiene en https://store.
continuum.io/cshop/anaconda/. Simplemente haga clic en Descargar Anaconda para obtener
acceso al producto gratuito. Debe proporcionar una dirección de correo electrónico para
obtener una copia de Anaconda. Después de proporcionar su dirección de correo electrónico,
irá a otra página, donde puede elegir su plataforma y el instalador para esa plataforma.
Anaconda admite las siguientes plataformas:
» Windows de 32 y 64 bits (el instalador puede ofrecerle solo la versión de 64 o 32 bits).
versión, dependiendo de qué versión de Windows detecta)
» Linux de 32 y 64 bits
» MacOS X de 64 bits
Debido a que el soporte de paquetes para Python 3.5 ha mejorado que las versiones 3.x
anteriores, verá que tanto Python 3.x como 2.x son igualmente compatibles en el sitio de
Analytics. Este libro utiliza Python 3.5 porque el soporte del paquete ahora es lo suficientemente
sustancial y estable para soportar todos los ejemplos de programación, y porque Python 3.x
Representa la dirección futura de Python.
Puede obtener Anaconda con versiones anteriores de Python. Si desea utilizar una versión
anterior de Python, haga clic en el enlace del archivo del instalador cerca de la parte inferior
de la página. Debe utilizar una versión anterior de Python sólo cuando tenga una necesidad
urgente de hacerlo.
CAPÍTULO 3 Uso de Python para trabajar con algoritmos 49
Machine Translated by Google
El instalador de Miniconda puede potencialmente ahorrar tiempo al limitar la cantidad de
funciones que instala. Sin embargo, tratar de determinar con precisión qué paquetes necesita
es un proceso propenso a errores y que requiere mucho tiempo. En general, desea realizar
una instalación completa para asegurarse de tener todo lo necesario para sus proyectos.
Incluso una instalación completa no requiere mucho tiempo ni esfuerzo para descargarla e
instalarla en la mayoría de los sistemas.
El producto gratuito es todo lo que necesita para este libro. Sin embargo, cuando consulta el
sitio, verá que hay muchos otros productos complementarios disponibles. Estos productos
pueden ayudarle a crear aplicaciones sólidas. Por ejemplo, cuando agrega Accelerate a la
mezcla, obtiene la capacidad de realizar operaciones multinúcleo y habilitadas para GPU. El
uso de estos productos complementarios está fuera del alcance de este libro, pero el sitio de
Anaconda proporciona detalles sobre su uso.
Considerando Enthink Canopy Express
Enthink Canopy Express es un producto gratuito para producir aplicaciones técnicas y
científicas utilizando Python. Puedes obtenerlo en https://www.enthink.
com/canopy­express/. Haga clic en Descargar gratis en la página principal para ver una
lista de las versiones que puede descargar. Sólo Canopy Express es gratuito; el producto
Canopy completo tiene un costo. Sin embargo, puede utilizar Canopy Express para
trabajar con los ejemplos de este libro. Canopy Express admite las siguientes plataformas:
» Windows de 32 y 64 bits
» Linux de 32 y 64 bits
» MacOS X de 32 y 64 bits
Elija la plataforma y la versión que desea descargar. Al hacer clic en Descargar Canopy Express,
verá un formulario opcional para proporcionar información sobre usted. La descarga se inicia
automáticamente, incluso si no proporciona información personal a la empresa.
Una de las ventajas de Canopy Express es que Enthinkt participa activamente en brindar apoyo
tanto a estudiantes como a profesores. Las personas también pueden tomar clases, incluidas
clases en línea, que enseñan el uso de Canopy Express de varias maneras (consulte https://
training.enthink.com/courses).
Considerando Pythonxy
El entorno de desarrollo integrado (IDE) de Pythonxy es un proyecto comunitario alojado
en Google en http://python­xy.github.io/. Es un producto exclusivo de Windows, por lo
que no puede usarlo fácilmente para necesidades multiplataforma. (De hecho, solo admite
50 PARTE 1 Primeros pasos
Machine Translated by Google
Windows Vista, Windows 7 y Windows 8). Sin embargo, viene con un conjunto completo de
paquetes y puede usarlo fácilmente para este libro si lo desea.
Debido a que Pythonxy usa la Licencia Pública General GNU (GPL) v3 (consulte http://www.
gnu.org/licenses/gpl.html), no tiene que preocuparse por complementos, capacitación ni otras
funciones pagas. Nadie vendrá a llamar a tu puerta con la esperanza de venderte algo. Además,
tienes acceso a todo el código fuente de Pythonxy, por lo que puedes realizar modificaciones si lo
deseas.
Considerando WinPython
El nombre le indica que WinPython es un producto exclusivo de Windows que puede
encontrar en http://winpython.sourceforge.net/. Este producto es en realidad un derivado de
pythonxy y no pretende reemplazarlo. Todo lo contrario: WinPython es simplemente una
forma más flexible de trabajar con Pythonxy. Puede leer sobre la motivación para crear
WinPython en http://sourceforge.net/p/winpython/wiki/Roadmap/.
La conclusión de este producto es que obtiene flexibilidad a costa de la amabilidad y una
pequeña integración de la plataforma. Sin embargo, para los desarrolladores que necesitan
mantener varias versiones de un IDE, WinPython puede marcar una diferencia significativa.
Cuando utilice WinPython con este libro, asegúrese de prestar especial atención a los
problemas de configuración o descubrirá que incluso el código descargable tiene pocas
posibilidades de funcionar.
Instalación de Python en Linux
Utiliza la línea de comando para instalar Anaconda en Linux; no se le ofrece ninguna opción
de instalación gráfica. Antes de poder realizar la instalación, debe descargar una copia del
software de Linux desde el sitio de Continuum Analytics. Puede encontrar la información de
descarga requerida en la sección "Obtención de Analytics Anaconda", anteriormente en este
capítulo. El siguiente procedimiento debería funcionar bien en cualquier sistema Linux, ya
sea que utilice la versión de Anaconda de 32 o 64 bits:
1. Abra una copia de Terminal.
Aparece la ventana Terminal.
2. Cambie los directorios a la copia descargada de Anaconda en su sistema.
El nombre de este archivo varía, pero normalmente aparece como Anaconda3­4.2.0­
Linux­x86.sh para sistemas de 32 bits y Anaconda3­4.2.0­Linux­x86_64.sh
para sistemas de 64 bits. El número de versión está incrustado como parte del nombre del archivo.
En este caso, el nombre del archivo se refiere a la versión 4.2.0, que es la versión utilizada para
CAPÍTULO 3 Uso de Python para trabajar con algoritmos 51
Machine Translated by Google
este libro. Si utiliza alguna otra versión, puede experimentar problemas con el código fuente
y necesitar realizar ajustes al trabajar con él.
3. Escriba bash Anaconda3­4.2.0­Linux­x86.sh (para la versión de 32 bits) o bash
Anaconda3­4.2.0­Linux­x86_64.sh (para la versión de 64 bits) y presione Enter.
Se inicia un asistente de instalación que le pide que acepte los términos de la licencia
para usar Anaconda.
4. Lea el acuerdo de licencia y acepte los términos utilizando el método
requerido para su versión de Linux.
El asistente le pide que proporcione una ubicación de instalación para Anaconda. El libro
supone que utiliza la ubicación predeterminada de ~/anaconda. Si elige otra ubicación, es
posible que tenga que modificar algunos procedimientos más adelante en el libro para que
funcionen con su configuración.
5. Proporcione una ubicación de instalación (si es necesario) y presione Entrar (o haga clic en
Próximo).
Comienza el proceso de extracción de la solicitud. Una vez completada la extracción, verá un
mensaje de finalización.
6. Agregue la ruta de instalación a su declaración PATH usando el método
requerido para su versión de Linux.
Estás listo para comenzar a usar Anaconda.
Instalación de Python en MacOS
La instalación de Mac OS X viene en una sola forma: 64 bits. Antes de poder realizar la
instalación, debe descargar una copia del software para Mac desde el sitio de Continuum
Analytics. Puede encontrar la información de descarga requerida en la sección "Obtención de
Analytics Anaconda", anteriormente en este capítulo.
Los archivos de instalación vienen en dos formas. El primero depende de un instalador gráfico;
el segundo se basa en la línea de comando. La versión de línea de comandos funciona de
manera muy similar a la versión de Linux descrita en la sección "Instalación de Python en
Linux" de este capítulo. Los siguientes pasos le ayudarán a instalar Anaconda de 64 bits en
un sistema Mac mediante el instalador gráfico:
1. Localice la copia descargada de Anaconda en su sistema.
El nombre de este archivo varía, pero normalmente aparece como Anaconda3­4.2.0­
MacOSX­x86_64.pkg. El número de versión está incrustado como parte del nombre
del archivo. En este caso, el nombre del archivo se refiere a la versión 4.2.0, que es la
52 PARTE 1 Primeros pasos
Machine Translated by Google
versión utilizada para este libro. Si utiliza alguna otra versión, puede experimentar problemas con el
código fuente y necesitar realizar ajustes al trabajar con él.
2. Haga doble clic en el archivo de instalación.
Aparece un cuadro de diálogo de introducción.
3. Haga clic en Continuar.
El asistente le preguntará si desea revisar los materiales Léame. Puede leer estos materiales más
tarde. Por ahora, puedes omitir la información con seguridad.
4. Haga clic en Continuar.
El asistente muestra un acuerdo de licencia. Asegúrese de leer el acuerdo de licencia para
conocer los términos de uso.
5. Haga clic en Acepto si acepta el acuerdo de licencia.
El asistente le pedirá que proporcione un destino para la instalación. El destino controla si la
instalación es para un usuario individual o para un grupo.
Es posible que vea un mensaje de error que indique que no puede instalar Anaconda en el sistema.
El mensaje de error se produce debido a un error en el instalador y no tiene nada que ver con su
sistema. Para deshacerse del mensaje de error, elija la opción Instalar solo para mí. No puede
instalar Anaconda para un grupo de usuarios en un sistema Mac.
6. Haga clic en Continuar.
El instalador muestra un cuadro de diálogo que contiene opciones para cambiar el tipo de instalación.
Haga clic en Cambiar ubicación de instalación si desea modificar dónde está instalado Anaconda en
su sistema. (El libro supone que utiliza la ruta predeterminada ~/anaconda). Haga clic en Personalizar
si desea modificar el funcionamiento del instalador.
Por ejemplo, puede optar por no agregar Anaconda a su declaración PATH .
Sin embargo, el libro supone que ha elegido las opciones de instalación predeterminadas y no
existe ninguna buena razón para cambiarlas a menos que tenga otra copia de Python 3.5 instalada
en otro lugar.
7. Haga clic en Instalar.
Comienza la instalación. Una barra de progreso le indica cómo avanza el proceso de instalación.
Cuando se complete la instalación, verá un cuadro de diálogo de finalización.
8. Haga clic en Continuar.
Estás listo para comenzar a usar Anaconda.
CAPÍTULO 3 Uso de Python para trabajar con algoritmos 53
Machine Translated by Google
Instalación de Python en Windows
Anaconda viene con una aplicación de instalación gráfica para Windows, por lo que obtener
una buena instalación significa utilizar un asistente, como lo haría con cualquier otra
instalación. Por supuesto, necesita una copia del archivo de instalación antes de comenzar
y puede encontrar la información de descarga requerida en la sección "Obtención de
Analytics Anaconda", anteriormente en este capítulo. El siguiente procedimiento debería
funcionar bien en cualquier sistema Windows, ya sea que utilice la versión de Anaconda de 32 o 64 bits:
1. Localice la copia descargada de Anaconda en su sistema.
El nombre de este archivo varía, pero normalmente aparece como Anaconda3­4.2.0­
Windows­x86.exe para sistemas de 32 bits y Anaconda3­4.2.0­Windows­x86_64.
exe para sistemas de 64 bits. El número de versión está incrustado como parte del nombre del archivo.
En este caso, el nombre del archivo se refiere a la versión 4.2.0, que es la versión utilizada para este
libro. Si utiliza alguna otra versión, puede experimentar problemas con el código fuente y necesitar
realizar ajustes al trabajar con él.
2. Haga doble clic en el archivo de instalación.
(Es posible que vea un cuadro de diálogo Abrir archivo ­ Advertencia de seguridad que le pregunta
si desea ejecutar este archivo. Haga clic en Ejecutar si ve este cuadro de diálogo emergente). Verá
un cuadro de diálogo Configuración de Anaconda 4.2.0 similar al que se muestra en Figura 3­1.
El cuadro de diálogo exacto que ve depende de la versión del programa de instalación de Anaconda
que descargue. Si tienes un sistema operativo de 64 bits, siempre es mejor utilizar la versión de
64 bits de Anaconda para obtener el mejor rendimiento posible. Este primer cuadro de diálogo le
indica cuándo tiene la versión de 64 bits del producto.
3. Haga clic en Siguiente.
El asistente muestra un acuerdo de licencia. Asegúrese de leer el acuerdo de licencia para
conocer los términos de uso.
4. Haga clic en Acepto si acepta el acuerdo de licencia.
Se le preguntará qué tipo de instalación realizar, como se muestra en la Figura 3­2.
En la mayoría de los casos, desea instalar el producto usted mismo. La excepción es si varias
personas utilizan su sistema y todas necesitan acceso a Anaconda.
5. Elija uno de los tipos de instalación y luego haga clic en Siguiente.
El asistente pregunta dónde instalar Anaconda en el disco, como se muestra en la Figura 3­3. El libro
supone que utiliza la ubicación predeterminada. Si elige otra ubicación, es posible que tenga
que modificar algunos procedimientos más adelante en el libro para que funcionen con su
configuración.
54 PARTE 1 Primeros pasos
Machine Translated by Google
FIGURA 3­1:
El proceso
de configuración
comienza
informándole si
tiene la versión de 64 bits.
FIGURA 3­2:
Dígale al
asistente
cómo instalar
Anaconda en su sistema.
CAPÍTULO 3 Uso de Python para trabajar con algoritmos 55
Machine Translated by Google
FIGURA 3­3:
Especifique
una ubicación
de instalación.
6. Elija una ubicación de instalación (si es necesario) y luego haga clic en Siguiente.
Verá las Opciones de instalación avanzadas, que se muestran en la Figura 3­4. Estas opciones
están seleccionadas de forma predeterminada y, en la mayoría de los casos, no existe ninguna
buena razón para cambiarlas. Es posible que deba cambiarlos si Anaconda no proporciona su
configuración predeterminada de Python 3.5 (o Python 2.7). Sin embargo, el libro asume que ha
configurado Anaconda usando las opciones predeterminadas.
7. Cambie las opciones de instalación avanzadas (si es necesario) y luego haga clic en
Instalar.
Verá un cuadro de diálogo de instalación con una barra de progreso. El proceso de instalación
puede tardar unos minutos, así que tómate una taza de café y lee los cómics un rato. Cuando
finaliza el proceso de instalación, verá un botón Siguiente habilitado.
8. Haga clic en Siguiente.
El asistente le indica que la instalación está completa.
9. Haga clic en Finalizar.
Estás listo para comenzar a usar Anaconda.
56 PARTE 1 Primeros pasos
Machine Translated by Google
FIGURA 3­4:
Configure las
opciones
avanzadas
de instalación.
UNAS PALABRA SOBRE LAS CAPTURAS DE PANTALLA
A medida que avanza en el libro, utiliza un IDE de su elección para abrir los archivos de
Python y Jupyter Notebook que contienen el código fuente del libro. Cada captura de pantalla
que contiene información específica de IDE se basa en Anaconda porque Anaconda se ejecuta
en las tres plataformas admitidas por el libro. El uso de Anaconda no implica que sea el mejor
IDE o que los autores estén haciendo algún tipo de recomendación al respecto; Anaconda
simplemente funciona bien como producto de demostración.
Cuando trabaja con Anaconda, el nombre del entorno gráfico (GUI), Jupyter Notebook, es
exactamente el mismo en las tres plataformas y ni siquiera verá ninguna diferencia significativa
en la presentación. (Jupyter Notebook es una evolución de IPython, por lo que es posible que
vea recursos en línea que hacen referencia a IPython Notebook). Las diferencias que ve son
menores y debe ignorarlas a medida que avanza en el libro. Teniendo esto en cuenta, el libro
se basa en gran medida en capturas de pantalla de Windows 7. Cuando trabaje en Linux,
Mac OS X u otra plataforma de versión Windows, debería esperar ver algunas diferencias
en la presentación, pero estas diferencias no deberían reducir su capacidad para trabajar con
los ejemplos.
CAPÍTULO 3 Uso de Python para trabajar con algoritmos 57
Machine Translated by Google
Descarga de los conjuntos de
datos y el código de ejemplo
Este libro trata sobre el uso de Python para realizar tareas de aprendizaje automático. Por supuesto,
puede dedicar todo su tiempo a crear el código de ejemplo desde cero, depurarlo y solo entonces
descubrir cómo se relaciona con el aprendizaje automático, o puede tomar el camino más fácil y
descargar el código preescrito del sitio Dummies (consulte la Introducción). (Consulte este libro para
obtener más detalles) para que pueda ponerse manos a la obra. Del mismo modo, crear conjuntos de
datos lo suficientemente grandes para fines de aprendizaje de algoritmos llevaría bastante tiempo.
Afortunadamente, puede acceder con bastante facilidad a conjuntos de datos estandarizados y creados
previamente utilizando las funciones proporcionadas en algunos de los paquetes de ciencia de datos
(que también funcionan bien para todo tipo de propósitos, incluido aprender a trabajar con algoritmos).
Las siguientes secciones lo ayudarán a descargar y utilizar el código de ejemplo y los conjuntos de
datos para que pueda ahorrar tiempo y comenzar a trabajar con tareas específicas de algoritmos.
Usando el cuaderno Jupyter
Para facilitar el trabajo con el código relativamente complejo de este libro, utilice Jupyter Notebook.
Esta interfaz le permite crear fácilmente archivos de cuaderno de Python que pueden contener
cualquier cantidad de ejemplos, cada uno de los cuales puede ejecutarse individualmente. El programa
se ejecuta en su navegador, por lo que no importa qué plataforma utilice para el desarrollo; Mientras
tenga un navegador, deberías estar bien.
Iniciar el cuaderno Jupyter
La mayoría de las plataformas proporcionan un ícono para acceder a Jupyter Notebook. Simplemente
haga clic en este icono para acceder a Jupyter Notebook. Por ejemplo, en un sistema Windows, elija
Inicio
Todos los programas
Anaconda 3
Jupyter Notebook. La Figura 3­5 muestra cómo se ve
la interfaz cuando se ve en un navegador Firefox. La apariencia precisa en su sistema depende del
navegador que utilice y del tipo de plataforma que haya instalado.
Si tiene una plataforma que no ofrece fácil acceso a través de un ícono, puede seguir estos pasos
para acceder a Jupyter Notebook:
1. Abra un símbolo del sistema o una ventana de terminal en su sistema.
La ventana se abre para que pueda escribir comandos.
2. Cambie los directorios al directorio \Anaconda3\Scripts en su máquina.
La mayoría de los sistemas le permiten utilizar el comando CD para esta tarea.
3. Escriba python jupyter­notebook­script.py y presione Entrar.
La página de Jupyter Notebook se abre en su navegador.
58 PARTE 1 Primeros pasos
Machine Translated by Google
FIGURA 3­5:
Jupyter Notebook
proporciona un
método sencillo para
crear ejemplos de
aprendizaje automático.
Detener el servidor Jupyter Notebook
No importa cómo inicie Jupyter Notebook (o simplemente Notebook, como aparece en el resto del
libro), el sistema generalmente abre un símbolo del sistema o una ventana de terminal para alojar
Jupyter Notebook. Esta ventana contiene un servidor que hace que la aplicación funcione. Después
de cerrar la ventana del navegador cuando se completa una sesión, seleccione la ventana del
servidor y presione Ctrl+C o Ctrl+Interrupción para detener el servidor.
Definiendo el repositorio de código
El código que cree y utilice en este libro residirá en un repositorio en su disco duro. Piense en un
repositorio como una especie de archivador donde guarda su código.
Notebook abre un cajón, saca la carpeta y le muestra el código. Puede modificarlo, ejecutar ejemplos
individuales dentro de la carpeta, agregar nuevos ejemplos y simplemente interactuar con su código
de manera natural. Las siguientes secciones le ayudarán a empezar a utilizar Notebook para que
pueda ver cómo funciona todo este concepto de repositorio.
Definiendo la carpeta del libro
Vale la pena organizar sus archivos para poder acceder a ellos más fácilmente más adelante. Este
libro guarda sus archivos en la carpeta A4D (Algoritmos para principiantes) . Siga estos pasos dentro
de Notebook para crear una nueva carpeta.
CAPÍTULO 3 Uso de Python para trabajar con algoritmos 59
Machine Translated by Google
1. Elija Nuevo
Carpeta.
Notebook crea una nueva carpeta llamada Carpeta sin título, como se muestra en la Figura 3­6.
El archivo aparece en orden alfanumérico, por lo que es posible que no lo veas inicialmente. Debe
desplazarse hacia abajo hasta la ubicación correcta.
FIGURA 3­6:
Aparecen
nuevas carpetas
con el nombre de
Carpeta sin título.
2. Seleccione la casilla junto a la entrada Carpeta sin título.
3. Haga clic en Cambiar nombre en la parte superior de la página.
Verá un cuadro de diálogo Cambiar nombre de directorio como el que se muestra en la Figura 3­7.
FIGURA 3­7:
Cambie el
nombre de la carpeta
para recordar los
tipos de entradas que
contiene.
4. Escriba A4D y haga clic en Aceptar.
Notebook cambia el nombre de la carpeta por usted.
5. Haga clic en la nueva entrada A4D en la lista.
Notebook cambia la ubicación a la carpeta A4D en la que realiza tareas relacionadas con los ejercicios
de este libro.
60 PARTE 1 Primeros pasos
Machine Translated by Google
Creando un nuevo cuaderno
Cada cuaderno nuevo es como una carpeta de archivos. Puede colocar ejemplos individuales
dentro de la carpeta de archivos, tal como lo haría con hojas de papel en una carpeta de
archivos física. Cada ejemplo aparece en una celda. También puedes poner otro tipo de cosas
en la carpeta de archivos, pero verás cómo funcionan a medida que avanza el libro. Siga estos
pasos para crear un nuevo cuaderno:
1. Haga clic en Nuevo
Python (predeterminado).
Se abre una nueva pestaña en el navegador con el nuevo cuaderno, como se muestra en la Figura 3­8.
Observe que el cuaderno contiene una celda y que Notebook ha resaltado la celda para que pueda comenzar
a escribir código en ella. El título del cuaderno es Sin título en este momento. Ese no es un título
particularmente útil, por lo que debes cambiarlo.
FIGURA 3­8:
Un cuaderno
contiene células
que usas para
código de retención.
2. Haga clic en Sin título en la página.
Notebook le pregunta qué desea usar como nombre nuevo, como se muestra en la Figura 3­9.
3. Escriba A4D; 03; Muestra y presione Enter.
El nuevo nombre le indica que se trata de un archivo de Algoritmos para principiantes, Capítulo 3,
Sample.ipynb. El uso de esta convención de nomenclatura le permite diferenciar fácilmente estos archivos
de otros archivos en su repositorio.
CAPÍTULO 3 Uso de Python para trabajar con algoritmos 61
Machine Translated by Google
FIGURA 3­9:
Proporcionar un nuevo
nombre para tu
computadora portátil.
Por supuesto, el cuaderno de muestra no contiene nada todavía. Coloque el cursor en la
celda, escriba print('¡Python es realmente genial!') y luego haga clic en el botón Ejecutar (el
botón con la flecha que apunta hacia la derecha en la barra de herramientas). Verá el resultado
que se muestra en la Figura 3­10. La salida es parte de la misma celda que el código. (El
código reside en un cuadro cuadrado y la salida reside fuera de ese cuadro cuadrado, pero
ambos están dentro de la celda). Sin embargo, Notebook separa visualmente la salida del
código para que pueda distinguirlos. Notebook crea automáticamente una nueva celda para usted.
FIGURA 3­10:
Notebook utiliza celdas
para almacenar su
código.
62 PARTE 1 Primeros pasos
Machine Translated by Google
Cuando termine de trabajar con una computadora portátil, es importante apagarla. Para cerrar un
cuaderno, elija Archivo
Cerrar y detener. Regresará a la página de inicio, donde podrá ver que
el cuaderno que acaba de crear se agrega a la lista, como se muestra en la Figura 3­11.
FIGURA 3­11:
Todos los
cuadernos
que cree
aparecen en la lista del repositorio.
Exportar un cuaderno
Crear cuadernos y guardarlos para ti no es muy divertido. En algún momento, querrás compartirlos
con otras personas. Para realizar esta tarea, debe exportar su cuaderno desde el repositorio a
un archivo. Luego puede enviar el archivo a otra persona, quien lo importará a su repositorio.
La sección anterior muestra cómo crear un cuaderno llamado A4D; 03; Muestra. Puede abrir este
cuaderno haciendo clic en su entrada en la lista de repositorios. El archivo se vuelve a abrir para
que pueda ver su código nuevamente. Para exportar este código, elija Archivo
como
Descargar
Notebook (.ipynb). Lo que vea a continuación depende de su navegador, pero
generalmente verá algún tipo de cuadro de diálogo para guardar el cuaderno como un archivo.
Utilice el mismo método para guardar el archivo IPython Notebook que utiliza para cualquier otro
archivo que guarde con su navegador.
Quitar un cuaderno
A veces los cuadernos quedan obsoletos o simplemente ya no es necesario trabajar con ellos.
En lugar de permitir que su repositorio se atasque con archivos que no necesita, puede eliminar
estos cuadernos no deseados de la lista. Utilice estos pasos para eliminar el archivo:
1. Seleccione la casilla junto al A4D; 03; Entrada de muestra.ipynb.
2. Haga clic en el ícono de la papelera (Eliminar) en la parte superior de la página.
Verá un mensaje de advertencia Eliminar libreta como el que se muestra en la Figura 3­12.
CAPÍTULO 3 Uso de Python para trabajar con algoritmos 63
Machine Translated by Google
FIGURA 3­12:
Notebook le
advierte
antes de
eliminar cualquier archivo del
repositorio.
3. Haga clic en Eliminar.
El archivo se elimina de la lista.
Importar un cuaderno
Para utilizar el código fuente de este libro, debe importar los archivos descargados a
su repositorio. El código fuente viene en un archivo que se extrae a una ubicación en
su disco duro. El archivo contiene una lista de archivos .ipynb (IPython Note­book)
que contienen el código fuente de este libro (consulte la Introducción para obtener
detalles sobre cómo descargar el código fuente). Los siguientes pasos le indican cómo
importar estos archivos a su repositorio:
1. Haga clic en Cargar en la parte superior de la página.
Lo que ve depende de su navegador. En la mayoría de los casos, verá algún tipo de cuadro
de diálogo Carga de archivos que brinda acceso a los archivos en su disco duro.
2. Navegue hasta el directorio que contiene los archivos que desea importar.
en el cuaderno.
3. Resalte uno o más archivos para importar y haga clic en Abrir (u otro,
similar) para comenzar el proceso de carga.
Verá el archivo agregado a una lista de carga, como se muestra en la Figura 3­13. El archivo
aún no forma parte del repositorio; simplemente lo seleccionó para cargarlo.
64 PARTE 1 Primeros pasos
Machine Translated by Google
FIGURA 3­13:
Los archivos que
desea agregar
al repositorio
aparecen como
parte de una
lista de carga que consta de uno
o más
nombres de archivos.
Cuando exporta un archivo, Notebook convierte los caracteres especiales a un formato que
su sistema aceptará con mayor facilidad. La figura 3­13 muestra esta conversión en acción. El
punto y coma aparecen como %3B y los espacios aparecen como + (signo más). Debes cambiar
estos caracteres a su formato Notebook para ver el título como lo esperas.
4. Haga clic en Cargar.
Notebook coloca el archivo en el repositorio para que pueda comenzar a usarlo.
Comprender los conjuntos de datos
utilizados en este libro
Este libro utiliza varios conjuntos de datos, todos los cuales aparecen en el paquete scikit­learn.
Estos conjuntos de datos demuestran varias formas en las que puede interactuar con los datos y
los utiliza en los ejemplos para realizar una variedad de tareas. La siguiente lista proporciona una
descripción general rápida de la función utilizada para importar cada uno de los conjuntos de
datos a su código Python:
» load_boston(): Análisis de regresión con el conjunto de datos de precios de la vivienda de Boston
» load_iris(): Clasificación con el conjunto de datos del iris
» load_diabetes(): Regresión con el conjunto de datos de diabetes
» load_digits([n_class]): Clasificación con el conjunto de datos de dígitos
» fetch_20newsgroups(subset='train'): datos de 20 grupos de noticias
» fetch_olivetti_faces():Olivetti se enfrenta al conjunto de datos de AT&T
CAPÍTULO 3 Uso de Python para trabajar con algoritmos 65
Machine Translated by Google
La técnica para cargar cada uno de estos conjuntos de datos es la misma en todos los ejemplos.
El siguiente ejemplo muestra cómo cargar el conjunto de datos de precios de viviendas de Boston.
Puedes encontrar el código en el A4D; 03; Cuaderno Dataset Load.ipynb .
desde sklearn.datasets importar load_boston
Boston = cargar_boston()
imprimir (Boston.data.shape)
(506, 13)
Para ver cómo funciona el código, haga clic en Ejecutar celda. El resultado de la llamada print()
es (506, 13). Puede ver el resultado que se muestra en la Figura 3­14.
FIGURA 3­14:
El objeto Boston
contiene el
conjunto de datos cargado.
66 PARTE 1 Primeros pasos
Machine Translated by Google
EN ESTE CAPÍTULO
» Realización de cálculos numéricos y lógicos.
tareas
» Trabajar con cuerdas
» Realizar tareas con fechas
» Código de empaquetado mediante el uso de funciones
» Tomar decisiones y repetir pasos
» Gestión de datos en la memoria
» Leer datos en objetos de almacenamiento
» Encontrar datos más rápido mediante el
uso de diccionarios
Capítulo 4
Introduciendo Python para
Programación de algoritmos
Una receta
unadeespecie
algoritmo
porque
te ayudaPuedes
a cocinar
unaes
serie
pasos (ydeasí
deshacerte
del hambre).
idearcomida
muchossabrosa usando
Formas de crear una secuencia de pasos que resuelven un problema. Abundan los
procedimientos de toda variedad y descripción, todos los cuales describen una secuencia de
pasos utilizados para resolver un problema. No todas las secuencias de pasos son concretas.
Las notaciones matemáticas presentan una serie de pasos para resolver un problema numérico,
pero muchas personas las ven como símbolos de formas extrañas en un lenguaje arcano que
pocos pueden entender. Un lenguaje informático puede convertir el lenguaje arcano en una forma
concreta de declaraciones similares al inglés que resuelven el problema de una manera que
funcione para la mayoría de los humanos.
El capítulo anterior de este libro, el Capítulo 3, le ayuda a instalar una copia de Python para
trabajar con los ejemplos de este libro. Utiliza Python a lo largo del libro para resolver problemas
numéricos utilizando algoritmos que también puede expresar en
CAPÍTULO 4 Introducción a Python para la programación de algoritmos 67
Machine Translated by Google
notación matemática. La razón por la que este libro utiliza un lenguaje de programación es
para convertir esos símbolos abstractos de formas extrañas en algo que la mayoría de la gente
pueda entender y utilizar para resolver problemas del mundo real.
Antes de poder utilizar Python para realizar tareas con algoritmos, necesita al menos un
conocimiento básico de cómo funciona Python. Este capítulo no está diseñado para convertirlo
en un experto en Python. Sin embargo, le proporciona suficiente información para entender el
código de ejemplo con el comentario proporcionado. Las distintas secciones le ayudarán a
comprender cómo Python realiza tareas de forma concreta. Por ejemplo, necesita saber
cómo funciona Python con varios tipos de datos para poder determinar qué hace el código de
ejemplo con esos datos. Encontrará los conceptos básicos para trabajar con datos numéricos,
lógicos, de cadenas y de fechas en las tres primeras secciones.
Imagine un libro de cocina, o cualquier libro, que proporcione pasos para realizar cada tarea
que el libro le indica cómo realizar como una narrativa larga sin interrupciones. Tratar de
encontrar una receta específica (u otro procedimiento) resultaría imposible y el libro sería inútil.
De hecho, nadie escribiría un libro así. La cuarta sección del capítulo trata sobre funciones que
son similares a las recetas individuales de un libro de cocina. Puede combinar funciones para
crear un programa completo, de la misma manera que combinaría recetas para crear una cena
completa.
Las siguientes cuatro secciones analizan varias formas de administrar datos, lo que significa
leerlos, escribirlos, modificarlos y borrarlos según sea necesario. También necesita saber
cómo tomar decisiones y qué hacer cuando necesite realizar el mismo conjunto de pasos más
de una vez. Los datos son un recurso, al igual que la harina, el azúcar y otros ingredientes son
recursos que se utilizan cuando se trabaja con una receta. Los diferentes tipos de datos
requieren diferentes técnicas para convertirlos en una aplicación que resuelva el problema
propuesto por un algoritmo. Estas secciones le informan sobre las diversas formas de manipular
datos y trabajar con ellos para resolver problemas.
Trabajar con números y lógica
Interactuar con algoritmos implica trabajar con datos de diversos tipos, pero gran parte del
trabajo involucra números. Además, utiliza valores lógicos para tomar decisiones sobre los
datos que utiliza. Por ejemplo, es posible que necesite saber si dos valores son iguales o si un
valor es mayor que otro. Python admite estos tipos de números y valores lógicos:
» Cualquier número entero es un número entero. Por ejemplo, el valor 1 es un número entero, por lo
que es un número entero. Por otra parte, 1.0 no es un número entero; tiene una parte decimal, por
lo que no es un número entero. Los números enteros están representados por el tipo de datos
int . En la mayoría de las plataformas, puede almacenar números entre –9,223,372,036,854,775,808
68 PARTE 1 Primeros pasos
Machine Translated by Google
y 9.223.372.036.854.775.807 dentro de un int (que es el valor máximo que cabe en una
variable de 64 bits).
» Cualquier número que incluya una porción decimal es un valor de punto flotante . Por ejemplo,
1.0 tiene una parte decimal, por lo que es un valor de punto flotante. Mucha gente se
confunde entre los números enteros y los números de punto flotante, pero la diferencia es
fácil de recordar. Si ve un decimal en el número, es un valor de punto flotante. Python
almacena valores de punto flotante en el tipo de datos flotante . El valor máximo que puede
contener una variable de coma flotante es ±1.7976931348623157 × 10308
y el valor mínimo que puede contener una variable de punto flotante es
±2,2250738585072014 × 10–308 en la mayoría de las plataformas.
» Un número complejo consta de un número real y un número imaginario que están
emparejados. En caso de que te hayas olvidado por completo de los números
complejos, puedes leer sobre ellos en http://www.mathsisfun.com/
números/números­complejos.html. La parte imaginaria de un número complejo
siempre aparece seguida de una j . Entonces, si deseas crear un número
complejo con 3 como parte real y 4 como parte imaginaria, realiza una tarea
como esta: myComplex = 3 + 4j.
» Los argumentos lógicos requieren valores booleanos, que llevan el nombre de George
Bool. Cuando utiliza un valor booleano en Python, confía en el tipo booleano .
Una variable de este tipo sólo puede contener dos valores: Verdadero o Falso. Puede
asignar un valor utilizando las palabras clave Verdadero o Falso , o puede crear una
expresión que defina una idea lógica que equivalga a verdadero o falso. Por
ejemplo, podría decir myBool = 1 > 2, lo que equivaldría a False porque 1
definitivamente no es mayor que 2.
Ahora que ya conoces los conceptos básicos, es hora de ver los tipos de datos en acción.
Los siguientes párrafos proporcionan una descripción general rápida de cómo puede trabajar
con datos numéricos y lógicos en Python.
Realizar asignaciones de variables
Cuando trabaja con aplicaciones, almacena información en variables. Una variable es una
especie de caja de almacenamiento. Siempre que quieras trabajar con la información,
accedes a ella mediante la variable. Si tiene información nueva que desea almacenar, la
coloca en una variable. Cambiar información significa acceder a la variable primero y luego
almacenar el nuevo valor en la variable. Así como almacenas cosas en cajas en el mundo
real, también almacenas cosas en variables (una especie de caja de almacenamiento)
cuando trabajas con aplicaciones. Para almacenar datos en una variable, se le asignan los
datos utilizando cualquiera de varios operadores de asignación (símbolos especiales que
indican cómo almacenar los datos). La Tabla 4­1 muestra los operadores de asignación que admite Python.
CAPÍTULO 4 Introducción a Python para la programación de algoritmos 69
Machine Translated by Google
TABLA 4­1
Operadores de asignación de Python
Descripción del operador
=
Asigna el valor encontrado en el operando derecho al operando izquierdo
Ejemplo
MiVar = 5 resultados en
MyVar que contiene 5
+=
­=
*=
/=
%=
**=
Agrega el valor encontrado en el operando derecho al valor encontrado en el operando
MyVar += 2 resultados en
izquierdo y coloca el resultado en el operando izquierdo
MyVar que contiene 7
Resta el valor encontrado en el operando derecho del valor encontrado en el operando
MyVar ­= 2 resultados en
izquierdo y coloca el resultado en el operando izquierdo
MyVar que contiene 3
Multiplica el valor encontrado en el operando derecho por el valor encontrado en el
MiVar *= 2 resultados en
operando izquierdo y coloca el resultado en el operando izquierdo
MyVar que contiene 10
Divide el valor encontrado en el operando izquierdo por el valor encontrado en el
MyVar /= 2 da como
operando derecho y coloca el resultado en el operando izquierdo
resultado MyVar que contiene 2,5
Divide el valor encontrado en el operando izquierdo por el valor encontrado en el
MyVar %= 2 resultados en
operando derecho y coloca el resto en el operando izquierdo
MiVar que contiene 1
Determina el valor exponencial encontrado en el operando izquierdo cuando se eleva a la
MyVar ** 2 resultados en
potencia del valor encontrado en el operando derecho y coloca el resultado en el
MyVar que contiene 25
operando izquierdo.
//=
Divide el valor encontrado en el operando izquierdo por el valor encontrado en el operando
MiVar //= 2 resultados en
derecho y coloca el resultado entero (número entero) en el operando izquierdo
MyVar que contiene 2
haciendo aritmética
Almacenar información en variables la hace fácilmente accesible. Sin embargo, para
realizar cualquier trabajo útil con la variable, normalmente se realiza algún tipo de operación
aritmética sobre ella. Python admite los operadores aritméticos comunes que se utilizan
para realizar tareas manualmente. Aparecen en la Tabla 4­2.
TABLA 4­2
Operadores aritméticos de Python
Descripción del operador
Ejemplo
+
Suma dos valores juntos
5+2=7
Resta el operando derecho del operando izquierdo
5–2=3
Multiplica el operando derecho por el operando izquierdo
5 * 2 = 10
/
Divide el operando izquierdo por el operando derecho
5/2 = 2,5
%
Divide el operando izquierdo por el operando derecho y devuelve el resto
5%2=1
Calcula el valor exponencial del operando derecho por el operando izquierdo
5 ** 2 = 25
Realiza una división de enteros, en la que el operando izquierdo se divide por el operando
5 // 2 = 2
­
*
**
//
derecho y solo se devuelve el número entero (también llamada división de piso)
70 PARTE 1 Primeros pasos
Machine Translated by Google
A veces necesitas interactuar con una sola variable. Python admite varios operadores unarios,
aquellos que funcionan con una sola variable, como se muestra en la Tabla 4­3.
Operadores unarios de Python
TABLA 4­3
Descripción del operador
Ejemplo
~
~4 da como resultado un valor de –5
Invierte los bits de un número para que todos los bits 0 se
conviertan en bits 1 y viceversa
­
Niega el valor original para que lo positivo se convierta en negativo
–(–4) da como resultado 4 y –4 da como resultado –4
y viceversa.
+
Se proporciona únicamente con el fin de que esté completo; devuelve el
+4 da como resultado un valor de 4
mismo valor que usted proporciona como entrada
Las computadoras pueden realizar otros tipos de tareas matemáticas debido a la forma en que
funciona el procesador. Es importante recordar que las computadoras almacenan datos como una
serie de bits individuales. Python le permite acceder a estos bits individuales mediante el uso de
operadores bit a bit, como se muestra en la Tabla 4­4.
TABLA 4­4
Operador
& (Y)
| (O)
Operadores bit a bit de Python
Descripción
Ejemplo
Determina si ambos bits individuales dentro de dos operadores son verdaderos y
0b1100 y 0b0110 =
establece el bit resultante en verdadero cuando lo son.
0b0100
Determina si alguno de los bits individuales dentro de dos operadores es
0b1100 | 0b0110 =
0b1110
verdadero y establece el bit resultante en verdadero cuando lo es.
^ (Exclusivo o) Determina si solo uno de los bits individuales dentro de dos operadores es verdadero y
~
0b1010
Calcula el valor en complemento a uno de un número.
~0b1100 = –0b1101
(Complemento a uno)
<< (desplazamiento a la izquierda)
>> (Desplazamiento a la derecha)
0b1100 ^ 0b0110 =
establece el bit resultante en verdadero cuando uno lo es.
Cuando ambos bits son verdaderos o ambos bits son falsos, el resultado es falso.
~0b0110 = –0b0111
Desplaza los bits del operando izquierdo a la izquierda por el valor del operando
0b00110011 << 2 =
derecho. Todos los bits nuevos se establecen en 0 y todos los bits que salen del
final se pierden.
0b11001100
Desplaza los bits del operando izquierdo a la derecha según el valor del operando
0b00110011 >> 2 =
derecho. Todos los bits nuevos se establecen en 0 y todos los bits que salen del
final se pierden.
0b00001100
CAPÍTULO 4 Introducción a Python para la programación de algoritmos 71
Machine Translated by Google
Comparar datos usando
Expresiones booleanas
Usar la aritmética para modificar el contenido de las variables es un tipo de manipulación de datos.
Para determinar el efecto de la manipulación de datos, una computadora debe comparar el
estado actual de la variable con su estado original o el estado de un valor conocido. En algunos
casos, también es necesario detectar el estado de una entrada frente a otra. Todas estas
operaciones verifican la relación entre dos variables, por lo que los operadores resultantes son
operadores relacionales, como se muestra en la Tabla 4­5.
TABLA 4­5
Operador
==
Operadores relacionales de Python
Descripción
Ejemplo
Determina si dos valores son iguales. Observe que el operador relacional utiliza dos
1 == 2 es falso
signos iguales. Un error que cometen muchos desarrolladores es utilizar solo un signo
igual, lo que da como resultado que un valor se asigne a otro.
!=
Determina si dos valores no son iguales. Algunas versiones anteriores de Python le
1! = 2 es verdadero
permitían usar el operador <> en lugar del operador !=. El uso del operador <>
genera un error en las versiones actuales de Python.
>
Verifica que el valor del operando izquierdo sea mayor que el valor del
1 > 2 es falso
operando derecho.
<
Verifica que el valor del operando izquierdo sea menor que el valor del
1 < 2 es verdadero
operando derecho.
>=
Verifica que el valor del operando izquierdo sea mayor o igual que el valor del
1 >= 2 es falso
operando derecho.
<=
Verifica que el valor del operando izquierdo sea menor o igual que el valor del
1 <= 2 es verdadero
operando derecho.
A veces un operador relacional no puede contar toda la historia de la comparación de dos valores.
Por ejemplo, es posible que necesite verificar una condición en la que se necesitan dos
comparaciones separadas, como Mi edad > 40 y Mi altura < 74. La necesidad de agregar
condiciones a la comparación requiere un operador lógico del tipo que se muestra en la Tabla 4. 6.
72 PARTE 1 Primeros pasos
Machine Translated by Google
TABLA 4­6
Operadores lógicos de Python
Operador
Descripción
Ejemplo
y
Determina si ambos operandos son verdaderos.
Verdadero y Verdadero es Verdadero
Verdadero y Falso es Falso
Falso y verdadero es falso
Falso y falso es falso
o
Determina cuándo uno de dos operandos es verdadero.
Verdadero o Verdadero es Verdadero
Verdadero o Falso es Verdadero
Falso o Verdadero es Verdadero
Falso o Falso es Falso
no
Niega el valor de verdad de un solo operando. Un valor
verdadero se vuelve falso y un valor falso se vuelve verdadero.
no es cierto es falso
no es falso es verdad
Las computadoras ordenan las comparaciones al hacer que algunos operadores sean más
significativos que otros. El orden de los operadores es la prioridad de los operadores. La Tabla 4­7
muestra la precedencia de operadores de todos los operadores comunes de Python, incluidos
algunos que aún no ha visto como parte de una discusión. Al hacer comparaciones, siempre
considere la precedencia de los operadores porque, de lo contrario, las suposiciones que haga
sobre el resultado de una comparación probablemente serán erróneas.
TABLA 4­7
Precedencia de operadores de Python
Operador
Descripción
()
Los paréntesis se utilizan para agrupar expresiones y anular la precedencia predeterminada para poder
forzar que una operación de menor precedencia (como la suma) tenga prioridad sobre una operación
de mayor precedencia (como la multiplicación).
**
La exponenciación eleva el valor del operando izquierdo a la potencia del operando
derecho.
~+­
Los operadores unarios interactúan con una sola variable o expresión.
* / % //
Multiplica, divide, módulo y división de piso.
+­
Adición y sustracción.
>> <<
Desplazamiento bit a derecha e izquierda.
&
Bit a bit Y.
^|
OR exclusivo bit a bit y OR estándar.
<= < > >=
Operadores de comparación.
(continuado)
CAPÍTULO 4 Introducción a Python para la programación de algoritmos 73
Machine Translated by Google
TABLA 4­7 (continuación)
Operador
Descripción
== !=
Operadores de igualdad.
= %= /= //= ­= += *= **= Operadores de asignación.
es
Operadores de identidad.
no es
en
Operadores de membresía.
no en
no o y
Operadores logicos.
Crear y usar cadenas
De todos los tipos de datos, las cadenas son las que los humanos entienden más fácilmente y las
computadoras no las entienden en absoluto. Una cadena es simplemente cualquier grupo de caracteres
que se colocan entre comillas dobles. Por ejemplo, myString = "Python es un gran lenguaje". asigna
una cadena de caracteres a myString.
La razón principal para usar cadenas cuando se trabaja con algoritmos es proporcionar interacción al
usuario, ya sea como solicitudes de entrada o como un medio para hacer que la salida sea más fácil
de entender. También se pueden realizar análisis de datos de cadenas como parte del trabajo con
algoritmos, pero la computadora en realidad no requiere cadenas como parte de su secuencia de
pasos para obtener una solución a un problema. De hecho, la computadora no ve ninguna letra. Cada
letra que utilizas está representada por un número en la memoria. Por ejemplo, la letra A es en realidad
el número 65. Para comprobarlo usted mismo, escriba ord("A") en el indicador de Python y presione
Entrar. Verás 65 como salida. Puede convertir cualquier letra a su equivalente numérico usando el
comando ord() .
INICIANDO IPython
La mayor parte del libro se basa en Jupyter Notebook (consulte el Capítulo 3) porque proporciona
métodos para crear, administrar e interactuar con ejemplos de codificación complejos. Sin
embargo, a veces se necesita un entorno interactivo simple para realizar pruebas rápidas, que
es la ruta que se utiliza en este capítulo. Anaconda viene con dos de estos entornos, IPython y
Jupyter QT Console. De los dos, IPython es el más sencillo de usar, pero ambos entornos
proporcionan una funcionalidad similar. Para iniciar IPython, simplemente haga clic en su
entrada en la carpeta Anaconda3 de su sistema. Por ejemplo, cuando trabaja con
Windows, elige Inicio
Todos los programas
Anaconda3
IPython. También puede iniciar
IPython en una consola o ventana de terminal escribiendo IPython y presionando Enter.
74 PARTE 1 Primeros pasos
Machine Translated by Google
Debido a que la computadora realmente no comprende las cadenas, pero las cadenas son tan
útiles para escribir aplicaciones, a veces es necesario convertir una cadena en un número.
Puede utilizar los comandos int() y float() para realizar esta conversión. Por ejemplo, si escribe
myInt = int("123") y presiona Enter en el indicador de Python, crea un int llamado myInt que
contiene el valor 123.
También puedes convertir números en una cadena usando el comando str() . Por ejemplo, si
escribe myStr = str(1234.56) y presiona Enter, crea una cadena que contiene el valor "1234.56"
y la asigna a myStr. La cuestión es que puedes avanzar y retroceder entre cadenas y números
con gran facilidad. Los capítulos posteriores demuestran cómo estas conversiones hacen que
muchas tareas aparentemente imposibles sean bastante realizables.
Al igual que con los números, puedes utilizar algunos operadores especiales con cadenas (y
muchos objetos). Los operadores miembro le permiten determinar cuándo una cadena contiene
contenido específico. La Tabla 4­8 muestra estos operadores.
TABLA 4­8
Operador
en
Operadores de membresía de Python
Descripción
Ejemplo
Determina si el valor del operando izquierdo aparece en la
"Hola" en "Hola, adiós" es verdadero
secuencia encontrada en el operando derecho
no en
Determina si el valor del operando izquierdo falta en la secuencia
encontrada en el operando derecho
"Hola" que no está en "Hola, adiós" es
falso
La discusión en esta sección también deja claro que es necesario saber el tipo de datos que
contienen las variables. Utilice los operadores de identidad para realizar esta tarea, como se
muestra en la Tabla 4­9.
TABLA 4­9
Operador
es
Operadores de identidad de Python
Descripción
Ejemplo
Se evalúa como verdadero cuando el tipo de valor o expresión
tipo(2) es int es verdadero
en el operando derecho apunta al mismo tipo en el operando
izquierdo
no es
Se evalúa como verdadero cuando el tipo de valor o expresión
tipo(2) no es int es falso
en el operando derecho apunta a un tipo diferente que el valor o
expresión en el operando izquierdo
CAPÍTULO 4 Introducción a Python para la programación de algoritmos 75
Machine Translated by Google
Interactuar con fechas
Las fechas y horas son elementos con los que la mayoría de la gente trabaja bastante. La
sociedad basa casi todo en la fecha y hora en que se debe o se completó una tarea.
Concertamos citas y planificamos eventos para fechas y horarios específicos. La mayor
parte de nuestro día gira las 24 horas del día. Cuando se trabaja con algoritmos, la fecha u
hora en la que ocurre un paso particular en una secuencia puede ser tan importante como
cómo ocurre el paso y qué sucede como resultado de realizarlo. Los algoritmos se basan en
la fecha y la hora para organizar los datos de modo que los humanos puedan comprender
mejor los datos y el resultado resultante del algoritmo.
Debido a la naturaleza humana orientada al tiempo, es una buena idea observar cómo
Python interactúa con la fecha y la hora (especialmente almacenando estos valores para su
uso posterior). Como ocurre con todo lo demás, las computadoras sólo entienden números:
la fecha y la hora realmente no existen. El algoritmo, no la computadora, se basa en la fecha
y la hora para ayudar a organizar la serie de pasos realizados para resolver un problema.
Para trabajar con fechas y horas, debe emitir un comando especial de importación de
fecha y hora . Técnicamente, este acto se llama importar un módulo. No se preocupe por
cómo funciona el comando en este momento; simplemente utilícelo cuando quiera hacer
algo con la fecha y la hora.
Las computadoras tienen relojes en su interior, pero los relojes son para los humanos que
usan la computadora. Sí, algunos programas también dependen del reloj, pero nuevamente,
el énfasis está en las necesidades humanas más que en cualquier cosa que la computadora
pueda requerir. Para obtener la hora actual, simplemente escriba datetime.datetime.now() y
presione Enter. Verá la información completa de fecha y hora tal como se encuentra en el
reloj de su computadora, como datetime.datetime(2016, 12, 20, 10, 37, 24, 460099).
Quizás hayas notado que la fecha y la hora son un poco difíciles de leer en el formato
existente. Digamos que desea obtener solo la fecha actual y en un formato legible. Para
realizar esta tarea, accede solo a la parte de la fecha del resultado y la convierte en una
cadena. Escriba str(datetime.datetime.now().date()) y presione Entrar. Ahora tienes algo un
poco más útil, como '2016­12­20'.
Curiosamente, Python también tiene un comando time() , que puedes usar para obtener la
hora actual. Puede obtener valores separados para cada uno de los componentes que
componen la fecha y la hora utilizando los valores de día, mes, año, hora, minuto, segundo
y microsegundo . Los capítulos posteriores le ayudarán a comprender cómo utilizar estas
diversas funciones de fecha y hora para facilitar el trabajo con algoritmos.
76 PARTE 1 Primeros pasos
Machine Translated by Google
Crear y usar funciones
Cada paso de un algoritmo normalmente requiere una sola línea de código Python, una instrucción
similar al inglés que le dice a la computadora cómo acercar la solución del problema un paso más a su
finalización. Combina estas líneas de código para lograr el resultado deseado. A veces es necesario
repetir las instrucciones con datos diferentes y, en algunos casos, el código se vuelve tan largo que es
difícil realizar un seguimiento de lo que hace cada parte. Las funciones sirven como herramientas de
organización que mantienen su código limpio y ordenado.
Además, las funciones facilitan la reutilización de las instrucciones que ha creado según sea necesario
con diferentes datos. Esta sección del capítulo le explica todo acerca de las funciones. Más importante
aún, en esta sección comenzarás a crear tus primeras aplicaciones serias de la misma manera que lo
hacen los desarrolladores profesionales.
Creando funciones reutilizables
Vas a tu armario, sacas pantalón y camisa, quitas las etiquetas y te los pones. Al final del día, te quitas
todo y lo tiras a la basura.
Mmm . . . Eso realmente no es lo que hace la mayoría de la gente. La mayoría de las personas se
quitan la ropa, la lavan y luego la devuelven al armario para reutilizarla. Las funciones también son
reutilizables. Nadie quiere seguir repitiendo la misma tarea; se vuelve monótono y aburrido. Cuando
creas una función, defines un paquete de código que puedes usar una y otra vez para realizar la misma
tarea. Todo lo que necesita hacer es decirle a la computadora que realice una tarea específica diciéndole
qué función usar. La computadora ejecuta fielmente cada instrucción de la función absolutamente
cada vez que usted se lo pide.
Cuando trabaja con funciones, el código que necesita servicios de la función se denomina llamador y
solicita a la función que realice tareas para él. Gran parte de la información que ve sobre las funciones
se refiere a la persona que llama. La persona que llama debe proporcionar información a la función y la
función devuelve información a la persona que llama.
Hubo un tiempo en que los programas informáticos no incluían el concepto de reutilización del código.
Como resultado, los desarrolladores tuvieron que seguir reinventando el mismo código. Sin embargo,
no pasó mucho tiempo antes de que a alguien se le ocurriera la idea de funciones, y el concepto ha
evolucionado a lo largo de los años hasta que las funciones se han vuelto bastante flexibles. Puedes
hacer que las funciones hagan lo que quieras. La reutilización del código es una parte necesaria de
las aplicaciones para
» Reducir el tiempo de desarrollo
» Reducir el error del programador
» Aumentar la confiabilidad de la aplicación
CAPÍTULO 4 Introducción a Python para la programación de algoritmos 77
Machine Translated by Google
» Permita que grupos enteros se beneficien del trabajo de un programador
» Hacer que el código sea más fácil de entender
» Mejorar la eficiencia de las aplicaciones
De hecho, las funciones hacen una lista completa de cosas para las aplicaciones en forma de
capacidad de reutilización. A medida que analiza los ejemplos de este libro, verá cómo la
reutilización le hace la vida mucho más fácil. Si no fuera por la reutilización, aún estaría
programando conectando 0 y 1 a la computadora a mano.
Crear una función no requiere mucho trabajo. Para ver cómo funcionan las funciones, abra una
copia de IPython y escriba el siguiente código (presionando Enter al final de cada línea):
def Di Hola():
print('¡Hola!')
Para finalizar la función, presione Enter una segunda vez después de la última línea. Una función
comienza con la palabra clave def (para definir). Usted proporciona un nombre de función,
paréntesis que pueden contener argumentos de función (datos utilizados en la función) y dos puntos.
El editor sangra automáticamente la siguiente línea. Python se basa en espacios en blanco para
definir bloques de código (declaraciones que están asociadas entre sí en una función).
Ahora puede utilizar la función. Simplemente escriba SayHello() y presione Enter. Los paréntesis
después del nombre de la función son importantes porque le dicen a Python que ejecute la
función en lugar de decirle que está accediendo a una función como un objeto (para determinar
cuál es). Verás ¡Hola! como salida.
Funciones de llamada
Las funciones pueden aceptar argumentos (bits de datos adicionales) y devolver valores. La
capacidad de intercambiar datos hace que las funciones sean mucho más útiles de lo que
podrían ser de otro modo. Las siguientes secciones describen cómo llamar funciones de diversas
formas para enviar y recibir datos.
Envío de argumentos de requisitos
Una función puede requerir que quien la llama le proporcione argumentos. Un argumento
requerido es una variable que debe contener datos para que la función funcione. Abra una
copia de IPy­thon y escriba el siguiente código:
def DoSuma(Valor1, Valor2):
devolver Valor1 + Valor2
78 PARTE 1 Primeros pasos
Machine Translated by Google
Tienes una nueva función, DoSum(). Esta función requiere que proporcione dos argumentos
para usarla. Al menos eso es lo que has oído hasta ahora. Escriba DoSum() y presione Entrar.
Verá un mensaje de error como este:
Error de tecleado
Rastreo (llamadas recientes más última)
<ipython­input­2­a37c1b30cd89> en <módulo>()
­­­­> 1 DoSuma()
TypeError: DoSum() faltan 2 posicionales requeridos
argumentos: 'Valor1' y 'Valor2'
Probar DoSum() con un solo argumento daría como resultado otro mensaje de error. Para
utilizar DoSum() , debe proporcionar dos argumentos. Para ver cómo funciona esto, escriba
DoSum(1, 2) y presione Enter. Ves el resultado esperado de 3.
Observe que DoSum() proporciona un valor de salida de 3 cuando proporciona 1 y 2 como
entradas. La declaración de devolución proporciona el valor de salida. Cada vez que veas regresar
en una función, sabes que la función proporciona un valor de salida.
Envío de argumentos por palabra clave
A medida que sus funciones se vuelven más complejas y los métodos para usarlas también,
es posible que desee proporcionar un poco más de control sobre cómo llamar a la función y
proporcionarle argumentos. Hasta ahora, tiene argumentos posicionales, lo que significa que
ha proporcionado valores en el orden en que aparecen en la lista de argumentos para la
definición de función. Sin embargo, Python también tiene un método para enviar argumentos
por palabra clave. En este caso, proporcione el nombre del argumento seguido de un signo
igual (=) y el valor del argumento. Para ver cómo funciona esto, abra una copia de IPython y
escriba el siguiente código:
def DisplaySum(Valor1, Valor2):
' + '
' = ' +
print(cadena(Valor1) + + cadena(Valor2)
+ cadena((Valor1
+ Valor2)))
Observe que el argumento de la función print() incluye una lista de elementos para imprimir y
que esos elementos están separados por signos más (+). Además, los argumentos son de
diferentes tipos, por lo que debes convertirlos usando la función str() . Python facilita mezclar y
combinar argumentos de esta manera. Esta función también introduce el concepto de
continuación automática de línea. La función print() en realidad aparece en dos líneas y Python
continúa automáticamente la función desde la primera línea a la segunda.
CAPÍTULO 4 Introducción a Python para la programación de algoritmos 79
Machine Translated by Google
A continuación, es hora de probar DisplaySum(). Por supuesto, primero desea probar la
función usando argumentos posicionales, así que escriba DisplaySum(2, 3) y presione
Enter. Verá el resultado esperado de 2 + 3 = 5. Ahora escriba DisplaySum(Value2 = 3,
Value1 = 2) y presione Enter. Nuevamente, recibe el resultado 2 + 3 = 5 aunque la
posición de los argumentos se haya invertido.
Dar a los argumentos de la función un valor predeterminado
Ya sea que realice la llamada utilizando argumentos posicionales o argumentos de palabras clave,
las funciones hasta este punto han requerido que proporcione un valor. A veces, una función puede
utilizar valores predeterminados cuando hay un valor común disponible. Los valores predeterminados
hacen que la función sea más fácil de usar y menos probable que cause errores cuando un
desarrollador no proporciona información. Para crear un valor predeterminado, simplemente siga el
nombre del argumento con un signo igual y el valor predeterminado. Para ver cómo funciona esto,
abra una copia de IPython y escriba el siguiente código:
def SayHello(Saludo = "No se proporciona ningún valor"):
imprimir (saludo)
La función SayHello() proporciona un valor automático para Saludo cuando la persona
que llama no proporciona uno. Cuando alguien intenta llamar a SayHello() sin un
argumento, no genera ningún error. Escriba SayHello() y presione Entrar para comprobarlo
usted mismo: verá el mensaje predeterminado. Escriba SayHello("¡Hola!") para ver una
respuesta normal.
Crear funciones con un número variable de
argumentos.
En la mayoría de los casos, sabes exactamente cuántos argumentos proporcionar a tu
función. Vale la pena trabajar para lograr este objetivo siempre que sea posible porque
las funciones con un número fijo de argumentos son más fáciles de solucionar más
adelante. Sin embargo, a veces simplemente no se puede determinar cuántos argumentos
recibirá la función al principio. Por ejemplo, cuando crea una aplicación Python que
funciona en la línea de comandos, el usuario puede no proporcionar ningún argumento,
el número máximo de argumentos (suponiendo que haya uno) o cualquier número de argumentos en
entre.
Afortunadamente, Python proporciona una técnica para enviar un número variable de
argumentos a una función. Simplemente crea un argumento que tiene un asterisco
delante, como *VarArgs. La técnica habitual consiste en proporcionar un segundo
argumento que contenga el número de argumentos pasados como entrada. Para ver
cómo funciona esto, abra una copia de IPython y escriba el siguiente código:
80 PARTE 1 Primeros pasos
Machine Translated by Google
def DisplayMulti(ArgCount = 0, *VarArgs):
print('Pasaste '
+ cadena(ArgCount) +
'argumentos.',
VarArgs)
Observe que la función print() muestra una cadena y luego la lista de argumentos.
Debido a la forma en que está diseñada esta función, puede escribir DisplayMulti() y
presionar Enter para ver que puede pasar cero argumentos. Para ver varios argumentos en
funcionamiento, escriba DisplayMulti(3, 'Hello', 1, True) y presione Entrar. La salida de
('Pasaste 3 argumentos.', ('Hola', 1, Verdadero)) muestra que no es necesario pasar valores
de ningún tipo en particular.
Uso de declaraciones condicionales y de bucle
Los algoritmos a menudo requieren pasos que toman decisiones o realizan algunos pasos
más de una vez. Por ejemplo, es posible que necesite descartar un valor que no coincide
con el resto de los datos, lo que requiere tomar una decisión, o puede que necesite procesar
los datos más de una vez para obtener el resultado deseado, como cuando filtrar los datos.
Python se adapta a esta necesidad proporcionando declaraciones especiales que toman
decisiones o le permiten realizar pasos más de una vez, como se describe en las
secciones siguientes.
Tomar decisiones usando la declaración if
Utiliza declaraciones if con regularidad en la vida cotidiana. Por ejemplo, puedes decirte a
ti mismo: "Si es miércoles, comeré ensalada de atún en el almuerzo". La declaración if de
Python es un poco menos detallada, pero sigue exactamente el mismo patrón. Para ver
cómo funciona esto, abra una copia de IPython y escriba el siguiente código:
def ValorPrueba(Valor):
si Valor == 5:
print('¡El valor es igual a 5!')
Valor elif == 6:
print('¡El valor es igual a 6!')
demás:
print('El valor es otra cosa.')
print('Es igual a ' + str(Valor))
Cada declaración if de Python comienza, por extraño que parezca, con la palabra if. Cuando
Python ve esto, sabe que quieres que tome una decisión. Después de la palabra si viene
una condición. Una condición simplemente indica qué tipo de comparación desea que haga
Python. En este caso, desea que Python determine si Valor contiene el valor 5.
CAPÍTULO 4 Introducción a Python para la programación de algoritmos 81
Machine Translated by Google
Observe que la condición utiliza el operador de igualdad relacional, ==, y no el operador de
asignación, =. Un error común que cometen los desarrolladores es utilizar el operador de
asignación en lugar del operador de igualdad. Usar el operador de asignación en lugar del
operador de igualdad hará que su código no funcione correctamente.
La condición siempre termina con dos puntos (:). Si no proporciona dos puntos, Python no sabe
que la condición ha finalizado y continuará buscando condiciones adicionales en las que basar
su decisión. Después de los dos puntos vienen las tareas que desea que realice Python.
Es posible que necesite realizar varias tareas utilizando una única declaración if. el elif
La cláusula permite agregar una condición adicional y tareas asociadas. Una cláusula es una
adición a una condición previa, que en este caso es una declaración if . La cláusula elif siempre
proporciona una condición, tal como lo hace la declaración if , y tiene su propio conjunto
asociado de tareas a realizar.
A veces es necesario hacer algo sin importar cuál sea la condición. En este caso, agrega la
cláusula else . La cláusula else le dice a Python que haga algo en particular cuando no se
cumplen las condiciones de la declaración if .
Observe cómo la sangría se vuelve más importante a medida que las funciones se vuelven más
complejas. La función contiene una declaración if . La declaración if contiene solo una declaración
print() . La cláusula else contiene dos declaraciones print() .
Para ver esta función en acción, escriba TestValue(1) y presione Enter. Ves el resultado de la
cláusula else . Escriba TestValue(5) y presione Entrar. El resultado ahora refleja el resultado de
la declaración if . Escriba TestValue(6) y presione Entrar. El resultado ahora muestra los
resultados de la cláusula elif . El resultado es que esta función es más flexible que las funciones
anteriores del capítulo porque puede tomar decisiones.
Elegir entre múltiples opciones
usando decisiones anidadas
Anidar es el proceso de colocar una declaración subordinada dentro de otra declaración. En la
mayoría de los casos, puede anidar cualquier declaración dentro de cualquier otra declaración.
Para ver cómo funciona esto, abra una copia de IPython y escriba el siguiente código:
def Número Secreto():
Uno = int(input("Escriba un número entre 1 y 10: "))
Dos = int(input("Escriba un número entre 1 y 10: "))
si (Uno >= 1) y (Uno <= 10):
si (Dos >= 1) y (Dos <= 10):
82 PARTE 1 Primeros pasos
Machine Translated by Google
+ str(Uno * Dos))
print('Tu número secreto es: '
demás:
print("¡Segundo valor incorrecto!")
demás:
print("¡Primer valor incorrecto!")
En este caso, SecretNumber() le pide que proporcione dos entradas. Sí, puede obtener entradas de un
usuario cuando sea necesario utilizando la función input() . La función int() convierte las entradas en un
número.
Esta vez hay dos niveles de declaración if . El primer nivel comprueba la validez del número en Uno. El
segundo nivel comprueba la validez del número en Dos. Cuando Uno y Dos tienen valores entre 1 y
10, .SecretNumber() genera un número secreto para el usuario.
Para ver SecretNumber() en acción, escriba SecretNumber() y presione Enter. tipo 20
y presione Enter cuando se le solicite el primer valor de entrada, y escriba 10 y presione Enter cuando se
le solicite el segundo. Verá un mensaje de error que le indica que el primer valor es incorrecto. Escriba
SecretNumber() y presione Enter nuevamente. Esta vez, use valores de 10 y 20. La función le dirá que la
segunda entrada es incorrecta.
Intente la misma secuencia nuevamente usando valores de entrada de 10 y 10.
Realizar tareas repetitivas
usando el bucle for
A veces es necesario realizar una tarea más de una vez. Utiliza la instrucción de bucle for
cuando necesita realizar una tarea una cantidad específica de veces. el para
El bucle tiene un comienzo definido y un final definido. La cantidad de veces que se ejecuta este bucle
depende de la cantidad de elementos en la variable que proporcione. Para ver cómo funciona esto, abra
una copia de IPython y escriba el siguiente código:
def VisualizaciónMulti(*VarArgs):
para Arg en VarArgs:
si Arg.upper() == 'CONT':
continuar
print('Continuar argumento: '
+ Arg)
elif Arg.upper() == 'BREAK':
romper
print('Romper argumento: '
print('Buen argumento: '
+ Arg)
+ Arg)
En este caso, el bucle for intenta procesar cada elemento en VarArgs. Observe que hay una declaración
if anidada en el bucle y prueba dos condiciones finales.
CAPÍTULO 4 Introducción a Python para la programación de algoritmos 83
Machine Translated by Google
En la mayoría de los casos, el código omite la declaración if y simplemente imprime el argumento.
Sin embargo, cuando la instrucción if encuentra las palabras CONT o BREAK en los valores de entrada,
realiza una de estas dos tareas:
» continuar: obliga al bucle a continuar desde el punto de ejecución actual con la
siguiente entrada en VarArgs.
» break: Detiene la ejecución del bucle.
Las palabras clave pueden aparecer usando cualquier combinación de letras mayúsculas y minúsculas, como
ConT, porque la función Upper() las convierte a mayúsculas. La función DisplayMulti() puede procesar cualquier
número de cadenas de entrada. Para verlo en acción, escriba DisplayMulti('Hola', 'Adiós', 'Primero', 'Último') y
presione Entrar. Verá cada una de las cadenas de entrada presentadas en una línea separada en la salida.
Ahora escriba DisplayMulti('Hola', 'Cont', 'Adiós', 'Pausa', 'Último') y presione Entrar. Observe que las palabras
Cont y Break no aparecen en el resultado porque son palabras clave. Además, la palabra Último no aparece en
el resultado porque el for
El bucle termina antes de que se procese esta palabra.
Usando la declaración while
La instrucción del bucle while continúa realizando tareas hasta el momento en que una condición ya no es
verdadera. Al igual que con la declaración for , la declaración while admite las palabras clave continuar y romper
para finalizar el ciclo prematuramente. Para ver cómo funciona esto, abra una copia de IPython y escriba el
siguiente código:
def Número Secreto():
Entendido = Falso
mientras que GotIt == Falso:
Uno = int(input("Escriba un número entre 1 y 10: "))
Dos = int(input("Escriba un número entre 1 y 10: "))
si (Uno >= 1) y (Uno <= 10):
si (Dos >= 1) y (Dos <= 10):
print('El número secreto es: ' + str(Uno * Dos))
Entendido = Verdadero
continuar
demás:
print("¡Segundo valor incorrecto!")
demás:
print("¡Primer valor incorrecto!")
print("¡Inténtalo de nuevo!")
84 PARTE 1 Primeros pasos
Machine Translated by Google
Esta es una expansión de la función SecretNumber() descrita por primera vez en la sección
“Elección entre múltiples opciones usando decisiones anidadas”, anteriormente en este capítulo.
Sin embargo, en este caso, agregar una declaración de bucle while significa que la función
continúa solicitando información hasta que recibe una respuesta válida.
Para ver cómo funciona la instrucción while , escriba SecretNumber() y presione Enter.
Escriba 20 y presione Entrar para el primer mensaje. Escriba 10 y presione Entrar para el
segundo mensaje. El ejemplo le indica que el primer número es incorrecto y luego le indica que
intente nuevamente. Inténtalo una segunda vez usando valores de 10 y 20. Esta vez, el segundo
número es incorrecto y aún debes intentarlo nuevamente. En el tercer intento, usa valores de 10
y 10. Esta vez, obtienes un número secreto. Observe que el uso de un continuar
cláusula significa que la aplicación no le indica que vuelva a intentarlo.
Almacenamiento de datos mediante conjuntos, listas y tuplas
Cuando se trabaja con algoritmos, lo importante son los datos. Python proporciona una gran
cantidad de métodos para almacenar datos en la memoria. Cada método tiene ventajas y
desventajas. Es importante elegir el método más apropiado para su necesidad particular. Las
siguientes secciones analizan tres técnicas comunes utilizadas para almacenar datos para las
necesidades de la ciencia de datos.
Creando conjuntos
La mayoría de las personas han usado conjuntos en algún momento en la escuela para crear
listas de elementos que van juntos. Estas listas luego se convirtieron en tema de manipulación
mediante operaciones matemáticas como intersección, unión, diferencia y diferencia simétrica.
Los conjuntos son la mejor opción para elegir cuando necesita realizar pruebas de membresía y
eliminar duplicados de una lista. No puede realizar tareas relacionadas con secuencias utilizando
conjuntos, como indexar o dividir. Para ver cómo puede trabajar con conjuntos, inicie una copia
de IPython y escriba el siguiente código:
ConjuntoA = conjunto(['Rojo', 'Azul', 'Verde', 'Negro'])
SetB = set(['Negro', 'Verde', 'Amarillo', 'Naranja'])
ConjuntoX = ConjuntoA.union(ConjuntoB)
ConjuntoY = ConjuntoA.intersección(ConjuntoB)
ConjuntoZ = ConjuntoA.diferencia(ConjuntoB)
Ahora tienes cinco conjuntos diferentes para jugar, cada uno de los cuales tiene algunos
elementos comunes. Para ver los resultados de cada operación matemática, escriba print('{0}\n{1}\n{2}'.
CAPÍTULO 4 Introducción a Python para la programación de algoritmos 85
Machine Translated by Google
formato (SetX, SetY, SetZ)) y presione Enter. Verá un conjunto impreso en cada línea, así:
{'Azul', 'Naranja', 'Rojo', 'Verde', 'Negro', 'Amarillo'}
{'Verde', 'Negro'}
{'Azul rojo'}
Las salidas muestran los resultados de las operaciones matemáticas: unión(), intersección() y
diferencia(). El formato de impresión más sofisticado de Python puede resultar útil al trabajar con
colecciones como conjuntos. La función format() le dice a Python qué objetos colocar dentro de cada
uno de los marcadores de posición en la cadena. Un marcador de posición es un conjunto de llaves
({}) con un número opcional. El carácter de escape (esencialmente una especie de control o carácter
especial), /n, proporciona un carácter de nueva línea entre las entradas.
Puede leer más sobre el formato sofisticado en https://docs.python.org/3/
tutorial/entradasalida.html.
También puede probar las relaciones entre los distintos conjuntos. Por ejemplo, escriba ConjuntoA.
issuperset(SetY) y presione Enter. El valor de salida de True le indica que SetA es un superconjunto
de SetY. Del mismo modo, si escribe SetA.issubset(SetX) y presiona Enter, encontrará que SetA es
un subconjunto de SetX.
Es importante comprender que los conjuntos son mutables o inmutables. Todos los conjuntos de este
ejemplo son mutables, lo que significa que puedes agregarles o quitarles elementos. Por ejemplo, si
escribe SetA.add('Purple') y presiona Enter, SetA recibe un nuevo elemento. Si escribe
SetA.issubset(SetX) y presiona Enter ahora, encontrará que SetA ya no es un subconjunto de SetX
porque SetA tiene el color 'Púrpura'.
elemento en el mismo.
Creando listas
La especificación de Python define una lista como una especie de secuencia. Las secuencias
simplemente proporcionan algún medio para permitir que varios elementos de datos existan juntos en
una única unidad de almacenamiento, pero como entidades separadas. Piense en uno de esos
grandes contenedores de correo que se ven en los edificios de apartamentos. Un único contenedor de
correo contiene varios buzones pequeños, cada uno de los cuales puede contener correo. Python
también admite otros tipos de secuencias:
» Tuplas: una tupla es una colección que se utiliza para crear secuencias complejas similares a listas.
Una ventaja de las tuplas es que puedes anidar el contenido de una tupla. Esta característica le
permite crear estructuras que pueden contener registros de empleados o pares de
coordenadas xy.
» Diccionarios: al igual que con los diccionarios reales, crea pares clave/valor cuando utiliza la
colección de diccionarios (piense en una palabra y su definición asociada).
86 PARTE 1 Primeros pasos
Machine Translated by Google
Un diccionario proporciona tiempos de búsqueda increíblemente rápidos y facilita significativamente
el pedido de datos.
» Pilas: la mayoría de los lenguajes de programación admiten pilas directamente. Sin embargo,
Python no es compatible con la pila, aunque existe una solución para ello.
Una pila es una secuencia de último en entrar/primero en salir (LIFO). Piense en una pila de
panqueques: puede agregar panqueques nuevos encima y también quitarlos. Una pila es una colección
importante que puedes simular en Python usando una lista.
» Colas: una cola es una colección de primero en entrar/primero en salir (FIFO). Lo usas para rastrear
elementos que necesitan ser procesados de alguna manera. Piense en una cola como una fila en el
banco. Entras en la fila, esperas tu turno y finalmente te llaman para hablar con un cajero.
» Deques: una cola de dos extremos (deque) es una estructura similar a una cola que le permite agregar o
eliminar elementos desde cualquier extremo, pero no desde el medio. Puede usar una deque como
una cola o una pila o cualquier otro tipo de colección a la que agrega y de la que elimina elementos
de manera ordenada (a diferencia de las listas, tuplas y diccionarios, que permiten el acceso
aleatorio). y gestión).
De todas las secuencias, las listas son las más fáciles de entender y las que están más
directamente relacionadas con un objeto del mundo real. Trabajar con listas le ayuda a ser
más capaz de trabajar con otros tipos de secuencias que proporcionan mayor funcionalidad
y flexibilidad mejorada. La cuestión es que los datos se almacenan en una lista de forma
muy parecida a como los escribiríamos en una hoja de papel: un elemento viene tras otro. La
lista tiene un principio, un desarrollo y un final. Python numera los elementos de la lista.
(Incluso si normalmente no numera los elementos de la lista en la vida real, el uso de una
lista numerada facilita el acceso a los elementos). Para ver cómo puede trabajar con listas,
inicie una copia de IPython y escriba el siguiente código:
ListaA = [0, 1, 2, 3]
ListaB = [4, 5, 6, 7]
ListaA.extender(ListaB)
ListaA
Cuando escribe la última línea de código, verá el resultado de [0, 1, 2, 3, 4, 5, 6, 7]. La
función extend() agrega los miembros de ListB a ListA. Además de ampliar las listas,
también puede agregarlas utilizando la función append() . Escriba ListaA.
agregar(­5) y presionar Enter. Cuando escribe ListA y presiona Enter nuevamente, ve que
Python ha agregado –5 al final de la lista. Es posible que necesites eliminar elementos
nuevamente, y lo haces usando la función remove() . Por ejemplo, escriba ListA.remove(­5)
y presione Entrar. Cuando vuelve a enumerar ListA escribiendo ListA
y al presionar Enter, verá que la entrada agregada desapareció.
CAPÍTULO 4 Introducción a Python para la programación de algoritmos 87
Machine Translated by Google
Las listas también admiten la concatenación mediante el uso del signo más (+) para agregar una lista a otra.
Por ejemplo, si escribe ListX = ListA + ListB y presiona Enter, encontrará que el ListX recién creado contiene
tanto ListA como ListB , con los elementos de ListA en primer lugar.
Crear y usar tuplas
Una tupla es una colección que se utiliza para crear listas complejas, en las que puedes incrustar una tupla
dentro de otra. Esta incrustación le permite crear jerarquías con tuplas. Una jerarquía puede ser algo tan
simple como el directorio de su disco duro o un organigrama de su empresa. La idea es que puedas crear
estructuras de datos complejas usando una tupla.
Las tuplas son inmutables, lo que significa que no puedes cambiarlas. Puedes crear una nueva tupla con el
mismo nombre y modificarla de alguna manera, pero no puedes modificar una tupla existente. Las listas son
mutables, lo que significa que puedes cambiarlas. Por lo tanto, al principio puede parecer que una tupla está
en desventaja, pero la inmutabilidad tiene todo tipo de ventajas, como ser más segura y más rápida.
Además, los objetos inmutables son más fáciles de usar con múltiples procesadores. Para ver cómo puedes
trabajar con tuplas, inicia una copia de IPython y escribe el siguiente código:
MiTupla = (1, 2, 3, (4, 5, 6, (7, 8, 9)))
MyTuple está anidado en tres niveles de profundidad. El primer nivel consta de los valores 1, 2, 3 y una
tupla. El segundo nivel consta de los valores 4, 5, 6 y otra tupla más.
El tercer nivel consta de los valores 7, 8 y 9. Para ver cómo funciona, escriba el siguiente código en IPython:
para Valor1 en MiTupla:
si tipo(Valor1) == int:
imprimir(Valor1)
demás:
para Valor2 en Valor1:
si tipo(Valor2) == int:
imprimir("\t", Valor2)
demás:
para Valor3 en Valor2:
imprimir("\t\t", Valor3)
Cuando ejecuta este código, descubre que los valores realmente están en tres niveles diferentes.
Puedes ver las sangrías que muestran el nivel:
88 PARTE 1 Primeros pasos
Machine Translated by Google
1
2
3
4
5
6
7
8
9
Es posible realizar tareas como agregar nuevos valores, pero debe hacerlo agregando las
entradas originales y los nuevos valores a una nueva tupla. Además, puede agregar tuplas
únicamente a una tupla existente. Para ver cómo funciona esto, escriba MyNewTuple =
MiTupla.__add__((10, 11, 12, (13, 14, 15))) y presione Enter. MyNewTuple contiene nuevas
entradas tanto en el primer como en el segundo nivel, como esta: (1, 2, 3, (4, 5, 6, (7, 8, 9)),
10, 11, 12, (13, 14 , 15)).
Definición de iteradores útiles
Los capítulos que siguen utilizan todo tipo de técnicas para acceder a valores individuales en
varios tipos de estructuras de datos. Para esta sección, utiliza dos listas simples, definidas de
la siguiente manera:
ListaA = ['Naranja', 'Amarillo', 'Verde', 'Marrón']
ListaB = [1, 2, 3, 4]
El método más sencillo para acceder a un valor particular es utilizar un índice. Por ejemplo,
si escribe ListaA[1] y presiona Enter, verá "Amarillo" como resultado. Todos los índices en
Python están basados en cero, lo que significa que la primera entrada es 0, no 1.
Los rangos presentan otro método simple para acceder a los valores. Por ejemplo, si escribe
ListaB[1:3] y presiona Enter, la salida es [2, 3]. Podrías usar el rango como entrada para un
bucle for , como
para valor en ListaB[1:3]:
imprimir (valor)
En lugar de la lista completa, verá solo 2 y 3 como resultados, impresos en líneas separadas.
El rango tiene dos valores separados por dos puntos. Sin embargo, los valores son opcionales.
Por ejemplo, ListB[:3] generaría [1, 2, 3]. Cuando omite un valor, el rango comienza al principio
o al final de la lista, según corresponda.
CAPÍTULO 4 Introducción a Python para la programación de algoritmos 89
Machine Translated by Google
A veces es necesario procesar dos listas en paralelo. El método más sencillo para hacer esto
es utilizar la función zip() . A continuación se muestra un ejemplo de la función zip() en acción:
para Valor1, Valor2 en zip (ListaA, ListaB):
imprimir(Valor1, '\t', Valor2)
Este código procesa tanto ListA como ListB al mismo tiempo. El procesamiento finaliza
cuando el bucle for llega a la más corta de las dos listas. En este caso, verá lo siguiente:
naranja 1
Amarillo 2
Verde 3
Marrón 4
Esta es la punta del iceberg. Verá una gran cantidad de tipos de iteradores utilizados a lo
largo del libro. La idea es permitirle enumerar solo los elementos que desea, en lugar de
todos los elementos en una lista u otra estructura de datos. Algunos de los iteradores
utilizados en los próximos capítulos son un poco más complicados que los que ve aquí, pero
este es un comienzo importante.
Indexación de datos mediante diccionarios
Un diccionario es un tipo especial de secuencia que utiliza un par de nombre y valor. El uso
de un nombre facilita el acceso a valores particulares con algo más que un índice numérico.
Para crear un diccionario, incluya pares de nombre y valor entre llaves. Cree un diccionario
de prueba escribiendo MyDict = {'Orange':1, 'Blue':2, 'Pink':3} y presionando Enter.
Para acceder a un valor particular, utiliza el nombre como índice. Por ejemplo, escriba
MyDict['Pink'] y presione Enter para ver el valor de salida de 3. El uso de diccionarios como
estructuras de datos facilita el acceso a conjuntos de datos increíblemente complejos
utilizando términos que todos pueden entender. En muchos otros aspectos, trabajar con un
diccionario es lo mismo que trabajar con cualquier otra secuencia.
Los diccionarios tienen algunas características especiales. Por ejemplo, escriba MyDict.keys()
y presione Entrar para ver una lista de las claves. Puede utilizar la función valores() para ver
la lista de valores en el diccionario.
90 PARTE 1 Primeros pasos
Machine Translated by Google
EN ESTE CAPÍTULO
» Usar matrices y vectores para
realizar cálculos
» Obtención de las combinaciones correctas
» Emplear técnicas recursivas para obtener
resultados específicos
» Considerando formas de
acelerar los cálculos
Capítulo 5
Realizar lo esencial
Manipulaciones de datos
Usando Python
términos aquellos símbolos arcanos que se utilizan a menudo en representaciones matemáticas de
El capítulo
4 analiza
usocapítulo,
de Python
como medio
para expresar
en concreto
algoritmos.
Enelese
descubrirás
las diversas
construcciones
del lenguaje.
Se utiliza para realizar tareas en Python. Sin embargo, no basta con saber cómo
controlar un lenguaje utilizando sus construcciones para realizar tareas. El objetivo
de los algoritmos matemáticos es convertir un tipo de datos en otro tipo de datos.
Manipular datos significa tomar datos sin procesar y hacer algo con ellos para lograr
el resultado deseado. (Al igual que con la ciencia de datos, este es un tema tratado en
Python for Data Science For Dummies, de John Paul Mueller y Luca Massaron [Wiley].)
Por ejemplo, hasta que no hagas algo con los datos de tráfico, no podrás ver los
patrones que Surgen sugerencias que le indican dónde gastar dinero adicional en
mejoras. Los datos de tráfico en su forma original no sirven para informarle: debe
manipularlos para ver el patrón de manera útil. Por lo tanto, esos símbolos arcanos
son útiles después de todo. Los utiliza como una especie de máquina para convertir
datos sin procesar en algo útil, que es lo que descubrirá en este capítulo.
CAPÍTULO 5 Realizar manipulaciones de datos esenciales usando Python 91
Machine Translated by Google
En el pasado, la gente tenía que realizar diversas manipulaciones para que los datos fueran útiles
a mano, lo que requería conocimientos avanzados de matemáticas. Afortunadamente, puedes
encontrar paquetes de Python para realizar la mayoría de estas manipulaciones usando un poco
de código. Ya no es necesario memorizar manipulaciones arcanas, solo debes saber qué
características de Python usar. Eso es lo que este capítulo le ayuda a lograr.
Descubrirá los medios para realizar diversos tipos de manipulaciones de datos utilizando paquetes
Python de fácil acceso diseñados especialmente para ese propósito. El capítulo comienza con
manipulaciones de vectores y matrices. Las secciones posteriores analizan técnicas como la
recursividad que pueden simplificar aún más las tareas y realizar algunas tareas que son casi
imposibles por otros medios. También descubrirá cómo acelerar los cálculos para pasar menos
tiempo manipulando los datos y más tiempo haciendo algo realmente interesante con ellos, como
descubrir cómo evitar que se produzcan tantos atascos.
Realizar cálculos utilizando
Vectores y matrices
Para realizar un trabajo útil con Python, a menudo es necesario trabajar con grandes cantidades
de datos que vienen en formas específicas. Estas formas tienen nombres que suenan extraños,
pero los nombres son bastante importantes. Los tres términos que necesita conocer para este
capítulo son los siguientes:
» Escalar: Un elemento de datos de base única. Por ejemplo, el número 2 se muestra solo.
es un escalar.
» Vector: una matriz unidimensional (esencialmente una lista) de elementos de datos. Por ejemplo, una matriz
que contenga los números 2, 3, 4 y 5 sería un vector. Accede a elementos en un vector utilizando un
índice de base cero, un puntero al elemento que desea. El elemento en el índice 0 es el primer elemento
del vector, que en este caso es 2.
» Matriz: una matriz de dos o más dimensiones (esencialmente una tabla) de elementos de datos.
Por ejemplo, una matriz que contiene los números 2, 3, 4 y 5 en la primera fila y 6, 7, 8 y 9 en la
segunda fila es una matriz. Se accede a los elementos de una matriz mediante un índice de filas y
columnas de base cero. El elemento de la fila 0, columna 0 es el primer elemento de la matriz, que en
este caso es 2.
Python proporciona una variedad interesante de características por sí solo, como se describe en el
Capítulo 4, pero aún necesitarás trabajar mucho para realizar algunas tareas. Para reducir la
cantidad de trabajo que realiza, puede confiar en el código escrito por otras personas y que se
encuentra en los paquetes. Las siguientes secciones describen cómo utilizar el paquete NumPy
para realizar diversas tareas en escalares, vectores y matrices.
92 PARTE 1 Primeros pasos
Machine Translated by Google
Comprender las operaciones escalares
y vectoriales
El paquete NumPy proporciona una funcionalidad esencial para la informática científica en Python.
Para usar numpy, lo importa usando un comando como importar numpy como np. Ahora puedes
acceder a numpy usando la abreviatura común de dos letras np.
Python proporciona acceso a un solo tipo de datos en cualquier categoría en particular. Por
ejemplo, si necesita crear una variable que represente un número sin una porción decimal, utilice
el tipo de datos entero. Usar una designación genérica como esta es útil porque simplifica el
código y le da al desarrollador mucho menos de qué preocuparse.
Sin embargo, en los cálculos científicos, a menudo se necesita un mejor control sobre cómo
aparecen los datos en la memoria, lo que significa tener más tipos de datos, algo que es tan complicado.
proporciona para ti. Por ejemplo, es posible que necesite definir un escalar particular como corto
(un valor de 16 bits de longitud). Usando numpy, puedes definirlo como myShort = np.short(15).
Podrías definir una variable de exactamente el mismo tamaño usando np.
función int16 . El paquete NumPy proporciona acceso a una variedad secundaria de tipos de
datos descritos en https://docs.scipy.org/doc/numpy/reference/arrays.
escalares.html.
Utilice la función de matriz numpy para crear un vector. Por ejemplo, miVect = np.
array([1, 2, 3, 4]) crea un vector con cuatro elementos. En este caso, el vector contiene
números enteros estándar de Python. También puede utilizar la función arange para
producir vectores, como myVect = np.arange(1, 10, 2), que llena myVect con array([1, 3, 5,
7, 9]). La primera entrada indica el punto de inicio, la segunda el punto de finalización y la
tercera el paso entre cada número. Un cuarto argumento le permite definir el tipo de datos
para el vector. También puedes crear un vector con un tipo de datos específico. Todo lo que
necesita hacer es especificar el tipo de datos de esta manera: myVect = np.array(np.int16([1,
2, 3, 4])) para llenar myVect con un vector como este: array([1, 2, 3, 4], tipod=int16).
En algunos casos, necesita funciones especiales para crear un vector (o una matriz) de un tipo
específico. Por ejemplo, algunas tareas matemáticas requieren que llenes el vector con unidades.
En este caso, usa la función unos como esta: myVect = np.ones(4, dtype=np.int16) para llenar
myVect con tipos de datos específicos como este: array([1, 1, 1, 1], dtipo=int16). También puedes
usar una función de ceros para llenar un vector con ceros.
Puede realizar funciones matemáticas básicas en vectores en su conjunto, lo que lo hace
increíblemente útil y menos propenso a errores que pueden ocurrir al usar construcciones de
programación como bucles para realizar la misma tarea. Por ejemplo, myVect + 1 produce una
salida de matriz ([2, 3, 4, 5]) cuando se trabaja con enteros estándar de Python. Si elige trabajar
con el tipo de datos numpy int16 , myVect + 1
produce una matriz ([2, 3, 4, 5], dtype = int16). Tenga en cuenta que el resultado le indica
específicamente qué tipo de datos está en uso. Como es de esperar, myVect ­ 1 produce
CAPÍTULO 5 Realizar manipulaciones de datos esenciales usando Python 93
Machine Translated by Google
una salida de matriz ([0, 1, 2, 3]). Incluso puedes usar vectores en escenarios matemáticos más
complejos, como 2 ** myVect, donde la salida es array([2, 4, 8, 16], dtype=int32). Sin embargo,
cuando se usa de esta manera, numpy a menudo asigna un tipo específico a la salida, incluso
cuando define un vector usando enteros estándar de Python.
Como reflexión final sobre las operaciones escalares y vectoriales, también puede realizar
tareas tanto lógicas como de comparación. Por ejemplo, el siguiente código realiza operaciones
de comparación en dos matrices:
a = np.matriz([1, 2, 3, 4])
b = np.matriz([2, 2, 4, 4])
a == b
matriz ([Falso, Verdadero, Falso, Verdadero], dtype = bool)
a <b
matriz ([Verdadero, Falso, Verdadero, Falso], dtype = bool)
Comenzando con dos vectores, a y b, el código verifica si los elementos individuales en a son
iguales a los de b. En este caso, a[0] no es igual a b[0]. Sin embargo, un[1]
es igual a b[1]. La salida es un vector de tipo bool que contiene valores verdaderos o falsos
basados en las comparaciones individuales. Del mismo modo, puede comprobar los casos en
los que a < b y producir otro vector que contenga los valores de verdad en este caso.
Las operaciones lógicas se basan en funciones especiales. Verifica la salida lógica de los
operadores booleanos AND, OR, XOR y NOT. A continuación se muestra un ejemplo de las
funciones lógicas:
a = np.array([Verdadero, Falso, Verdadero, Falso])
b = np.array([Verdadero, Verdadero, Falso, Falso])
np.lógico_o (a, b)
matriz ([Verdadero, Verdadero, Verdadero, Falso], dtype = bool)
np.lógico_y (a, b)
matriz ([Verdadero, Falso, Falso, Falso], dtype = bool)
np.lógico_no(a)
matriz ([Falso, Verdadero, Falso, Verdadero], dtype = bool)
np.lógico_xor (a, b)
matriz ([Falso, Verdadero, Verdadero, Falso], dtype = bool)
94 PARTE 1 Primeros pasos
Machine Translated by Google
También puede utilizar la entrada numérica para estas funciones. Cuando se utiliza entrada
numérica, un 0 es falso y un 1 es verdadero. Al igual que con las comparaciones, las
funciones funcionan elemento por elemento aunque solo realice una llamada. Puede leer
más sobre las funciones lógicas en https://docs.scipy.org/doc/numpy­1.10.0/reference/
rutinas.logic.html.
Realizar multiplicación de vectores
La suma, resta o división de vectores se produce elemento por elemento, como se describe
en la sección anterior. Sin embargo, cuando se trata de multiplicar, las cosas se ponen un
poco extrañas. De hecho, dependiendo de lo que realmente quieras hacer, las cosas pueden
volverse bastante extrañas. Considere el tipo de multiplicación discutido en la sección
anterior. Tanto myVect * myVect como np.multiply(myVect, myVect) producen una salida
elemento por elemento de array([ 1, 4, 9, 16]).
Desafortunadamente, una multiplicación elemento por elemento puede producir resultados
incorrectos cuando se trabaja con algoritmos. En muchos casos, lo que realmente necesitas
es un producto escalar, que es la suma de los productos de dos secuencias numéricas.
Cuando se trabaja con vectores, el producto escalar es siempre la suma de las
multiplicaciones individuales elemento por elemento y da como resultado un solo número. Por ejemplo, miVe
dot(myVect) da como resultado un resultado de 30. Si suma los valores de la multiplicación
elemento por elemento, encontrará que efectivamente suman 30. La discusión en https://
www.mathsisfun.com/algebra /vectores­punto­producto.html le informa sobre los productos
punto y le ayuda a comprender dónde podrían encajar con los algoritmos.
Puede obtener más información sobre las funciones de manipulación de álgebra lineal para
numpy en https://docs.scipy.org/doc/numpy/reference/routines.linalg.html.
Crear una matriz es la forma correcta de comenzar
Muchas de las mismas técnicas que usas con vectores también funcionan con matrices. Para
crear una matriz básica, simplemente usa la función de matriz como lo haría con un vector,
pero define dimensiones adicionales. Una dimensión es una dirección en la matriz.
Por ejemplo, una matriz bidimensional contiene filas (una dirección) y columnas (una
segunda dirección). La llamada de matriz myMatrix = np.array([[1,2,3], [4,5,6], [7,8,9]])
produce una matriz que contiene tres filas y tres columnas, como esta:
matriz([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
CAPÍTULO 5 Realizar manipulaciones de datos esenciales usando Python 95
Machine Translated by Google
Observe cómo incrusta tres listas dentro de una lista contenedora para crear las dos
dimensiones. Para acceder a un elemento de matriz en particular, debe proporcionar un
valor de índice de fila y columna, como myMatrix[0, 0] para acceder al primer valor de 1.
Puede producir matrices con cualquier número de dimensiones utilizando una técnica
similar. Por ejemplo, myMatrix = np.array([[[1,2], [3,4]], [[5,6], [7,8]]]) produce una matriz
tridimensional con x, y , y eje z que se ve así:
matriz([[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]]])
En este caso, incrusta dos listas, dentro de dos listas de contenedores, dentro de una
única lista de contenedores que mantiene todo junto. En este caso, debe proporcionar
un valor de índice x, y, z para acceder a un valor particular. Por ejemplo, miMatriz[0, 1, 1]
accede al valor 4.
En algunos casos, es necesario crear una matriz que tenga ciertos valores iniciales. Por
ejemplo, si necesita una matriz llena de unos desde el principio, puede usar los
función. La llamada a myMatrix = np.ones([4,4], dtype=np.int32) produce una matriz que
contiene cuatro filas y cuatro columnas llenas de valores int32 , como esta:
matriz([[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]])
Del mismo modo, una llamada a myMatrix = np.ones([4,4,4], dtype=np.bool) creará una
matriz tridimensional. Esta vez, la matriz contendrá valores booleanos de Verdadero.
También hay funciones para crear una matriz llena de ceros, la matriz de identidad, y
para satisfacer otras necesidades. Puede encontrar una lista completa de funciones de
creación de matrices vectoriales y matriciales en https://docs.scipy.org/doc/numpy/
referencia/rutinas.array­creation.html.
El paquete NumPy admite una clase de matriz real . La clase de matriz admite
características especiales que facilitan la realización de tareas específicas de la matriz.
Descubrirá estas características más adelante en este capítulo. Por ahora, todo lo que
realmente necesita saber es cómo crear una matriz del tipo de datos de matriz . El
método más sencillo es hacer una llamada similar a la que usa para la función de
matriz , pero usando la función mat en su lugar, como myMatrix = np.mat([[1,2,3], [4,5,
6], [7,8,9]]), lo que produce la siguiente matriz:
96 PARTE 1 Primeros pasos
Machine Translated by Google
matriz([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
También puede convertir una matriz existente en una matriz usando la función asmatrix .
Utilice la función asarray para convertir un objeto de matriz nuevamente a una forma de matriz .
El único problema con la clase de matriz es que sólo funciona con matrices bidimensionales. Si
intenta convertir una matriz tridimensional a la matriz
clase, verá un mensaje de error que le indica que la forma es demasiado grande para ser una matriz.
Multiplicar matrices
Multiplicar dos matrices implica las mismas preocupaciones que multiplicar dos vectores (como se
analizó en la sección “Realización de la multiplicación de vectores”, anteriormente en este capítulo). El
siguiente código produce una multiplicación elemento por elemento de dos matrices.
a = np.matriz([[1,2,3],[4,5,6]])
b = np.matriz([[1,2,3],[4,5,6]])
a*b
matriz([[ 1, 4, 9],
[16, 25, 36]])
Tenga en cuenta que a y b tienen la misma forma, dos filas y tres columnas. Para realizar una
multiplicación elemento por elemento, las dos matrices deben tener la misma forma.
De lo contrario, verá un mensaje de error que le indica que las formas son incorrectas. Al igual que
con los vectores, la función multiplicar también produce un resultado elemento por elemento.
Los productos escalares funcionan de manera completamente diferente con las matrices. En este
caso, el número de columnas de la matriz a debe coincidir con el número de filas de la matriz b. Sin
embargo, el número de filas de la matriz a puede ser cualquier número y el número de columnas de la
matriz b puede ser cualquier número siempre que multiplique a por b. Por ejemplo, el siguiente
código produce un producto escalar correcto:
a = np.matriz([[1,2,3],[4,5,6]])
b = np.matriz([[1,2,3],[3,4,5],[5,6,7]])
a.punto(b)
matriz([[22, 28, 34],
[49, 64, 79]])
CAPÍTULO 5 Realizar manipulaciones de datos esenciales usando Python 97
Machine Translated by Google
Tenga en cuenta que la salida contiene el número de filas que se encuentran en la matriz a y
el número de columnas que se encuentran en la matriz b. ¿Entonces, cómo funciona todo esto?
Para obtener el valor encontrado en la matriz de salida en el índice [0,0] de 22, suma los valores
de a[0,0]*b[0,0] (que es 1), a[0,1]* b[1,0] (que es 6) y a[0,2]*b[2,0] (que es 15) para obtener el
valor de 22. Las otras entradas funcionan exactamente de la misma manera.
Una ventaja de utilizar la clase de matriz NumPy es que algunas tareas se vuelven más sencillas.
Por ejemplo, la multiplicación funciona exactamente como esperabas. El siguiente código genera
un producto escalar utilizando la clase de matriz :
a = np.mat([[1,2,3],[4,5,6]])
b = np.mat([[1,2,3],[3,4,5],[5,6,7]])
a*b
matriz([[22, 28, 34],
[49, 64, 79]])
La salida con el operador * es la misma que usar la función de punto con una matriz. Este
ejemplo también señala que debes saber si estás usando una matriz o un objeto de matriz al
realizar tareas como multiplicar dos matrices.
Para realizar una multiplicación elemento por elemento usando dos objetos matriciales , debe
usar la función de multiplicación numpy .
Definición de operaciones matriciales avanzadas
Este libro le lleva a través de todo tipo de operaciones matriciales interesantes, pero algunas de
ellas se utilizan habitualmente, razón por la cual aparecen en este capítulo. Cuando se trabaja
con matrices, a veces se obtienen datos en una forma que no funciona con el algoritmo.
Afortunadamente, numpy viene con una función especial de remodelación que le permite poner
los datos en cualquier forma necesaria. De hecho, puedes usarlo para transformar un vector en
una matriz, como se muestra en el siguiente código:
cambiarIt = np.array([1,2,3,4,5,6,7,8])
cambialo
matriz([1, 2, 3, 4, 5, 6, 7, 8])
cambiarlo.reformar (2,4)
matriz([[1, 2, 3, 4],
[5, 6, 7, 8]])
98 PARTE 1 Primeros pasos
Machine Translated by Google
cambiarlo.reformar(2,2,2)
matriz([[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]]])
La forma inicial del cambio es un vector, pero el uso de la función de remodelación lo
convierte en una matriz. Además, puede darle forma a la matriz en cualquier cantidad de
dimensiones que funcionen con los datos. Sin embargo, debe proporcionar una forma que
se ajuste a la cantidad requerida de elementos. Por ejemplo, llamar a changeIt.reshape(2,3,2)
fallará porque no hay suficientes elementos para proporcionar una matriz de ese tamaño.
Es posible que encuentre dos operaciones matriciales importantes en algunas fórmulas de
algoritmos. Son la transpuesta y la inversa de una matriz. La transposición ocurre cuando una
matriz de forma nxm se transforma en una matriz mxn intercambiando las filas con las columnas.
La mayoría de los textos indican esta operación utilizando el superíndice T, como en AT. Verá
que esta operación se usa con mayor frecuencia para la multiplicación con el fin de obtener las
dimensiones correctas. Cuando trabaja con numpy, utiliza la función de transposición para realizar
el trabajo requerido. Por ejemplo, al comenzar con una matriz que tiene dos filas y cuatro
columnas, puede transponerla para que contenga cuatro filas con dos columnas cada una, como
se muestra en este ejemplo:
cambialo
matriz([[1, 2, 3, 4],
[5, 6, 7, 8]])
np.transpose(cambiarlo)
matriz([[1, 5],
[2, 6],
[3, 7],
[4, 8]])
La inversión de matrices se aplica a matrices de forma mxm, que son matrices cuadradas que
tienen el mismo número de filas y columnas. Esta operación es bastante importante porque
permite la resolución inmediata de ecuaciones que involucran multiplicación de matrices, como
y=bX, donde hay que descubrir los valores en el vector b.
Debido a que la mayoría de los números escalares (las excepciones incluyen el cero) tienen un
número cuya multiplicación da como resultado un valor de 1, la idea es encontrar una matriz
inversa cuya multiplicación dará como resultado una matriz especial llamada matriz identidad.
Para ver una matriz de identidad en numpy, use la función de identidad como esta:
np.identidad(4)
matriz([[ 1., 0., 0., 0.],
CAPÍTULO 5 Realizar manipulaciones de datos esenciales usando Python 99
Machine Translated by Google
[ 0., 1., 0., 0.],
[ 0., 0., 1., 0.],
[ 0., 0., 0., 1.]])
Tenga en cuenta que una matriz identidad contiene todos los unos en la diagonal. Encontrar el
inverso de un escalar es bastante fácil (el número escalar n tiene un inverso de n–1 , es decir, 1/
n). Es una historia diferente para una matriz. La inversión de matrices implica una gran cantidad
de cálculos. La inversa de una matriz A se indica como A–1. Cuando trabajas con numpy, usas la
función linalg.inv para crear una inversa. El siguiente ejemplo muestra cómo crear una inversa,
usarla para obtener un producto escalar y luego comparar ese producto escalar con la matriz
identidad usando la función allclose .
a = np.matriz([[1,2], [3,4]])
b = np.linalg.inv(a)
np.allclose(np.punto(a,b), np.identidad(2))
Verdadero
A veces, encontrar la inversa de una matriz es imposible. Cuando una matriz no se puede invertir,
se la denomina matriz singular o matriz degenerada. Las matrices singulares no son la norma;
son bastante raros.
Creando combinaciones de la manera correcta
Dar forma a los datos a menudo implica verlos de múltiples maneras. Los datos no son
simplemente una secuencia de números: presentan una secuencia significativa que, cuando se
ordena de la manera adecuada, transmite información al espectador. Crear las combinaciones
de datos correctas mediante la manipulación de secuencias de datos es una parte esencial para
lograr que los algoritmos hagan lo que usted quiere que hagan. Las siguientes secciones analizan
tres técnicas de modelado de datos: permutaciones, combinaciones y repeticiones.
Distinguir permutaciones
Cuando recibe datos sin procesar, aparecen en un orden específico. La orden puede representar
casi cualquier cosa, como el registro de un dispositivo de entrada de datos que monitorea algo
así como una línea de producción. Quizás los datos sean una serie de números que representan
la cantidad de productos fabricados en un momento determinado. La razón por la que recibe los
datos en un orden particular es importante, pero quizás ese orden no se preste para obtener el
resultado que necesita de un algoritmo. Quizás crear una permutación de datos, reordenar los
datos para que presenten una vista diferente, ayude a lograr el resultado deseado.
100 PARTE 1 Primeros pasos
Machine Translated by Google
Puede ver las permutaciones de varias maneras. Un método para ver una permutación es como una
presentación aleatoria del orden de la secuencia. En este caso, puede utilizar la función numpy
random.permutation , como se muestra aquí:
a = np.matriz([1,2,3])
np.permutación.aleatoria(a)
matriz([2, 3, 1])
Es probable que el resultado de su sistema varíe del que se muestra. Cada vez que ejecuta este
código, recibe un orden aleatorio diferente de la secuencia de datos, lo que resulta útil con algoritmos
que requieren que aleatorice el conjunto de datos para obtener los resultados deseados. Por ejemplo,
el muestreo es una parte esencial del análisis de datos y la técnica que se muestra es una forma
eficaz de realizar esta tarea.
Otra forma de ver el problema es la necesidad de obtener todas las permutaciones de un conjunto de
datos para poder probar cada una de ellas. Para realizar esta tarea, necesita importar el paquete
itertools . El siguiente código muestra una técnica que puede utilizar para obtener una lista de todas las
permutaciones de un vector en particular:
desde itertools importar permutaciones
a = np.matriz([1,2,3])
para p en permutaciones (a):
imprimir(p)
(1, 2, 3)
(1, 3, 2)
(2, 1, 3)
(2, 3, 1)
(3, 1, 2)
(3, 2, 1)
Para guardar la lista de conjuntos, siempre puede crear una lista en blanco y confiar en el anexo
función para agregar cada conjunto a la lista en lugar de imprimir los elementos uno a la vez, como se
muestra en el código. La lista resultante podría servir como entrada para un algoritmo diseñado para
trabajar con múltiples conjuntos. Puede leer más sobre itertools en https://docs.
python.org/3/library/itertools.html.
Combinaciones aleatorias
En algunos casos, no es necesario un conjunto de datos completo; Todo lo que realmente necesitas
son algunos de los miembros en combinaciones de una longitud específica. Por ejemplo, es posible
que tenga un conjunto de datos que contenga cuatro números y desee solo dos combinaciones de números.
CAPÍTULO 5 Realizar manipulaciones de datos esenciales usando Python 101
Machine Translated by Google
(La capacidad de obtener partes de un conjunto de datos es una función clave para generar un
gráfico completamente conectado, que se describe en la Parte 3 del libro). El siguiente código
muestra cómo obtener dichas combinaciones:
desde itertools importar combinaciones
a = np.matriz([1,2,3,4])
para peine en combinaciones(a, 2):
imprimir (peine)
(1, 2)
(1, 3)
(1, 4)
(2, 3)
(2, 4)
(3, 4)
La salida contiene todas las combinaciones posibles de dos números de a. Tenga en cuenta que
este ejemplo utiliza la función de combinaciones de itertools (la función de permutaciones aparece
en la sección anterior). Por supuesto, es posible que no necesites todas esas combinaciones;
quizás un subconjunto aleatorio de ellos funcionaría mejor. En este caso, puede confiar en la
función random.sample para que le ayude, como se muestra aquí:
piscina = []
para peine en combinaciones(a, 2):
piscina.append(peine)
muestra.aleatoria(grupo, 3)
[(1, 4), (3, 4), (1, 2)]
Las combinaciones precisas que vea como resultado variarán. Sin embargo, la idea es que haya
limitado su conjunto de datos de dos maneras. En primer lugar, no se utilizan todos los elementos
de datos todo el tiempo y, en segundo lugar, no se utilizan todas las combinaciones posibles de
esos elementos de datos. El efecto es crear un conjunto de elementos de datos de apariencia
relativamente aleatoria que puede utilizar como entrada para un algoritmo.
Otra variación de este tema es crear una lista completa pero aleatorizar el orden de los elementos.
El acto de aleatorizar el orden de la lista es barajar y se utiliza la función random.shuffle para
hacerlo. De hecho, Python proporciona una gran cantidad de métodos de aleatorización que puedes
ver en https://docs.python.org/3/library/
aleatorio.html. Muchos de los ejemplos posteriores de este libro también se basan en la aleatorización
para ayudar a obtener el resultado correcto de los algoritmos.
102 PARTE 1 Primeros pasos
Machine Translated by Google
Frente a repeticiones
Los datos repetidos pueden ponderar injustamente el resultado de un algoritmo, de modo que
se obtengan resultados inexactos. A veces se necesitan valores únicos para determinar el
resultado de una manipulación de datos. Afortunadamente, Python facilita la eliminación de
ciertos tipos de datos repetidos. Considere este ejemplo:
a = np.matriz([1,2,3,4,5,6,6,7,7,1,2,3])
b = np.array(lista(conjunto(a)))
b
matriz([1, 2, 3, 4, 5, 6, 7])
En este caso, a comienza con una variedad de números sin ningún orden en particular y con
muchas repeticiones. En Python, un conjunto nunca contiene datos repetidos. En consecuencia,
al convertir la lista de a en un conjunto y luego volver a una lista, y luego colocar esa lista en una
matriz, se obtiene un vector que no tiene repeticiones.
Obtener los resultados deseados
Usando recursividad
La recursividad es un método elegante para resolver muchos problemas informáticos que se
basa en la capacidad de una función de seguir llamándose a sí misma hasta que satisface una
condición particular. El término recursión en realidad proviene del verbo latino recurrir, que
significa volver corriendo.
Cuando usas la recursividad, resuelves un problema llamando a la misma función varias veces
pero modificando los términos bajo los cuales la llamas. La razón principal para usar la
recursividad es que proporciona una manera más fácil de resolver problemas cuando se trabaja
con algunos algoritmos porque imita la forma en que los resolvería un humano.
Desafortunadamente, la recursividad no es una herramienta fácil porque requiere cierto esfuerzo
para comprender cómo crear una rutina recursiva y puede causar problemas de falta de memoria
en su computadora si no establece algunas configuraciones de memoria. Las siguientes
secciones detallan cómo funciona la recursividad y le brindan un ejemplo de cómo funciona la recursividad en Pyt
Explicando la recursividad
Muchas personas tienen problemas al utilizar la recursividad porque no pueden visualizar
fácilmente cómo funciona. En la mayoría de los casos, llamas a una función de Python, hace algo y
CAPÍTULO 5 Realizar manipulaciones de datos esenciales usando Python 103
Machine Translated by Google
luego se detiene. Sin embargo, en la recursividad, llamas a una función de Python, hace algo y
luego se llama a sí misma repetidamente hasta que la tarea alcanza una condición específica,
pero todas esas llamadas anteriores siguen activas. Las llamadas se van desenrollando una a
la vez hasta que la primera llamada finalmente finaliza con la respuesta correcta, y este proceso
de desenredado es donde la mayoría de las personas encuentran un problema. La Figura 5­1
muestra cómo se ve la recursividad cuando se usa un diagrama de flujo.
FIGURA 5­1:
en la recursividad
proceso, una
función se
llama continuamente a
sí misma hasta que
cumple
una condición.
Observe el condicional en el centro. Para que la recursividad funcione, la función debe tener dicho
condicional o podría convertirse en un bucle sin fin. El condicional determina una de dos cosas:
» No se han cumplido las condiciones para finalizar la recursividad, por lo que la función debe llamarse a sí
misma nuevamente.
» Se han cumplido las condiciones para finalizar la recursividad, por lo que la función devuelve un
Valor final que se utiliza para calcular el resultado final.
104 PARTE 1 Primeros pasos
Machine Translated by Google
Cuando una función se llama a sí misma, no utiliza los mismos argumentos que se le pasaron.
Si usara continuamente los mismos argumentos, la condición nunca cambiaría y la recursividad
nunca terminaría. En consecuencia, la recursividad requiere que las llamadas posteriores a la
función cambien los argumentos de la llamada para acercar la función a una solución final.
Uno de los ejemplos de recursividad más comunes para todos los lenguajes de programación
es el cálculo de un factorial. Un factorial es la multiplicación de una serie de números entre un
punto inicial y un punto final en la que cada número de la serie es uno menos que el número
anterior. Por ejemplo, para calcular 5! (léase como factorial cinco) multiplicas 5 * 2 * 1. El
* 4 * 3
cálculo representa un ejemplo perfecto
y simple de recursividad. Aquí está el código Python
que puede utilizar para realizar el cálculo. (Puede encontrar este código en el archivo A4D;
05; Recursion.ipynb en el sitio Dummies como parte del código descargable; consulte la
Introducción para obtener más detalles).
definición factorial(n):
print("factorial llamado con n = ", str(n))
si n == 1 o n == 0:
print("Condición final cumplida.")
regresar 1
demás:
devolver n * factorial(n­1)
imprimir(factorial(5))
factorial llamado con n = 5
factorial llamado con n = 4
factorial llamado con n = 3
factorial llamado con n = 2
factorial llamado con n = 1
Condición final cumplida.
120
El código cumple la condición final cuando n == 1. Cada llamada sucesiva al factorial usa
factorial(n­1), lo que reduce el argumento inicial en 1. El resultado muestra cada llamada
sucesiva al factorial y el cumplimiento de la condición final. ¡El resultado, 120, es igual a 5!
(cinco factoriales).
Es importante darse cuenta de que no existe un solo método para utilizar la recursividad para
resolver un problema. Como ocurre con cualquier otra técnica de programación, puedes
encontrar todo tipo de formas de lograr lo mismo. Por ejemplo, aquí hay otra versión de la
recursividad factorial que usa menos líneas de código pero realiza efectivamente la misma tarea:
definición factorial(n):
print("factorial llamado con n = ", str(n))
CAPÍTULO 5 Realizar manipulaciones de datos esenciales usando Python 105
Machine Translated by Google
si norte > 1:
devolver n * factorial(n­1)
print("Condición final cumplida.")
regresar 1
imprimir(factorial(5))
factorial llamado con n = 5
factorial llamado con n = 4
factorial llamado con n = 3
factorial llamado con n = 2
factorial llamado con n = 1
Condición final cumplida.
120
Note la diferencia. En lugar de verificar la condición final, esta versión verifica la condición de continuación.
Mientras n sea mayor que 1, el código seguirá realizando llamadas recursivas. Aunque este código es
más corto que la versión anterior, también es menos claro porque ahora debes pensar en qué condición
finalizará la recursividad.
Eliminando la recursividad de llamadas de cola
Muchas formas de recursividad se basan en una llamada de cola. De hecho, el ejemplo de la sección
anterior sí lo hace. Una llamada de cola ocurre cada vez que la recursividad realiza una llamada a la
función como último paso antes de regresar. En la sección anterior, la línea de retorno n * factorial(n­1) es
la llamada final.
Las llamadas de cola no son necesariamente malas y representan la manera en que la mayoría de las
personas escriben rutinas recursivas. Sin embargo, el uso de una llamada de cola obliga a Python a
realizar un seguimiento de los valores de llamada individuales hasta que la recursividad se rebobine. Cada
llamada consume memoria. En algún momento, el sistema se quedará sin memoria y la llamada fallará, lo
que provocará que su algoritmo también falle. Dada la complejidad y los enormes conjuntos de datos que
utilizan algunos algoritmos hoy en día, las llamadas de cola pueden causar un problema considerable a
cualquiera que las utilice.
Con un poco de programación sofisticada, potencialmente puedes eliminar las llamadas finales
de tus rutinas recursivas. Puede encontrar una gran cantidad de técnicas realmente
sorprendentes en línea, como el uso de un trampolín, como se explica en http://blog.moertel.com/posts/2013­
06­12­recursión­a­iteración­4­trampolines.html. Sin embargo, el enfoque más sencillo a seguir cuando se
desea eliminar la recursividad es crear una alternativa iterativa que realice la misma tarea. Por ejemplo,
aquí hay una función factorial que utiliza iteración en lugar de recursividad para eliminar la posibilidad de
problemas de memoria:
106 PARTE 1 Primeros pasos
Machine Translated by Google
definición factorial(n):
print("factorial llamado con n = ", str(n))
resultado = 1
mientras norte > 1:
resultado = resultado * n
norte = norte ­ 1
print("El valor actual de n es ", str(n))
print("Condición final cumplida.")
resultado de retorno
imprimir(factorial(5))
factorial llamado con n = 5
El valor actual de n es 4
El valor actual de n es 3.
El valor actual de n es 2
El valor actual de n es 1
Condición final cumplida.
120
El flujo básico de esta función es el mismo que el de la función recursiva. Un bucle while
reemplaza la llamada recursiva, pero aún es necesario verificar la misma condición y continuar el
bucle hasta que los datos cumplan la condición. El resultado es el mismo. Sin embargo,
reemplazar la recursividad con iteración no es trivial en algunos casos, como se explora en el
ejemplo en http://blog.moertel.com/posts/2013­06­03­recursion­to­iteration­3.html .
Realizar tareas más rápidamente
Obviamente, lo ideal siempre es realizar las tareas lo más rápido posible. Sin embargo,
siempre es necesario sopesar cuidadosamente las técnicas que utiliza para lograrlo.
Intercambiar un poco de memoria para realizar una tarea más rápido es fantástico siempre
que tengas memoria de sobra. Los capítulos posteriores del libro exploran todo tipo de formas
de realizar tareas más rápido, pero puedes probar algunas técnicas esenciales sin importar
con qué tipo de algoritmo estés trabajando en un momento dado. Las siguientes secciones
exploran algunas de estas técnicas.
Considerando dividir y conquistar
Algunos problemas parecen abrumadores cuando los inicias. Tomemos, por ejemplo, escribir
un libro. Si consideras el libro completo, escribirlo es una tarea abrumadora.
Sin embargo, si divide el libro en capítulos y considera solo uno, el
CAPÍTULO 5 Realizar manipulaciones de datos esenciales usando Python 107
Machine Translated by Google
El problema parece un poco más factible. Por supuesto, un capítulo entero también puede
parecer un poco desalentador, por lo que divides la tarea en títulos de primer nivel, lo que
parece aún más factible, pero aún no lo suficiente. Los títulos de primer nivel podrían contener
títulos de segundo nivel y así sucesivamente hasta que hayas dividido lo más posible el
problema de escribir sobre un tema en artículos breves. Incluso un artículo breve puede
parecer demasiado difícil, por lo que hay que dividirlo en párrafos, luego en oraciones y
finalmente en palabras individuales. Escribir una sola palabra no es demasiado difícil.
Entonces, escribir un libro se reduce a escribir palabras individuales, muchas. Así es como
funciona divide y vencerás. Divide un problema en problemas más pequeños hasta que
encuentra un problema que puede resolver sin demasiados problemas.
Las computadoras también pueden utilizar el enfoque de divide y vencerás. Intentar resolver
un problema enorme con un conjunto de datos enorme podría llevar días, suponiendo que la
tarea sea factible. Sin embargo, al dividir el gran problema en partes más pequeñas, puede
resolverlo mucho más rápido y con menos recursos. Por ejemplo, al buscar una entrada en
una base de datos, no es necesario buscar en toda la base de datos si utiliza una base de
datos ordenada. Digamos que estás buscando la palabra Hola en la base de datos. Puedes
comenzar dividiendo la base de datos por la mitad (letras A
a M y las letras N a Z). El valor numérico de H en Hello (un valor de 72 cuando se usa una
tabla ASCII estándar) es menor que M (un valor de 77) en la apuesta alfa, por lo que miras
la primera mitad de la base de datos en lugar de la segunda. . Al dividir nuevamente la mitad
restante (letras de la A a la G y letras de la H a la M), encontrará que necesita la segunda
mitad del resto, que ahora es solo una cuarta parte de la base de datos. Con el tiempo, más
divisiones le ayudarán a encontrar precisamente lo que desea buscando sólo en una pequeña
fracción de toda la base de datos. A este enfoque de búsqueda se le llama búsqueda binaria.
El problema se convierte en uno de seguir estos pasos:
1. Divide el contenido en cuestión por la mitad.
2. Compara las claves del contenido con el término de búsqueda.
3. Elige la mitad que contiene la llave.
4. Repita los pasos 1 a 3 hasta que encuentre la clave.
La mayoría de los problemas de divide y vencerás siguen un enfoque similar, aunque algunos
de estos enfoques se vuelven bastante complicados. Por ejemplo, en lugar de simplemente
dividir la base de datos por la mitad, en algunos casos podría dividirla en tercios. Sin embargo,
el objetivo es el mismo en todos los casos: dividir el problema en una parte más pequeña y
determinar si puede resolver el problema utilizando sólo esa parte como caso generalizado.
Después de encontrar el caso generalizado que sabes cómo resolver, puedes usar esa pieza
para resolver también cualquier otra pieza. El siguiente código muestra una versión
extremadamente simple de una búsqueda binaria que supone que tiene la lista ordenada.
(Puede encontrar este código en el archivo A4D; 05; Binary Search.ipynb en el sitio Dummies
como parte del código descargable; consulte la Introducción para obtener más detalles).
108 PARTE 1 Primeros pasos
Machine Translated by Google
def búsqueda (lista de búsqueda, clave):
mid = int(len(searchList) / 2) print("Buscando
punto medio en ", str(searchList[mid]))
si medio == 0:
print("¡Clave no encontrada!") tecla
de retorno
clave elif == lista de búsqueda[medio]:
print("¡Clave encontrada!")
devuelve lista de búsqueda[medio]
clave elif > lista de búsqueda[mid]: print("la
lista de búsqueda ahora contiene ",
listabúsqueda[mid:len(listabúsqueda)])
búsqueda(listabúsqueda[mediados:len(listabúsqueda)], clave)
demás:
print("la lista de búsqueda ahora contiene ", lista de
búsqueda[0:media])
búsqueda(lista de búsqueda[0:media], clave)
unaLista = lista(rango(1, 21))
buscar(unaLista, 5)
El punto medio de búsqueda en 11
searchList ahora contiene [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
El punto medio de búsqueda en 6
searchList ahora contiene [1, 2, 3, 4, 5]
El punto medio de búsqueda en 3
searchList ahora contiene [3, 4, 5]
El punto medio de búsqueda en 4
searchList ahora contiene [4, 5]
Buscando el punto medio en 5 Key
Found!
Este enfoque recursivo de la búsqueda binaria comienza con una Lista que contiene los
números del 1 al 20. Busca un valor de 5 en una Lista. Cada iteración de la recursividad
comienza buscando el punto medio de la lista, la mitad y luego usando ese punto medio
para determinar el siguiente paso. Cuando la clave coincide con el punto medio, el valor
se encuentra en la lista y la recursividad finaliza.
CAPÍTULO 5 Realizar manipulaciones de datos esenciales usando Python 109
Machine Translated by Google
Tenga en cuenta que este ejemplo realiza una de dos llamadas recursivas. Cuando la clave es
mayor que el valor del punto medio de la lista existente, searchList[mid], el código llama a buscar
nuevamente con solo el lado derecho de la lista restante. En otras palabras, cada llamada a
buscar utiliza sólo la mitad de la lista encontrada en la llamada anterior. Cuando la clave es
menor o igual que searchList[mid], la búsqueda recibe la mitad izquierda de la lista existente.
Es posible que la lista no contenga un valor de búsqueda, por lo que siempre debe proporcionar
un método de escape para la recursividad o la pila se llenará, lo que generará un mensaje de
error. En este caso, el escape ocurre cuando mid == 0, lo que significa que no hay más lista de
búsqueda para buscar. Por ejemplo, si cambia search(aList, 5) a search(aList, 22), obtendrá el
siguiente resultado:
Buscando el punto medio en 11
searchList ahora contiene [11, 12, 13, 14, 15, 16, 17, 18,
19, 20]
Buscando el punto medio en 16
searchList ahora contiene [16, 17, 18, 19, 20]
Buscando el punto medio a los 18
searchList ahora contiene [18, 19, 20]
Buscando el punto medio en 19
searchList ahora contiene [19, 20]
Buscando el punto medio en 20
searchList ahora contiene [20]
Buscando el punto medio en 20
¡Clave no encontrada!
Tenga en cuenta también que el código busca la condición de escape antes de realizar cualquier
otro trabajo para garantizar que el código no cause un error inadvertidamente debido a la falta
de contenido de la lista de búsqueda . Cuando trabaje con recursividad, debe ser proactivo o
soportar las consecuencias más adelante.
Distinguir entre diferentes
soluciones posibles
La recursividad es parte de muchas soluciones de programación algorítmica diferentes, como
verá en los próximos capítulos. De hecho, en muchos casos es difícil escapar de la recursividad
porque un enfoque iterativo resulta no intuitivo, engorroso y requiere mucho tiempo. Sin
embargo, puedes crear varias versiones diferentes de la misma solución, cada una de las cuales
tiene sus propias características, defectos y virtudes.
110 PARTE 1 Primeros pasos
Machine Translated by Google
La solución que este capítulo no considera es la búsqueda secuencial, porque una búsqueda
secuencial generalmente lleva más tiempo que cualquier otra solución que pueda emplear.
En el mejor de los casos, una búsqueda secuencial requiere solo una comparación para completar
la búsqueda, pero en el peor de los casos, encontrará el elemento que desea como última
verificación. Como promedio, la búsqueda secuencial requiere (n+1)/2 comprobaciones u O(n)
tiempo para completarse.
La búsqueda binaria de la sección anterior funciona mucho mejor que una búsqueda secuencial.
Funciona en tiempo logarítmico u O (log n). En el mejor de los casos, solo se necesita una
verificación, como ocurre con una búsqueda secuencial, pero el resultado del ejemplo muestra que
incluso en el peor de los casos, donde el valor ni siquiera aparece en la lista, solo se requieren
seis comprobaciones. en lugar de las 21 comprobaciones que requeriría una búsqueda secuencial.
Este libro cubre una amplia variedad de algoritmos de búsqueda y clasificación porque la búsqueda
y la clasificación representan dos categorías principales de procesamiento informático. Piense en
cuánto tiempo dedica a buscar datos en Google cada día. En teoría, podrías pasar días enteros
sin hacer nada más que buscar datos. Las rutinas de búsqueda funcionan mejor con datos
ordenados, por lo que se ve la necesidad de rutinas de búsqueda y clasificación eficientes.
Afortunadamente, no es necesario pasar horas intentando descubrir qué rutinas de búsqueda y
clasificación funcionan mejor. Sitios como Big­O Cheat Sheet, http://bigocheatsheet.com/,
proporcionarle los datos necesarios para determinar qué solución funciona mejor.
Sin embargo, si observa únicamente los tiempos de rendimiento, los datos que recibe pueden
inducirlo a pensar erróneamente que una solución particular funcionará increíblemente bien para
su aplicación cuando en realidad no es así. También debe considerar el tipo de datos con los que
trabaja, la complejidad de crear la solución y muchos otros factores.
Es por eso que los ejemplos posteriores de este libro también consideran los pros y los contras de
cada enfoque: los peligros ocultos de elegir una solución que parece tener potencial y luego no
produce el resultado deseado.
CAPÍTULO 5 Realizar manipulaciones de datos esenciales usando Python 111
Machine Translated by Google
Machine Translated by Google
2 comprensión
la necesidad de ordenar
y buscar
Machine Translated by Google
EN ESTA PARTE . . .
Utilice varias estructuras de datos de Python.
Trabajar con árboles y gráficos.
Ordene los datos para que los algoritmos funcionen más rápido.
Busque datos para localizar con precisión la información correcta rápidamente.
Emplee técnicas de hash para crear índices de datos más pequeños.
Machine Translated by Google
EN ESTE CAPÍTULO
» Definir por qué los datos requieren estructura
» Trabajar con pilas, colas, listas y
diccionarios
» Usar árboles para organizar datos
» Usar gráficos para representar datos con
relaciones
Capítulo 6
Estructuración de datos
podría encontrar algunas partes faltantes o dañadas de alguna manera, o simplemente que
Los datos
procesar son
solotu
eso:
sin procesar.
está estructurado
ni limpiado de ninguna
manera.
Tú
nosin
funcionará
para
problema.
De No
hecho,
no estás completamente
seguro
de qué
lo obtienes porque está crudo.
Antes de poder hacer algo con la mayoría de los datos, debe estructurarlos de
alguna manera para poder comenzar a ver qué contienen (y, a veces, qué no
contienen). Estructurar datos implica organizarlos de alguna manera para que todos
los datos tengan los mismos atributos, apariencia y componentes. Por ejemplo,
puede obtener datos de una fuente que contenga fechas en forma de cadena y otra
fuente que utilice objetos de fecha. Para utilizar la información, debe hacer coincidir los tipos de dat
Las fuentes de datos también pueden estructurar los datos de manera diferente. Una fuente
puede tener el apellido y el nombre en un solo campo; otra fuente podría utilizar campos
individuales para la misma información. Una parte importante de la estructuración de datos es
la organización. No está cambiando los datos de ninguna manera, simplemente los está haciendo más útiles.
(Estructurar datos contrasta con remediar o dar forma a los datos, donde a veces
se cambian valores para convertir un tipo de datos a otro o se experimenta una
pérdida de precisión, como con las fechas, al moverse entre fuentes de datos).
Python proporciona acceso a una serie de estructuras organizativas de datos. El
libro utiliza estas estructuras, especialmente pilas, colas y diccionarios, para
muchos de los ejemplos. Cada estructura de datos proporciona un medio diferente
para trabajar con los datos y un conjunto diferente de herramientas para realizar
tareas como clasificar los datos en un orden particular. Este capítulo le presenta
los métodos de organización más comunes, incluidos árboles y gráficos (ambos
tan importantes que aparecen en sus propias secciones).
CAPÍTULO 6 Estructuración de datos 115
Machine Translated by Google
Determinar la necesidad de estructura
La estructura es un elemento esencial para que los algoritmos funcionen. Como se muestra en el
ejemplo de búsqueda binaria del Capítulo 5, implementar un algoritmo utilizando datos
estructurados es mucho más fácil que tratar de descubrir cómo interpretar los datos en el código.
Por ejemplo, el ejemplo de búsqueda binaria se basa en tener los datos ordenados. Intentar
realizar las comparaciones requeridas con datos no clasificados requeriría mucho más esfuerzo
y potencialmente resultaría imposible de implementar. Con todo esto en mente, debe considerar
los requisitos estructurales de los datos que utiliza con sus algoritmos, como se analiza en las
siguientes secciones.
Facilitando la visualización del contenido
Una necesidad esencial que se debe satisfacer como parte del trabajo con datos es comprender
el contenido de los datos. Un algoritmo de búsqueda funciona sólo cuando comprende el conjunto
de datos para saber qué buscar utilizando el algoritmo. Buscar palabras cuando el conjunto de
datos contiene números es una tarea imposible que siempre resulta en errores. Sin embargo, los
errores de búsqueda debidos a la falta de comprensión del contenido del conjunto de datos son
algo común incluso en los mejores motores de búsqueda. Los seres humanos hacen suposiciones
sobre el contenido de los conjuntos de datos que provocan que los algoritmos fallen. En
consecuencia, cuanto mejor pueda ver y comprender el contenido a través del formato
estructurado, más fácil será realizar con éxito tareas basadas en algoritmos.
Sin embargo, incluso mirar el contenido suele ser propenso a errores cuando se trata de humanos
y computadoras. Por ejemplo, si intenta buscar un número con formato de cadena cuando el
conjunto de datos contiene números con formato de números enteros, la búsqueda fallará. Las
computadoras no traducen automáticamente entre cadenas y números enteros como lo hacen
los humanos. De hecho, las computadoras ven todo como números y las cadenas son solo una
interpretación impuesta a los números por un programador. Por lo tanto, cuando busca "1" (la
cadena), la computadora lo ve como una solicitud del número 49 cuando usa caracteres ASCII.
Para encontrar el valor numérico 1, debes buscar un 1 como valor entero.
La estructura también le permite descubrir detalles de datos matizados. Por ejemplo, un número
de teléfono puede aparecer en el formulario (555)555­1212. Si realiza una búsqueda u otra tarea
algorítmica utilizando el formulario 1(555)555­1212, la búsqueda podría fallar debido a la adición
de un 1 al comienzo del término de búsqueda. Este tipo de problemas causan problemas
importantes porque la mayoría de la gente ve las dos formas como iguales, pero la computadora
no. La computadora ve dos formas completamente diferentes e incluso las ve con dos longitudes
diferentes. Intentar imponer una forma a los humanos rara vez funciona y generalmente resulta
en frustración que hace que el uso del algoritmo sea aún más difícil, por lo que la estructura
impuesta mediante la manipulación de datos se vuelve aún más importante.
116 PARTE 2 Comprender la necesidad de ordenar y buscar
Machine Translated by Google
Coincidencia de datos de varias fuentes
Interactuar con datos de una única fuente es un problema; interactuar con datos de varias fuentes
es otra muy distinta. Sin embargo, hoy en día los conjuntos de datos generalmente provienen de
más de una fuente, por lo que es necesario comprender las complicaciones que puede causar el
uso de múltiples fuentes de datos. Cuando trabaje con múltiples fuentes de datos, debe hacer lo
siguiente:
» Determine si ambos conjuntos de datos contienen todos los datos requeridos. Dos diseños­
Es poco probable que los investigadores creen conjuntos de datos que contengan exactamente
los mismos datos, en el mismo formato, del mismo tipo y en el mismo orden. En
consecuencia, debe considerar si los conjuntos de datos proporcionan los datos que necesita
o si necesita corregir los datos de alguna manera para obtener el resultado deseado, como
se analiza en la siguiente sección.
» Verifique ambos conjuntos de datos para detectar problemas de tipo de datos. Un conjunto de datos podría tener
fechas ingresadas como cadenas y otro podría tener fechas ingresadas como objetos de fecha reales.
Las inconsistencias entre tipos de datos causarán problemas a un algoritmo que espera datos
en una forma y los recibe en otra.
» Garantizar que todos los conjuntos de datos den el mismo significado a los elementos de datos. Datos
Los datos creados por una fuente pueden tener un significado diferente al de los datos
creados por otra fuente. Por ejemplo, el tamaño de un número entero puede variar según las
fuentes, por lo que es posible que vea un entero de 16 bits de una fuente y un entero de 32
bits de otra. Los valores más bajos tienen el mismo significado, pero el entero de 32 bits
puede contener valores más grandes, lo que puede causar problemas con el algoritmo. Las
fechas también pueden causar problemas porque a menudo dependen del almacenamiento de
tantos milisegundos desde una fecha determinada (como JavaScript, que almacena la cantidad
de milisegundos desde el 1 de enero de 1970 UTC). La computadora sólo ve números; los
humanos agregan significado a estos números para que las aplicaciones los interpreten de maneras específicas.
» Verificar los atributos de los datos. Los elementos de datos tienen atributos específicos, razón por la cual el
Capítulo 4 le explica todo acerca de cómo Python interpreta varios tipos de datos.
El capítulo 5 señala que esta interpretación puede cambiar cuando se usa numpy.
De hecho, descubrirá que los atributos de los datos cambian entre entornos y los
desarrolladores pueden cambiarlos aún más creando tipos de datos personalizados. Para
combinar datos de varias fuentes, debe comprender estos atributos para asegurarse de
interpretar los datos correctamente.
Cuanto más tiempo dedique a verificar la compatibilidad de los datos de cada una de las fuentes
que desea utilizar para un conjunto de datos, es menos probable que encuentre problemas al
trabajar con un algoritmo. Los problemas de incompatibilidad de datos no siempre aparecen
como errores absolutos. En algunos casos, una incompatibilidad puede causar otros problemas,
como resultados erróneos que parecen correctos pero proporcionan información engañosa.
CAPÍTULO 6 Estructuración de datos 117
Machine Translated by Google
Es posible que combinar datos de múltiples fuentes tampoco siempre signifique crear un nuevo
conjunto de datos que se parezca exactamente a los conjuntos de datos de origen. En algunos
casos, crea agregados de datos o realiza otras formas de manipulación para crear nuevos datos
a partir de los datos existentes. El análisis adopta todo tipo de formas, y algunas de las más
exóticas pueden producir errores terribles cuando se utilizan incorrectamente. Por ejemplo, una
fuente de datos podría proporcionar información general del cliente y una segunda fuente de
datos podría proporcionar hábitos de compra de los clientes. Las discrepancias entre las dos
fuentes pueden hacer coincidir a los clientes con información incorrecta sobre sus hábitos de
compra y causar problemas al intentar comercializar nuevos productos para estos clientes. Como
ejemplo extremo, considere lo que sucedería al combinar información de pacientes de varias
fuentes y crear entradas combinadas de pacientes en una nueva fuente de datos con todo tipo
de discrepancias. Un paciente sin antecedentes de una enfermedad en particular podría terminar
con registros que muestren el diagnóstico y la atención de la enfermedad.
Considerando la necesidad de remediación
Después de encontrar problemas con su conjunto de datos, debe solucionarlo para que funcione
correctamente con los algoritmos que utiliza. Por ejemplo, cuando trabaje con tipos de datos en
conflicto, debe cambiar los tipos de datos de cada fuente de datos para que coincidan y luego
crear la fuente de datos única utilizada con el algoritmo.
La mayor parte de esta remediación, aunque requiere mucho tiempo, es sencilla. Simplemente
debe asegurarse de comprender los datos antes de realizar cambios, lo que significa poder ver
el contenido en el contexto de lo que planea hacer con él.
Sin embargo, es necesario considerar qué hacer en dos casos especiales: duplicación de datos
y datos faltantes. Las siguientes secciones muestran cómo abordar estos problemas.
Lidiar con la duplicación de datos
Los datos duplicados se producen por varias razones. Algunas de ellas son obvias. Un usuario
podría ingresar los mismos datos más de una vez. Las distracciones hacen que las personas
pierdan su lugar en una lista o en ocasiones dos usuarios ingresan al mismo registro. Algunas de
las fuentes son menos obvias. La combinación de dos o más conjuntos de datos podría crear
múltiples registros cuando los datos aparecen en más de una ubicación. También puede crear
duplicaciones de datos al utilizar diversas técnicas de modelado de datos para crear nuevos
datos a partir de fuentes de datos existentes. Afortunadamente, paquetes como Pandas le
permiten eliminar datos duplicados, como se muestra en el siguiente ejemplo. (Puede encontrar
este código en el archivo A4D; 06; Remediation.ipynb en el sitio Dummies como parte del código
descargable; consulte la Introducción para obtener más detalles).
importar pandas como pd
df = pd.DataFrame({'A': [0,0,0,0,0,1,0],
'B': [0,2,3,5,0,2,0],
'C': [0,3,4,1,0,2,0]})
118 PARTE 2 Comprender la necesidad de ordenar y buscar
Machine Translated by Google
imprimir(df, "\n")
df = df.drop_duplicates()
imprimir(df)
ABC
0000
1023
2034
3051
4000
5122
6000
ABC
0000
1023
2034
3051
5122
La función drop_duplicates elimina los registros duplicados que se encuentran en las filas 4 y 6
en este ejemplo. Al leer sus datos de una fuente en un DataFrame de pandas, puede eliminar
rápidamente las entradas adicionales para que los duplicados no ponderen injustamente el
resultado de los algoritmos que utilice.
Lidiar con los valores perdidos
Los valores faltantes también pueden sesgar los resultados de la salida de un algoritmo. De
hecho, pueden hacer que algunos algoritmos reaccionen de manera extraña o incluso generen
un error. El punto es que los valores faltantes causan problemas con sus datos, por lo que debe
eliminarlos. Tiene muchas opciones cuando trabaja con valores faltantes. Por ejemplo, podría
simplemente establecerlos en un valor estándar, como 0 para números enteros. Por supuesto,
utilizar una configuración estándar también podría sesgar los resultados. Otro enfoque es
utilizar la media de todos los valores, lo que tiende a hacer que los valores faltantes no cuenten.
Usar una media es el enfoque adoptado en el siguiente ejemplo.
importar pandas como pd
importar numpy como np
df = pd.DataFrame({'A': [0,0,1,Ninguno],
'B': [1,2,3,4],
'C': [np.NAN,3,4,1]},
tipo d = int)
CAPÍTULO 6 Estructuración de datos 119
Machine Translated by Google
imprimir(df, "\n")
valores = pd.Series(df.mean(), dtype=int)
imprimir(valores, "\n")
df = df.fillna(valores)
imprimir(df)
AB
C
0
0 1 NaN
1
02
3
2
13
4
3 Ninguno 4
1
Un 0
2
B
C2
tipo de letra: int32
ABC
0012
1023
2134
3041
La función fillna le permite deshacerse de los valores faltantes, ya sea que no sean un número
(NAN) o simplemente falten (Ninguno). Puede proporcionar los valores de datos que faltan en
varias formas. Este ejemplo se basa en una serie que contiene la media de cada columna de
datos independiente (de forma muy parecida a como lo haría cuando trabaja con una base de
datos).
Tenga en cuenta que el código tiene cuidado de no introducir errores en la salida asegurándose
de que los valores sean del tipo de datos correcto. Normalmente, la función media genera valores
de punto flotante, pero puedes forzar que la serie que completa tenga el tipo correcto.
En consecuencia, la salida no sólo carece de valores faltantes sino que también contiene valores
del tipo correcto.
Comprender otras cuestiones de remediación
La remediación puede adoptar otras formas. A veces un usuario proporciona datos inconsistentes
o incorrectos. Las aplicaciones no siempre aplican reglas de entrada de datos, por lo que los
usuarios pueden ingresar nombres de estado o región incorrectos. También se producen errores
de ortografía. A veces los valores están fuera de rango o son simplemente imposibles en una
situación determinada. Es posible que no siempre puedas limpiar tus datos por completo en el
primer intento. A menudo, te das cuenta de un problema al ejecutar el algoritmo y observar que los resultados
120 PARTE 2 Comprender la necesidad de ordenar y buscar
Machine Translated by Google
están sesgados de alguna manera o que el algoritmo no funciona en absoluto (incluso si funcionó en un
subconjunto de datos). En caso de duda, verifique sus datos para detectar posibles necesidades de
solución.
Apilar y apilar datos en orden
Python proporciona una serie de metodologías de almacenamiento, como se analiza en el Capítulo 4.
Como ya vio en el Capítulo 5 y en este capítulo, los paquetes a menudo ofrecen métodos de
almacenamiento adicionales. Tanto NumPy como Pandas ofrecen alternativas de almacenamiento que
podría considerar al resolver diversos problemas de estructuración de datos.
Un problema común del almacenamiento de datos no es sólo el hecho de que necesita almacenar los
datos, sino que debe almacenarlos en un orden particular para poder acceder a ellos cuando sea
necesario. Por ejemplo, es posible que desee asegurarse de que el primer elemento que coloque en una
pila de elementos para procesar sea también el primer elemento que realmente procese. Teniendo en
cuenta esta cuestión del ordenamiento de los datos, las siguientes secciones describen los métodos
estándar de Python para garantizar un almacenamiento ordenado de los datos que le permitan tener una
disposición de procesamiento específica.
Realizar pedidos en pilas
Una pila proporciona almacenamiento de datos de último en entrar/primero en salir (LIFO). El paquete
NumPy proporciona una implementación de pila real. Además, Pandas asocia pilas con objetos como
DataFrame . Sin embargo, ambos paquetes ocultan los detalles de implementación de la pila, y ver cómo
funciona una pila realmente ayuda. En consecuencia, el siguiente ejemplo implementa una pila utilizando
una lista estándar de Python. (Puede encontrar este código en el archivo A4D; 06; Stacks, Queues, and
Dictionaries.ipynb en el sitio Dummies como parte del código descargable; consulte la Introducción para
obtener más detalles).
Mi pila = []
Tamaño de pila = 3
def Pila de visualización():
print("La pila contiene actualmente:")
para artículo en MyStack:
imprimir (artículo)
def Empujar(Valor):
si len(MiPila) <Tamaño de Pila:
Mi pila.append(Valor)
CAPÍTULO 6 Estructuración de datos 121
Machine Translated by Google
demás:
print("¡La pila está llena!")
def Pop(): si
len(MyStack) > 0: print("Popping:
", MyStack.pop())
demás:
print("La pila está vacía.")
Empujar(1)
Empujar(2)
Empujar(3)
Pila de visualización()
Empujar(4)
Estallido()
Pila de visualización()
Estallido()
Estallido()
Estallido()
La pila contiene actualmente:
1
2
3
¡La pila está llena!
Estallido: 3
La pila contiene actualmente:
1
2
Estallando: 2
Estallando: 1 La
pila está vacía.
El ejemplo garantiza que la pila mantenga la integridad de los datos y funcione con ellos en el
orden esperado. El código se basa en una simple manipulación de listas , pero es eficaz al
proporcionar una representación de pila que puede utilizar para cualquier necesidad.
Las listas de Python son listas ordenadas de valores de datos que son fáciles e intuitivas de usar.
Desde la perspectiva de un algoritmo, a menudo no funcionan bien porque almacenan los
elementos de la lista en la memoria de la computadora y acceden a ellos mediante un índice y
punteros de memoria (un número que proporciona la dirección de memoria de los datos). Funcionan
exactamente como lo hace un índice de libros o un paquete. Las listas no tienen conocimiento de su contenido.
122 PARTE 2 Comprender la necesidad de ordenar y buscar
Machine Translated by Google
Cuando su aplicación realiza una solicitud de datos, la lista escanea todos sus elementos, lo cual es
aún más lento. Cuando los datos están dispersos en la memoria de su computadora, las listas deben
recopilar los datos de cada ubicación individualmente y ralentizar aún más el acceso.
Usando colas
A diferencia de las pilas, las colas son estructuras de datos de primero en entrar/primero en salir
(FIFO). Al igual que con las pilas, puedes encontrar implementaciones predefinidas en muchos
paquetes, incluidos NumPy y Pandas. Afortunadamente, también puedes encontrar una implementación
de cola específica en Python, que encontrarás demostrada en el siguiente código:
cola de importación
MiCola = cola.Cola(3)
print("Cola vacía: ", MiCola.vacía())
MiCola.put(1)
MiCola.put(2)
MiCola.put(3)
print("Cola llena: ", MiCola.full())
print("Haciendo estallar: ", MyQueue.get())
print("Cola llena: ", MiCola.full())
print("Haciendo estallar: ", MyQueue.get())
print("Haciendo estallar: ", MyQueue.get())
print("Cola vacía: ", MiCola.vacía())
Cola vacía: Verdadero
Cola llena: Verdadero
Estallido: 1
Cola llena: Falso
Estallido: 2
Estallido: 3
Cola vacía: Verdadero
Usar la cola incorporada requiere mucho menos código que construir una pila desde cero usando una
lista, pero observe cómo los dos difieren en el resultado. El ejemplo de la pila inserta 1, 2 y 3 en la
pila, por lo que el primer valor extraído de la pila es 3. Sin embargo, en este ejemplo, insertar 1, 2 y 3
en la cola da como resultado un primer valor extraído de 1.
CAPÍTULO 6 Estructuración de datos 123
Machine Translated by Google
Encontrar datos usando diccionarios
Crear y usar un diccionario es muy parecido a trabajar con una lista excepto que ahora debes definir
un par de clave y valor. La gran ventaja de esta estructura de datos es que los diccionarios pueden
proporcionar acceso rápidamente a elementos de datos específicos utilizando la clave.
Existen límites en cuanto a los tipos de claves que puede utilizar. Estas son las reglas especiales
para crear una clave:
» La clave debe ser única. Cuando ingresa una clave duplicada, gana la información que se
encuentra en la segunda entrada; la primera entrada reemplaza a la segunda.
» La clave debe ser inmutable. Esta regla significa que puedes usar cadenas, números
o tuplas para la clave. Sin embargo, no puede utilizar una lista como clave.
La diferencia entre valores mutables e inmutables es que los valores inmutables no
pueden cambiar. Para cambiar el valor de una cadena, por ejemplo, Python en
realidad crea una nueva cadena que contiene el nuevo valor y le da a la nueva cadena
el mismo nombre que la anterior. Luego destruye la cuerda vieja.
Los diccionarios de Python son la implementación de software de una estructura de datos llamada
tabla hash, una matriz que asigna claves a valores. El Capítulo 7 explica los hashes en detalle y
cómo su uso puede ayudar a que los diccionarios funcionen más rápido. No tiene restricciones
sobre los valores que proporciona. Un valor puede ser cualquier objeto de Python, por lo que puede
utilizar un diccionario para acceder a un registro de empleado u otros datos complejos. El siguiente
ejemplo le ayudará a comprender mejor cómo utilizar los diccionarios:
Colores = {"Sam": "Azul", "Amy": "Rojo", "Sarah": "Amarillo"}
imprimir(Colores["Sarah"])
imprimir(Colores.claves())
para artículo en Colors.keys():
print("A {0} le gusta el color {1}."
.formato(Artículo, Colores[Artículo]))
Colores["Sarah"] = "Púrpura"
Colores.update({"Harry": "Naranja"})
del Colores["Sam"]
imprimir (colores)
Amarillo
dict_keys(['Sarah', 'Amy', 'Sam'])
A Sarah le gusta el color amarillo.
A Amy le gusta el color rojo.
124 PARTE 2 Comprender la necesidad de ordenar y buscar
Machine Translated by Google
A Sam le gusta el color azul.
{'Harry': 'Naranja', 'Sarah': 'Púrpura', 'Amy': 'Rojo'}
Como puede ver, un diccionario siempre tiene un par de clave y valor separados entre sí por dos puntos
(:). En lugar de utilizar un índice para acceder a valores individuales, utiliza la clave. La función de teclas
especiales le permite obtener una lista de teclas que puede manipular de varias maneras. Por ejemplo,
puede utilizar las claves para realizar un procesamiento iterativo de los valores de datos que contiene el
diccionario.
Los diccionarios son un poco como tablas individuales dentro de una base de datos. Puede actualizar,
agregar y eliminar registros en un diccionario como se muestra. La función de actualización puede
sobrescribir o agregar nuevas entradas al diccionario.
Trabajar con árboles
La estructura de un árbol se parece mucho al objeto físico del mundo natural. El uso de árboles le ayuda
a organizar los datos rápidamente y encontrarlos en menos tiempo que el uso de otras técnicas de
almacenamiento de datos. Es común encontrar árboles que se utilizan para rutinas de búsqueda y
clasificación, pero también tienen muchos otros propósitos. Las siguientes secciones le ayudarán a
comprender los árboles en un nivel básico. Encontrará árboles utilizados en muchos de los ejemplos de
los próximos capítulos.
Comprender los conceptos básicos de los árboles.
Construir un árbol funciona de manera muy similar a construir un árbol en el mundo físico. Cada elemento
que agrega al árbol es un nodo. Los nodos se conectan entre sí mediante enlaces. La combinación de
nodos y enlaces forma una estructura que se parece mucho a un árbol, como se muestra en la Figura 6­1.
Tenga en cuenta que el árbol tiene un solo nodo raíz, al igual que un árbol físico. El nodo raíz proporciona
el punto de partida para los distintos tipos de procesamiento que realiza.
Conectadas al nodo raíz hay ramas u hojas. Un nodo hoja es siempre un punto final del árbol. Los nodos
de rama sostienen otras ramas u hojas.
El tipo de árbol que se muestra en la Figura 6­1 es un árbol binario porque cada nodo tiene, como máximo,
dos conexiones.
Al observar el árbol, la Rama B es hija del nodo Raíz. Esto se debe a que el nodo raíz aparece primero
en la lista. La Hoja E y la Hoja F son hijas de la Rama B, lo que convierte a la Rama B en la madre de la
Hoja E y la Hoja F. La relación entre los nodos es importante porque las discusiones sobre árboles a
menudo consideran la relación hijo/padre entre los nodos. Sin estos términos, las discusiones sobre
árboles podrían volverse bastante confusas.
CAPÍTULO 6 Estructuración de datos 125
Machine Translated by Google
FIGURA 6­1:
Un árbol en
Python se parece
mucho a la
alternativa física.
Construyendo un árbol
Python no viene con un objeto de árbol incorporado. Debe crear su propia implementación o utilizar un
árbol suministrado con un paquete. Una implementación básica de un árbol requiere que usted cree
una clase para contener el objeto de datos del árbol. El siguiente código muestra cómo se puede crear
una clase de árbol básica. (Puede encontrar este código en el archivo A4D; 06; Trees.ipynb en el sitio
Dummies como parte del código descargable; consulte la Introducción para obtener más detalles).
clase árbol binario:
def __init__(self, nodeData, izquierda=Ninguno, derecha=Ninguno):
self.nodeData = nodoData
self.left = izquierda
self.right = correcto
def __str__(yo):
devolver cadena (self.nodeData)
Todo lo que hace este código es crear un objeto de árbol básico que define los tres elementos que
debe incluir un nodo: almacenamiento de datos, conexión izquierda y conexión derecha. Como los
nodos hoja no tienen conexión, el valor predeterminado para izquierda y derecha es Ninguno. La clase
también incluye un método para imprimir el contenido de nodeData para que pueda
vea qué datos almacena el nodo.
El uso de este árbol simple requiere que no intentes almacenar nada a la izquierda o a la derecha.
que no sea una referencia a otro nodo. De lo contrario, el código fallará porque no
126 PARTE 2 Comprender la necesidad de ordenar y buscar
Machine Translated by Google
No hay ninguna captura de errores. La entrada nodeData puede contener cualquier valor. El
siguiente código muestra cómo utilizar la clase binarioTree para construir el árbol que se muestra
en la Figura 6­1:
árbol = árbol binario ("raíz")
RamaA = árbol binario("Rama A")
RamaB = árbol binario ("Rama B")
árbol.izquierda = RamaA
árbol.derecha = RamaB
HojaC = árbol binario("Hoja C")
HojaD = árbol binario("Hoja D")
HojaE = árbol binario("Hoja E")
HojaF = árbol binario("Hoja F")
RamaA.izquierda = HojaC
RamaA.derecha = HojaD
RamaB.izquierda = HojaE
RamaB.derecha = HojaF
Tiene muchas opciones al construir un árbol, pero construirlo de arriba hacia abajo (como se
muestra en este código) o de abajo hacia arriba (en el que construye las hojas primero) son dos
métodos comunes. Por supuesto, en este momento no se sabe realmente si el árbol realmente
funciona. Atravesar el árbol significa comprobar los enlaces y verificar que realmente se conectan
como cree que deberían. El siguiente código muestra cómo utilizar la recursividad (como se
describe en el Capítulo 5) para recorrer el árbol que acaba de construir.
def atravesar(árbol):
si árbol.izquierda! = Ninguno:
atravesar (árbol.izquierda)
si árbol.derecho! = Ninguno:
recorrer(árbol.derecha)
imprimir(árbol.nodeData)
atravesar (árbol)
Hoja C
Hoja D
Sucursal A
Hoja E
Hoja F
Sucursal B
Raíz
CAPÍTULO 6 Estructuración de datos 127
Machine Translated by Google
Como muestra el resultado, la función transversal no imprime nada hasta que llega a la primera hoja.
Luego imprime ambas hojas y el padre de esas hojas. El recorrido sigue primero la rama izquierda y
luego la rama derecha. El nodo raíz es el último.
Existen diferentes tipos de estructuras de almacenamiento de datos. Aquí hay una lista rápida de los
tipos de estructuras que se encuentran comúnmente:
» Árboles equilibrados: Un tipo de árbol que mantiene una estructura equilibrada mediante
la reorganización para que pueda proporcionar tiempos de acceso reducidos. El
número de elementos del tamaño izquierdo difiere del número del lado derecho
como máximo en uno.
» Árboles desequilibrados: un árbol que coloca nuevos elementos de datos donde sea
necesario en el árbol sin tener en cuenta el equilibrio. Este método de agregar
elementos acelera la construcción del árbol pero reduce la velocidad de acceso al buscar u ordenar.
» Heaps: Un árbol sofisticado que permite la inserción de datos en la estructura del árbol.
El uso de la inserción de datos agiliza la clasificación. Puede clasificar además estos
árboles como montones máximos y montones mínimos, según la capacidad del
árbol para proporcionar inmediatamente el valor máximo o mínimo presente en el árbol.
Más adelante en el libro encontrará algoritmos que utilizan árboles equilibrados, árboles desequilibrados
y montones. Por ejemplo, el Capítulo 9 analiza el algoritmo de Dijkstra y el Capítulo 14 analiza la
codificación de Huffman. Como parte de estas discusiones, el libro proporciona imágenes y código para
explicar cómo funciona cada estructura de datos y su papel en el funcionamiento del algoritmo.
Representar relaciones en un gráfico
Los gráficos son otra forma de estructura de datos común utilizada en algoritmos. Ves gráficos utilizados
en lugares como mapas para GPS y todo tipo de otros lugares donde el enfoque de arriba hacia abajo
de un árbol no funciona. Las siguientes secciones describen los gráficos con más detalle.
Más allá de los árboles
Un gráfico es una especie de extensión de árbol. Al igual que con los árboles, tienes nodos que se
conectan entre sí para crear relaciones. Sin embargo, a diferencia de los árboles binarios, un gráfico
puede tener más de una o dos conexiones. De hecho, los nodos del gráfico suelen tener una multitud
de conexiones. Sin embargo, para simplificar las cosas, considere el gráfico que se muestra en la Figura
6­2.
128 PARTE 2 Comprender la necesidad de ordenar y buscar
Machine Translated by Google
FIGURA 6­2:
Nodos gráficos
pueden conectarse
entre sí de
innumerables maneras.
En este caso, la gráfica crea un anillo donde A se conecta tanto con B como con F. Sin embargo,
no tiene por qué ser así. A podría ser un nodo desconectado o también podría conectarse a C.
Un gráfico muestra la conectividad entre nodos de una manera que resulta útil para definir
relaciones complejas.
Los gráficos también añaden algunos giros nuevos en los que quizás no hayas pensado antes.
Por ejemplo, un gráfico puede incluir el concepto de direccionalidad. A diferencia de un árbol,
que tiene relaciones padre/hijo, un nodo gráfico puede conectarse a cualquier otro nodo con una
dirección específica en mente. Piensa en las calles de una ciudad. La mayoría de las calles son
bidireccionales, pero algunas son calles de un solo sentido que permiten el movimiento en una
sola dirección.
Es posible que la presentación de una conexión gráfica en realidad no refleje las realidades del
gráfico. Un gráfico puede designar un peso para una conexión particular. El peso podría definir
la distancia entre dos puntos, definir el tiempo necesario para recorrer la ruta o proporcionar otro
tipo de información.
CAPÍTULO 6 Estructuración de datos 129
Machine Translated by Google
Construyendo gráficos
La mayoría de los desarrolladores utilizan diccionarios (o, a veces, listas) para crear gráficos.
El uso de un diccionario facilita la creación del gráfico porque la clave es el nombre del nodo
y los valores son las conexiones para ese nodo. Por ejemplo, aquí hay un diccionario que crea
el gráfico que se muestra en la Figura 6­2. (Puede encontrar este código en el archivo A4D;
06; Graphs.ipynb en el sitio Dummies como parte del código descargable; consulte la
Introducción para obtener más detalles).
gráfico = {'A': ['B', 'F'],
'B': ['A', 'C'],
'C': ['B', 'D'],
'D': ['C', 'E'],
'E': ['D', 'F'],
'F': ['E', 'A']}
Este diccionario refleja la naturaleza bidireccional del gráfico de la Figura 6­2. Podría definir
con la misma facilidad conexiones unidireccionales o proporcionar nodos sin ninguna conexión.
Sin embargo, el diccionario funciona bastante bien para este propósito y lo verás utilizado en
otras áreas del libro. Ahora es el momento de recorrer el gráfico usando el siguiente código:
def find_path(gráfico, inicio, fin, ruta=[]):
camino = camino + [inicio]
si inicio == final:
imprimir("Final")
vía de retorno
para nodo en gráfico[inicio]:
print("Comprobando Nodo ", nodo)
si el nodo no está en la ruta:
print("Ruta hasta ahora ", ruta)
newp = find_path(gráfico, nodo, final, ruta)
si es nuevo:
volver nuevo
find_path(gráfico, 'B', 'E')
Comprobando el nodo A
Camino hasta ahora ['B']
Comprobando el nodo B
130 PARTE 2 Comprender la necesidad de ordenar y buscar
Machine Translated by Google
Comprobando el nodo F
Camino hasta ahora ['B', 'A']
Comprobando el nodo E
Ruta hasta ahora ['B', 'A', 'F']
Finalizando
['B', 'A', 'F', 'E']
Los capítulos posteriores analizan cómo encontrar el camino más corto. Por ahora, el código sólo
encuentra una ruta. Comienza construyendo el camino nodo por nodo. Como ocurre con todas las
rutinas recursivas, ésta requiere una estrategia de salida, que consiste en que cuando el valor inicial
coincida con el valor final , la ruta finaliza.
Debido a que cada nodo en el gráfico puede conectarse a múltiples nodos, necesita un bucle for para
verificar cada una de las conexiones potenciales. Cuando el nodo en cuestión ya aparece en la ruta,
el código lo omite. De lo contrario, el código rastrea la ruta actual y llama recursivamente a find_path
para localizar el siguiente nodo en la ruta.
CAPÍTULO 6 Estructuración de datos 131
Machine Translated by Google
Machine Translated by Google
EN ESTE CAPÍTULO
» Realizar clasificaciones usando
Mergesort y Quicksort
» Realizar búsquedas utilizando árboles y
el montón
» Considerando los usos del hash y
diccionarios
Capítulo 7
Organizar y
Buscando datos
Todo, desde los datos necesarios para que el negocio funcione hasta la guía nutricional que se
Los datos
te rodean
el tiempo.
Dedehecho,
no puedes
escapar
de ello.de datos
encuentra
en eltodo
costado
de la caja
cereal, realmente
se basa en datos.
Las cuatro
operaciones
Las funciones son crear, leer, actualizar y eliminar (CRUD), que se centran en la necesidad de
acceder a los datos que necesita para realizar casi todas las tareas de la vida de forma rápida y sencilla.
Por eso es esencial contar con los medios para organizar y buscar datos de diversas
maneras. A menos que pueda acceder a los datos cuando lo desee y de la manera que
desee, el CRUD necesario para que su negocio funcione se volverá bastante complicado.
En consecuencia, este es un capítulo especialmente importante para todos aquellos que
quieran hacer brillar una aplicación.
La primera sección de este capítulo se centra en la clasificación de datos. Colocar los datos en un
orden que facilite la realización de operaciones CRUD es importante porque cuanto menos código
necesite para que el acceso a los datos funcione, mejor. Además, aunque ordenar los datos puede
no parecer particularmente importante, los datos ordenados hacen que las búsquedas sean
considerablemente más rápidas, siempre que la clasificación coincida con la búsqueda. La
clasificación y la búsqueda van juntas: usted clasifica los datos de una manera que agiliza la búsqueda.
La segunda sección del capítulo analiza la búsqueda. No le sorprenderá saber
que hay muchas formas diferentes de buscar datos. Algunas de estas técnicas
son más lentas que otras; algunos tienen atributos que los hacen
CAPÍTULO 7 Organización y búsqueda de datos 133
Machine Translated by Google
atractivo para los desarrolladores. El hecho es que no existe una estrategia de búsqueda perfecta, pero la exploración
de dicho método continúa.
La última sección del capítulo analiza el hash y los diccionarios. El uso de la indexación hace que la clasificación y la
búsqueda sean significativamente más rápidas, pero también conlleva compensaciones que es necesario considerar
(como el uso de recursos adicionales). Un índice es una especie de puntero o dirección. No son los datos, pero
apuntan a los datos, de la misma manera que su dirección apunta a su casa. Una búsqueda manual cuadra por
cuadra de su casa en la ciudad llevaría mucho tiempo porque la persona que lo busca tendría que preguntarle a cada
persona en cada dirección si usted se encuentra allí, pero encontrar su dirección en la guía telefónica y entonces
utilizar esa dirección para localizar tu domicilio es mucho más rápido.
Ordenar datos usando
Mergesort y Quicksort
La clasificación es uno de los elementos esenciales al trabajar con datos. En consecuencia, a lo largo de los años, a
mucha gente se le han ocurrido muchas formas diferentes de ordenar los datos. Todas estas técnicas dan como
resultado datos ordenados, pero algunas funcionan mejor que otras y algunas funcionan excepcionalmente bien para
tareas específicas. Las siguientes secciones le ayudarán a comprender la necesidad de realizar búsquedas y a
considerar las distintas opciones de búsqueda.
Definir por qué es importante ordenar los datos
Se puede argumentar a favor de no ordenar los datos. Después de todo, los datos siguen siendo accesibles, incluso
si no los ordena, y ordenarlos lleva tiempo. Por supuesto, el problema con los datos no clasificados es el mismo
problema que ese cajón de basura en su cocina (o dondequiera que tenga su cajón de basura, suponiendo que pueda
encontrarlo). Buscar cualquier cosa en el cajón de la basura lleva mucho tiempo porque ni siquiera puedes empezar
a adivinar dónde encontrar algo. En lugar de simplemente acercarte y tomar lo que deseas, debes sacar una gran
cantidad de otros elementos que no deseas en un esfuerzo por encontrar el elemento que necesitas.
Desafortunadamente, es posible que el artículo que necesitas no esté en el cajón de basura; es posible que lo hayas
tirado o lo hayas guardado en un cajón diferente.
El cajón de basura de su casa es como datos desordenados en su sistema. Cuando los datos no están ordenados,
necesita buscar un elemento a la vez y ni siquiera sabe si encontrará lo que necesita sin buscar primero todos los
elementos del conjunto de datos. Es una forma frustrante de trabajar con datos. El ejemplo de búsqueda binaria en la
sección “Considerando dividir y vencerás” del Capítulo 5 señala bastante bien la necesidad de ordenar. Imagínese
intentar encontrar un elemento en una lista sin ordenarlo primero.
Cada búsqueda se convierte en una búsqueda secuencial que requiere mucho tiempo.
134 PARTE 2 Comprender la necesidad de ordenar y buscar
Machine Translated by Google
Por supuesto, no basta con ordenar los datos. Si tiene una base de datos de empleados ordenada por
apellido, pero necesita buscar un empleado por fecha de nacimiento, la clasificación no es útil.
(Supongamos que desea encontrar todos los empleados que cumplen años en un día determinado).
Para encontrar la fecha de nacimiento que necesita, aún debe buscar en todo el conjunto de datos, un
elemento a la vez. En consecuencia, la clasificación debe centrarse en una necesidad particular. Sí,
necesitaba la base de datos de empleados ordenada por departamento en un momento y por apellido
en otro momento, pero ahora necesita ordenarla por fecha de nacimiento para poder utilizar el conjunto
de datos de manera efectiva.
La necesidad de mantener varios órdenes de clasificación para los mismos datos es la razón por la que
los desarrolladores crearon índices. Ordenar un índice pequeño es más rápido que ordenar todo el
conjunto de datos. El índice mantiene un orden de datos específico y apunta al conjunto de datos
completo para que pueda encontrar lo que necesita extremadamente rápido. Al mantener un índice para
cada requisito de clasificación, puede reducir de manera efectiva el tiempo de acceso a los datos y
permitir que varias personas accedan a los datos al mismo tiempo en el orden en que necesitan acceder
a ellos. La sección "Confiar en Hashing", más adelante en este capítulo, le brinda una idea de cómo
funciona la indexación y por qué realmente la necesita en algunos casos, a pesar del tiempo y los
recursos adicionales necesarios para mantener los índices.
Hay muchas formas disponibles de categorizar los algoritmos de clasificación. Una de estas formas es
la velocidad del tipo. Al considerar qué tan efectivo es un algoritmo de clasificación particular para
organizar los datos, los puntos de referencia de tiempo generalmente consideran dos factores:
» Comparaciones: para mover datos de una ubicación en un conjunto de datos a otra, necesita
saber dónde moverlos, lo que significa comparar los datos de destino con otros datos en
el conjunto de datos. Tener menos comparaciones significa un mejor rendimiento.
» Intercambios: dependiendo de cómo se escriba un algoritmo, es posible que los datos no
lleguen a su ubicación final en el conjunto de datos en el primer intento. En realidad, los datos
podrían moverse varias veces. La cantidad de intercambios afecta considerablemente la
velocidad porque ahora en realidad estás moviendo datos de una ubicación a otra en la memoria.
Menos intercambios y más pequeños (como cuando se utilizan índices) significan un
mejor rendimiento.
Ordenar datos ingenuamente
Ordenar datos ingenuamente significa ordenarlos utilizando métodos de fuerza bruta, sin tener en cuenta
en absoluto hacer ningún tipo de conjetura sobre dónde deberían aparecer los datos en la lista. Además,
estas técnicas tienden a funcionar con todo el conjunto de datos en lugar de aplicar enfoques que
probablemente reducirían el tiempo de clasificación (como la técnica de divide y vencerás descrita en el
Capítulo 5). Sin embargo, estas búsquedas también son relativamente fáciles de entender y utilizan los
recursos de manera eficiente. En consecuencia, no debes descartarlos por completo. Aunque muchas
búsquedas entran en esta categoría, las siguientes secciones analizan los dos enfoques más populares.
CAPÍTULO 7 Organización y búsqueda de datos 135
Machine Translated by Google
Usando una clasificación de selección
El tipo de selección reemplazó a un predecesor, el tipo de burbuja, porque tiende a proporcionar un mejor
rendimiento que el tipo de burbuja. Aunque ambas clasificaciones tienen una velocidad de clasificación en el peor
de los casos de O(n2), la clasificación por selección realiza menos intercambios. Una clasificación por selección
funciona de dos maneras: busca el elemento más pequeño de la lista y lo coloca al frente de la lista (asegurándose
de que el elemento esté en su ubicación correcta) o busca el elemento más grande y lo coloca en al final de la lista.
De cualquier manera, la clasificación es excepcionalmente fácil de implementar y garantiza que los elementos
aparezcan inmediatamente en la ubicación final una vez movidos (razón por la cual algunas personas lo llaman
clasificación de comparación in situ). A continuación se muestra un ejemplo de ordenación por selección. (Puede
encontrar este código en el archivo A4D; 07; Sorting Techniques.ipynb en el sitio Dummies como parte del código
descargable; consulte la Introducción para obtener más detalles).
datos = [9, 5, 7, 4, 2, 8, 1, 10, 6, 3]
para scanIndex en rango (0, len (datos)):
minIndex = índice de escaneo
para compIndex en el rango (scanIndex + 1, len (datos)):
si datos[compIndex] <datos[minIndex]:
minIndex = compIndex
si minIndex! = scanIndex:
datos[scanIndex], datos[minIndex] = \
datos[minIndex], datos[scanIndex]
imprimir (datos)
[1, 5, 7, 4, 2, 8, 9, 10, 6, 3]
[1, 2, 7, 4, 5, 8, 9, 10, 6, 3]
[1, 2, 3, 4, 5, 8, 9, 10, 6, 7]
[1, 2, 3, 4, 5, 6, 9, 10, 8, 7]
[1, 2, 3, 4, 5, 6, 7, 10, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 10, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Cambiar a un tipo de inserción
Una ordenación por inserción funciona utilizando un solo elemento como punto de partida y agregando elementos a
la izquierda o a la derecha del mismo en función de si estos elementos son menores o mayores que el elemento
seleccionado. A medida que aumenta el número de elementos ordenados, el algoritmo compara los elementos
nuevos con los elementos ordenados e inserta el nuevo elemento en la posición correcta de la lista. Una ordenación
por inserción tiene una velocidad de ordenación en el mejor de los casos de O(n) y una velocidad de ordenación en
el peor de los casos de O(n2).
136 PARTE 2 Comprender la necesidad de ordenar y buscar
Machine Translated by Google
Un ejemplo de velocidad de clasificación en el mejor de los casos es cuando todo el conjunto de datos ya está ordenado
porque la clasificación por inserción no tendrá que mover ningún valor. Un ejemplo de la velocidad de clasificación en el
peor de los casos es cuando todo el conjunto de datos se ordena en orden inverso porque cada inserción requerirá mover
todos los valores que ya aparecen en la salida. Puede leer más sobre las matemáticas involucradas en este tipo en
https://www.
khanacademy.org/computing/computer­science/algorithms/insertion­sort/a/analysis­of­
insertion­sort .
La clasificación por inserción sigue siendo un método de fuerza bruta para clasificar elementos, pero puede requerir menos
comparaciones que una clasificación por selección. A continuación se muestra un ejemplo de ordenación por inserción:
datos = [9, 5, 7, 4, 2, 8, 1, 10, 6, 3]
para scanIndex en rango (1, len (datos)):
temperatura = datos[scanIndex]
minIndex = índice de escaneo
mientras minIndex > 0 y temp < datos[minIndex ­ 1]:
datos[minIndex] = datos[minIndex ­ 1]
índicemínimo ­= 1
datos[minIndex] = temperatura
imprimir (datos)
[5, 9, 7, 4, 2, 8, 1, 10, 6, 3]
[5, 7, 9, 4, 2, 8, 1, 10, 6, 3]
[4, 5, 7, 9, 2, 8, 1, 10, 6, 3]
[2, 4, 5, 7, 9, 8, 1, 10, 6, 3]
[2, 4, 5, 7, 8, 9, 1, 10, 6, 3]
[1, 2, 4, 5, 7, 8, 9, 10, 6, 3]
[1, 2, 4, 5, 7, 8, 9, 10, 6, 3]
[1, 2, 4, 5, 6, 7, 8, 9, 10, 3]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Emplear mejores técnicas de clasificación
A medida que la tecnología de clasificación mejora, los algoritmos de clasificación comienzan a adoptar un enfoque más
inteligente para ordenar los datos en el orden correcto. La idea es hacer que el problema sea más pequeño y más fácil de
gestionar. En lugar de trabajar con un conjunto de datos completo, los algoritmos de clasificación inteligentes trabajan con
elementos individuales, lo que reduce el trabajo necesario para realizar la tarea. Las siguientes secciones analizan dos de
estas técnicas de clasificación inteligentes.
CAPÍTULO 7 Organización y búsqueda de datos 137
Machine Translated by Google
Reorganizar datos con Mergesort
Un Mergesort funciona aplicando el enfoque de divide y vencerás. La clasificación comienza
dividiendo el conjunto de datos en partes individuales y clasificándolas. Luego fusiona las piezas
de una manera que garantiza que ha ordenado la pieza fusionada.
La clasificación y fusión continúa hasta que todo el conjunto de datos vuelve a ser una sola pieza.
La velocidad de clasificación en el peor de los casos de Mergesort es O(n log n), lo que la hace
considerablemente más rápida que las técnicas utilizadas en la sección anterior (porque log n
siempre es menor que n). Este tipo en realidad requiere el uso de dos funciones. La primera
función funciona de forma recursiva para dividir las piezas y volver a unirlas.
datos = [9, 5, 7, 4, 2, 8, 1, 10, 6, 3]
def mergeOrdenar(lista):
# Determinar si la lista está dividida en
# piezas individuales.
si len(lista) < 2:
lista de retorno
# Encuentra el medio de la lista.
medio = len(lista)//2
# Divida la lista en dos partes.
izquierda = mergeOrdenar(lista[:medio])
derecha = fusionarOrdenar(lista[medio:])
# Fusiona las dos piezas ordenadas en una pieza más grande.
imprimir("Lado izquierdo: ", izquierda)
imprimir("Lado derecho: ", derecha)
fusionado = fusionar (izquierda, derecha)
print("Fusionado", fusionado)
volver fusionado
La segunda función realiza la tarea real de fusionar los dos lados mediante un proceso iterativo.
Aquí está el código utilizado para fusionar las dos piezas:
def fusionar(izquierda, derecha):
# Cuando el lado izquierdo o el lado derecho están vacíos,
# significa que este es un artículo individual y es
# ya ordenados.
si no len(izquierda):
regresar a la izquierda
si no len (derecha):
volver a la derecha
138 PARTE 2 Comprender la necesidad de ordenar y buscar
Machine Translated by Google
# Definir las variables utilizadas para fusionar las dos piezas. resultado =
[]
índice izquierdo = 0
rightIndex = 0 totalLen
= len(izquierda) + len(derecha)
# Sigue trabajando hasta que todos los elementos se fusionen. mientras
(len(resultado) < totalLen):
# Realizar las comparaciones requeridas y fusionar # las piezas
según el valor. si izquierda[leftIndex] <
derecha[rightIndex]: resultado.append(izquierda[leftIndex])
índice izquierdo+= 1
demás:
resultado.append(right[rightIndex]) rightIndex+= 1
# Cuando el lado izquierdo o el lado derecho son más largos, # agrega los
elementos restantes al resultado. si leftIndex == len(izquierda) o \
rightIndex == len(derecha):
resultado.extend(izquierda[leftIndex:]
o derecha[rightIndex:])
romper
resultado de retorno
fusionarOrdenar(datos)
Las declaraciones impresas en el código le ayudan a ver cómo funciona el proceso de fusión.
Aunque el proceso parece bastante complejo, en realidad es relativamente sencillo cuando
se trabaja en el proceso de fusión que se muestra aquí.
Lado izquierdo: [9]
Lado derecho: [5]
Fusionado [5, 9]
Lado izquierdo: [4]
Lado derecho: [2]
Fusionado [2, 4]
Lado izquierdo: [7]
Lado derecho: [2, 4]
Fusionado [2, 4, 7]
Lado izquierdo: [5, 9]
CAPÍTULO 7 Organización y búsqueda de datos 139
Machine Translated by Google
Lado derecho: [2, 4, 7]
Fusionado [2, 4, 5, 7, 9]
Lado izquierdo: [8]
Lado derecho: [1]
Fusionado [1, 8]
Lado izquierdo: [6]
Lado derecho: [3]
Fusionado [3, 6]
Lado izquierdo: [10]
Lado derecho: [3, 6]
Fusionado [3, 6, 10]
Lado izquierdo: [1, 8]
Lado derecho: [3, 6, 10]
Fusionado [1, 3, 6, 8, 10]
Lado izquierdo: [2, 4, 5, 7, 9]
Lado derecho: [1, 3, 6, 8, 10]
Fusionado [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Resolver problemas de clasificación de la
mejor manera usando Quicksort
Quicksort es uno de los métodos más rápidos para ordenar datos. Al leer en línea sobre
Mergesort y Quicksort, encontrará que algunas personas prefieren usar uno sobre el otro en
una situación determinada. Por ejemplo, la mayoría de la gente cree que Quick­sort funciona
mejor para ordenar matrices, y Mergesort funciona mejor para ordenar listas enlazadas
(consulte la discusión en http://www.geeksforgeeks.org/why­quick­sort­preferred­for­ matrices­
y­combinar­ordenar­para­listas­enlazadas/). Tony Hoare escribió la primera versión de
Quicksort en 1959, pero desde entonces, los desarrolladores han escrito muchas otras
versiones de Quicksort. El tiempo de clasificación promedio de Quicksort es O (n log n), pero
el tiempo de clasificación en el peor de los casos es O (n2).
La primera parte de la tarea es particionar los datos. El código elige un punto de pivote
que determina el lado izquierdo y derecho del tipo. Aquí está el código de partición para
este ejemplo:
datos = [9, 5, 7, 4, 2, 8, 1, 10, 6, 3]
partición def (datos, izquierda, derecha):
pivote = datos[izquierda]
lÍndice = izquierda + 1
rÍndice = derecho
140 PARTE 2 Comprender la necesidad de ordenar y buscar
Machine Translated by Google
mientras que Verdadero:
mientras que lIndex <= rIndex y datos[lIndex] <= pivote:
lÍndice += 1
mientras rIndex >= lIndex y datos[rIndex] >= pivote:
rÍndice ­= 1
si rÍndice <= lÍndice:
romper
datos[lÍndice], datos[rÍndice] = \
datos[rÍndice], datos[lÍndice]
imprimir (datos)
datos[izquierda], datos[rIndex] = datos[rIndex], datos[izquierda]
imprimir (datos)
devolver índice r
ENTENDIENDO LA PEOR ORDENACIÓN RÁPIDA
RENDIMIENTO DEL CASO
Quicksort rara vez incurre en el peor tiempo de clasificación. Sin embargo, incluso las versiones modificadas de
Quicksort pueden tener un tiempo de clasificación en el peor de los casos de O(n2) cuando uno de estos eventos
ocurre:
• El conjunto de datos ya está ordenado en el orden deseado.
• El conjunto de datos se ordena en orden inverso.
• Todos los elementos del conjunto de datos son iguales.
Todos estos problemas ocurren debido al punto de pivote que utiliza una función de clasificación menos
inteligente. Afortunadamente, el uso de la técnica de programación correcta puede mitigar estos problemas
al definir algo distinto del índice más a la izquierda o más a la derecha como punto de pivote. Las técnicas
en las que se basan las versiones modernas de Quicksort incluyen:
• Elegir un índice aleatorio
• Elegir el índice medio de la partición
• Elegir la mediana del primer, medio y último elemento de la partición para el pivote (especialmente
para particiones más largas)
CAPÍTULO 7 Organización y búsqueda de datos 141
Machine Translated by Google
El bucle interno de este ejemplo busca continuamente elementos que están en el lugar
equivocado y los intercambia. Cuando el código ya no puede intercambiar elementos, sale
del bucle y establece un nuevo punto de pivote, que devuelve a la persona que llama. Esta
es la parte iterativa del proceso. La parte recursiva del proceso maneja los lados izquierdo y
derecho del conjunto de datos, como se muestra aquí:
def clasificación rápida(datos, izquierda, derecha):
si derecha <= izquierda:
devolver
demás:
pivote = partición(datos, izquierda, derecha)
clasificación rápida(datos, izquierda, pivote­1)
clasificación rápida(datos, pivote+1, derecha)
devolver datos
clasificación rápida(datos, 0, len(datos)­1)
La cantidad de comparaciones e intercambios para este ejemplo es relativamente pequeña
en comparación con los otros ejemplos. Aquí está el resultado de este ejemplo:
[9, 5, 7, 4, 2, 8, 1, 3, 6, 10]
[6, 5, 7, 4, 2, 8, 1, 3, 9, 10]
[6, 5, 3, 4, 2, 8, 1, 7, 9, 10]
[6, 5, 3, 4, 2, 1, 8, 7, 9, 10]
[1, 5, 3, 4, 2, 6, 8, 7, 9, 10]
[1, 5, 3, 4, 2, 6, 8, 7, 9, 10]
[1, 2, 3, 4, 5, 6, 8, 7, 9, 10]
[1, 2, 3, 4, 5, 6, 8, 7, 9, 10]
[1, 2, 3, 4, 5, 6, 8, 7, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Usando árboles de búsqueda y el montón
Los árboles de búsqueda le permiten buscar datos rápidamente. El Capítulo 5 le presenta la
idea de una búsqueda binaria y la sección “Trabajar con árboles” del Capítulo 6 le ayuda a
comprender los árboles hasta cierto punto. Obtener elementos de datos, colocarlos en orden
en un árbol y luego buscar en ese árbol es una de las formas más rápidas de encontrar
información.
Un tipo especial de estructura de árbol es el montón binario, que coloca cada uno de los
elementos del nodo en un orden especial. El nodo raíz siempre contiene el valor más pequeño.
Al ver las ramas, verá que las ramas de nivel superior son siempre una
142 PARTE 2 Comprender la necesidad de ordenar y buscar
Machine Translated by Google
valor menor que las ramas y hojas de nivel inferior. El efecto es mantener el árbol equilibrado y en
un orden predecible para que la búsqueda sea extremadamente eficiente.
El costo está en mantener el árbol equilibrado. Las siguientes secciones describen en detalle cómo
funcionan los árboles de búsqueda y el montón.
Considerando la necesidad de buscar eficazmente
De todas las tareas que realizan las aplicaciones, la búsqueda es la que consume más tiempo y
también la que más se requiere. Aunque agregar datos (y ordenarlos más tarde) requiere cierta
cantidad de tiempo, el beneficio de crear y mantener un conjunto de datos proviene de usarlo para
realizar un trabajo útil, lo que significa buscar información importante en él. En consecuencia, a
veces puede arreglárselas con una funcionalidad CRUD menos eficiente e incluso con una rutina
de clasificación no óptima, pero las búsquedas deben realizarse de la manera más eficiente
posible. El único problema es que ninguna búsqueda realiza todas las tareas con absoluta
eficiencia, por lo que debe sopesar sus opciones en función de lo que espera hacer como parte
de las rutinas de búsqueda.
Dos de los métodos de búsqueda más eficientes implican el uso del árbol de búsqueda binaria
(BST) y del montón binario. Ambas técnicas de búsqueda se basan en una estructura similar a
un árbol para contener las claves utilizadas para acceder a los elementos de datos. Sin embargo,
la disposición de los dos métodos es diferente, por lo que uno tiene ventajas sobre el otro a la
hora de realizar determinadas tareas. La Figura 7­1 muestra la disposición de un BST.
FIGURA 7­1:
La disposición de las
teclas cuando
se utiliza un BST.
Observe cómo las teclas siguen un orden en el que los números menores aparecen a la izquierda
y los números mayores a la derecha. El nodo raíz contiene un valor que se encuentra en el medio
del rango de claves, lo que le brinda al BST un enfoque equilibrado y fácil de entender para
almacenar las claves. Compare esta disposición con el montón binario que se muestra en la Figura 7­2.
CAPÍTULO 7 Organización y búsqueda de datos 143
Machine Translated by Google
FIGURA 7­2:
La disposición de las
claves cuando
se utiliza un
montón binario.
Cada nivel contiene valores que son menores que el nivel anterior y la raíz contiene el valor clave
máximo para el árbol. Además, en este caso particular, los valores menores aparecen a la izquierda y
los mayores a la derecha (aunque este orden no se aplica estrictamente). La figura en realidad
representa un montón máximo binario. También puede crear un montón mínimo binario en el que la
raíz contenga el valor clave más bajo y cada nivel se desarrolle hasta valores más altos, donde los
valores más altos aparecen como parte de las hojas.
Como se señaló anteriormente, BST tiene algunas ventajas sobre el montón binario cuando se utiliza
para realizar una búsqueda. La siguiente lista proporciona algunos de los aspectos más destacados
de estas ventajas:
» La búsqueda de un elemento requiere un tiempo O(log n), en contraste con el tiempo O(n) para un
montón binario.
» Imprimir los elementos en orden requiere solo O(log n) tiempo, en contraste con O(n
log n) tiempo para un montón binario.
» Encontrar el piso y el techo requiere tiempo O(log n).
» Ubicar Kth elemento más pequeño/más grande requiere tiempo O(log n) cuando el árbol está
configurado correctamente.
La importancia de estos tiempos depende de su aplicación. BST tiende a funcionar mejor en situaciones
en las que se dedica más tiempo a buscar y menos a construir el árbol. Un montón binario tiende a
funcionar mejor en situaciones dinámicas en las que las claves cambian con regularidad. El montón
binario también ofrece ventajas, como se describe en la siguiente lista:
144 PARTE 2 Comprender la necesidad de ordenar y buscar
Machine Translated by Google
» Crear las estructuras necesarias requiere menos recursos porque los montones binarios
dependen de matrices, lo que también los hace más amigables con el caché.
» La creación de un montón binario requiere tiempo O(n), a diferencia de BST, que requiere tiempo
O(n log n).
» No es necesario utilizar punteros para implementar el árbol.
» Depender de variaciones del montón binario (por ejemplo, el montón de Fibonacci) ofrece
ventajas como aumentar y disminuir los tiempos clave del tiempo O(1).
Construyendo un árbol de búsqueda binario
Puede crear un BST utilizando diversos métodos. Algunas personas simplemente usan un
diccionario; otros usan código personalizado (consulte el artículo en https://interactivepython.
org/courselib/static/pythonds/Trees/SearchTreeImplementation.html
y http://code.activestate.com/recipes/577540­python­binary­search­tree/ como ejemplos). Sin
embargo, la mayoría de los desarrolladores no quieren reinventar la rueda cuando se trata de
BST. Con esto en mente, necesita un paquete, como bintrees, que proporcione toda la
funcionalidad necesaria para crear e interactuar con BST utilizando un mínimo de código. Para
descargar e instalar bintrees, abra un símbolo del sistema, escriba pip install bintrees y presione
Entrar. Verá bintrees instalados en su sistema. La documentación de este paquete aparece en
https://pypi.python.
org/pypi/bintrees/2.0.6.
Puede utilizar bintrees para todo tipo de necesidades, pero el ejemplo de esta sección analiza
específicamente un BST. En este caso el árbol está desequilibrado. El siguiente código muestra
cómo construir y mostrar un BST usando bintrees. (Puede encontrar este código en el archivo
A4D; 07; Search Techniques.ipynb en el sitio Dummies como parte del código descargable;
consulte la Introducción para obtener más detalles).
desde bintrees importar BinaryTree
datos = {3:'Blanco', 2:'Rojo', 1:'Verde', 5:'Naranja',
4:'Amarillo', 7:'Púrpura', 0:'Magenta'}
árbol = árbol binario (datos)
tree.update({6:'Verde'})
def mostrarKeyValue(clave, valor):
print('Clave: ', clave, 'Valor: ', valor)
árbol.foreach(displayKeyValue)
print('El elemento 3 contiene: ', tree.get(3))
print('El elemento máximo es: ', tree.max_item())
CAPÍTULO 7 Organización y búsqueda de datos 145
Machine Translated by Google
Clave: 0 Valor: Magenta
Clave: 1 Valor: Verde
Clave: 2 Valor: Rojo
Clave: 3 Valor: Blanco
Clave: 4 Valor: Amarillo
Clave: 5 Valor: Naranja
Clave: 6 Valor: Verde azulado
Clave: 7 Valor: Púrpura
El artículo 3 contiene: Blanco
El artículo máximo es: (7, 'Púrpura')
Para crear un árbol binario, debe proporcionar pares de clave y valor. Una forma de realizar esta
tarea es crear un diccionario como se muestra. Después de crear el árbol, puede utilizar la función
de actualización para agregar nuevas entradas. Las entradas deben incluir un par de clave y valor
como se muestra.
Este ejemplo utiliza una función para realizar una tarea con los datos en el árbol. En este caso,
la función simplemente imprime los pares clave y valor, pero podría usar el árbol como entrada
para un algoritmo de análisis (entre otras tareas). La función, visualización.
KeyValue actúa como entrada para la función foreach , que muestra los pares clave y valor como
salida. También tienes acceso a muchas otras funciones, como el uso de get
para obtener un solo elemento o max_item para obtener el máximo de elementos almacenados en el árbol.
Realizar búsquedas especializadas
utilizando un montón binario
Al igual que con BST, existen muchas formas de implementar un montón binario. Escribir uno a
mano o usar un diccionario funciona bien, pero confiar en un paquete hace que las cosas sean
considerablemente más rápidas y confiables. El paquete heapq viene con Python, por lo que ni
siquiera necesitas instalarlo. Puede encontrar la documentación de este paquete en https://
docs.python.org/3/library/heapq.html. El siguiente ejemplo muestra cómo construir y buscar un
montón binario usando heapq:
importar montónq
datos = {3:'Blanco', 2:'Rojo', 1:'Verde', 5:'Naranja',
4:'Amarillo', 7:'Púrpura', 0:'Magenta'}
montón = []
para clave, valor en data.items():
heapq.heappush(montón, (clave, valor))
heapq.heappush(montón, (6, 'Verde azulado'))
montón.sort()
146 PARTE 2 Comprender la necesidad de ordenar y buscar
Machine Translated by Google
para el artículo en el montón:
print('Clave: ', elemento[0], 'Valor: ', elemento[1])
print('El elemento 3 contiene: ', montón[3][1])
print('El elemento máximo es: ', heapq.nlargest(1, montón))
Clave: 0 Valor: Magenta
Clave: 1 Valor: Verde
Clave: 2 Valor: Rojo
Clave: 3 Valor: Blanco
Clave: 4 Valor: Amarillo
Clave: 5 Valor: Naranja
Clave: 6 Valor: Verde azulado
Clave: 7 Valor: Púrpura
El artículo 3 contiene: Blanco
El elemento máximo es: [(7, 'Púrpura')]
El código de ejemplo realiza las mismas tareas y proporciona el mismo resultado que el ejemplo de
la sección anterior, excepto que en este caso se basa en un montón binario.
El conjunto de datos es el mismo que antes. Sin embargo, observe la diferencia en la forma en que
agrega los datos al montón usando heappush. Además, después de agregar un nuevo elemento,
debe llamar a sort para asegurarse de que los elementos aparezcan en orden. Manipular los datos
es muy parecido a manipular una lista, en contraste con el enfoque de diccionario utilizado para los
bintrees. Cualquiera que sea el enfoque que utilice, vale la pena elegir una opción que funcione bien
con la aplicación que desea crear y proporcione los tiempos de búsqueda más rápidos posibles
para las tareas que realiza.
Confiando en Hashing
Un problema importante con la mayoría de las rutinas de clasificación es que clasifican todos los datos de un conjunto de datos.
Cuando el conjunto de datos es pequeño, apenas se nota la cantidad de datos que la rutina de
clasificación intenta mover. Sin embargo, a medida que el conjunto de datos crece, el movimiento
de los datos se vuelve perceptible cuando uno se sienta mirando la pantalla durante horas y horas.
Una forma de solucionar este problema es ordenar solo la información clave. Una clave son los
datos de identificación de un registro de datos en particular. Cuando interactúa con el registro de un
empleado, el nombre o número del empleado generalmente sirve como clave para acceder a toda
la demás información que tiene sobre el empleado. No tiene sentido ordenar toda la información de
los empleados cuando en realidad solo necesitas ordenar las claves, que es de lo que se trata el
uso de hash. Al trabajar con estas estructuras de datos, se obtiene una importante ventaja de
velocidad al ordenar la menor cantidad de datos presentados por las claves, en lugar de los registros
en su conjunto.
CAPÍTULO 7 Organización y búsqueda de datos 147
Machine Translated by Google
Poner todo en cubos
Hasta ahora, las rutinas de búsqueda y clasificación del libro funcionan realizando una serie
de comparaciones hasta que el algoritmo encuentra el valor correcto. El acto de realizar
comparaciones ralentiza los algoritmos porque cada comparación tarda cierto tiempo en
completarse.
Una forma más inteligente de realizar la tarea implica predecir la ubicación de un elemento de
datos particular en la estructura de datos (cualquiera que sea esa estructura) antes de buscarlo.
Eso es lo que hace una tabla hash : proporciona los medios para crear un índice de claves
que apunta a elementos individuales en una estructura de datos para que un algoritmo pueda
predecir fácilmente la ubicación de los datos. Colocar claves en el índice implica el uso de una
función hash que convierte la clave en un valor numérico. El valor numérico actúa como un
índice en la tabla hash, y la tabla hash proporciona un puntero al registro completo en el
conjunto de datos. Debido a que la función hash produce resultados repetibles, puede predecir
la ubicación de los datos requeridos. En muchos casos, una tabla hash proporciona un tiempo
de búsqueda de O(1). En otras palabras, sólo necesitas una comparación para encontrar los
datos.
Una tabla hash contiene una cantidad específica de espacios que puede ver como depósitos
para almacenar datos. Cada ranura puede contener un elemento de datos. El número de
espacios ocupados en comparación con el número de espacios disponibles es el factor de
carga. Cuando el factor de carga es alto, el potencial de colisiones (donde dos entradas de
datos tienen el mismo valor hash) también aumenta. La siguiente sección del capítulo analiza
cómo evitar colisiones, pero todo lo que realmente necesita saber por ahora es que pueden ocurrir.
Uno de los métodos más típicos para calcular el valor hash de una entrada es obtener el
módulo del valor dividido por el número de ranuras. Por ejemplo, si desea almacenar el número
54 en una tabla hash que contiene 15 espacios, el valor hash es 9. En consecuencia, el valor
54 va al espacio 9 de la tabla hash cuando los espacios son números del 0 al 14 (dado que la
mesa tiene 15 espacios). Una tabla hash real contendrá una cantidad considerablemente
mayor de espacios, pero 15 funciona bien para los propósitos de esta sección. Después de
colocar el elemento en la ranura hash, puedes usar la función hash por segunda vez para
encontrar su ubicación.
En teoría, si tiene una función hash perfecta y un número infinito de espacios, cada valor que
presente a la función hash producirá un valor único. En algunos casos, el cálculo del hash
puede volverse bastante complejo para garantizar valores únicos la mayor parte del tiempo.
Sin embargo, cuanto más complejo sea el cálculo del hash, menos beneficios recibirá del
hash, por lo que mantener las cosas simples es la mejor manera de hacerlo.
El hashing puede funcionar con todo tipo de estructuras de datos. Sin embargo, a efectos de
demostración, el siguiente ejemplo utiliza una lista simple para contener los datos originales y
una segunda lista para contener el hash resultante. (Puede encontrar este código en el archivo
A4D; 07; Hashing.ipynb en el sitio Dummies como parte del código descargable; consulte la
Introducción para obtener más detalles).
148 PARTE 2 Comprender la necesidad de ordenar y buscar
Machine Translated by Google
datos = [22, 40, 102, 105, 23, 31, 6, 5]
hash_table = [Ninguno] * 15
tblLen = len(hash_table)
def función_hash(valor, tamaño_tabla):
valor de retorno % tamaño_tabla
para valor en datos:
hash_table[hash_function(valor, tblLen)] = valor
imprimir(hash_table)
[105, 31, Ninguno, Ninguno, Ninguno, 5, 6, 22, 23, Ninguno, 40, Ninguno,
102, Ninguno, Ninguno]
Para encontrar un valor particular nuevamente, simplemente ejecútelo a través de
hash_function. Por ejemplo, print(hash_table[hash_function(102, tblLen)]) muestra 102 como
salida después de ubicar su entrada en hash_table. Debido a que los valores hash son
únicos en este caso particular, hash_function puede localizar los datos necesarios en todo momento.
Evitar colisiones
Se produce un problema cuando dos entradas de datos tienen el mismo valor hash. Si
simplemente escribe el valor en la tabla hash, la segunda entrada sobrescribirá la primera,
lo que provocará una pérdida de datos. Las colisiones, el uso del mismo valor hash por dos
valores, requieren que tengas algún tipo de estrategia en mente para manejarlas. Por
supuesto, la mejor estrategia es evitar la colisión en primer lugar.
Uno de los métodos para evitar colisiones es asegurarse de tener una tabla hash lo
suficientemente grande. Mantener el factor de carga bajo es su primera línea de defensa
contra tener que ser creativo en el uso de su tabla hash. Sin embargo, incluso con una mesa
grande, no siempre se pueden evitar colisiones. A veces, el conjunto de datos potencial es
tan grande, pero el conjunto de datos utilizado es tan pequeño, que evitar el problema
resulta imposible. Por ejemplo, si tiene una escuela con 400 niños y confía en su número de
seguro social para identificarse, las colisiones son inevitables porque nadie va a crear una
tabla hash con mil millones de entradas para tantos niños. El desperdicio de memoria sería
enorme. En consecuencia, es posible que una función hash deba utilizar algo más que una
simple salida de módulo para crear el valor hash. A continuación se muestran algunas
técnicas que puede utilizar para evitar colisiones:
» Valores parciales: Cuando se trabaja con algún tipo de información, parte de esa
información se repite, lo que puede crear colisiones. Por ejemplo, los primeros tres
dígitos de un número de teléfono pueden repetirse para un área determinada, por lo que
eliminar esos números y usar solo los cuatro restantes puede ayudar a resolver un problema de colisión.
CAPÍTULO 7 Organización y búsqueda de datos 149
Machine Translated by Google
» Plegado: Crear un número único puede ser tan fácil como dividir el número original en partes,
sumar las partes y usar el resultado para el valor hash. Por ejemplo, usando el número de
teléfono 555­1234, el hash podría comenzar dividiéndolo en pedazos: 55 51 234, y luego
sumando el resultado para obtener 340 como número utilizado para generar el hash.
» Mitad del cuadrado: el hash eleva al cuadrado el valor en cuestión, utiliza una cierta cantidad
de dígitos del centro del número resultante y descarta el resto de esos dígitos. Por ejemplo,
considere el valor 120. Cuando lo eleva al cuadrado, obtiene 14,400.
El hash podría usar 440 para generar el valor hash y descartar el 1 de la izquierda y el 0 de la
derecha.
Obviamente, hay tantas formas de generar el hash como imaginación tenga alguien para
crearlas. Desafortunadamente, ninguna cantidad de creatividad solucionará todos los
problemas de colisión, y es probable que se produzcan colisiones. Por lo tanto, necesitas
otro plan. Cuando ocurre una colisión, puede utilizar uno de los siguientes métodos para
solucionarla:
» Direccionamiento abierto: el código almacena el valor en la siguiente ranura abierta mirando las
ranuras secuencialmente hasta encontrar una ranura abierta para usar. El problema con
este enfoque es que supone un espacio abierto para cada valor potencial, lo que puede
no ser el caso. Además, el direccionamiento abierto significa que la búsqueda se
ralentiza considerablemente después de que aumenta el factor de carga. Ya no puede
encontrar el valor necesario en la primera comparación.
» Refrito: el código codifica el valor hash más una constante. Por ejemplo,
considere el valor 1020 cuando trabaje con una tabla hash que contiene 30 espacios y una
constante de 100. El valor hash en este caso es 22. Sin embargo, si el espacio 22 ya
contiene un valor, el refrito ((22 + 100) % 30) produce un nuevo valor hash de 2. En este
caso, no es necesario buscar un valor en la tabla hash secuencialmente. Cuando se
implementa correctamente, una búsqueda aún puede incluir una cantidad baja de
comparaciones para encontrar el valor objetivo.
» Encadenamiento: cada ranura de la tabla hash puede contener múltiples valores. Puede
implementar este enfoque utilizando una lista dentro de una lista. Cada vez que ocurre
una colisión, el código simplemente agrega el valor a la lista en la ranura de destino. Este
enfoque ofrece el beneficio de saber que el hash siempre producirá la ranura correcta, pero
la lista dentro de esa ranura aún requerirá algún tipo de búsqueda secuencial (u otro tipo) para
encontrar el valor específico.
Creando tu propia función hash
En ocasiones, es posible que necesites crear funciones hash personalizadas para satisfacer
las necesidades del algoritmo que utilizas o mejorar su rendimiento. Aparte de criptográfico
150 PARTE 2 Comprender la necesidad de ordenar y buscar
Machine Translated by Google
usos (que merecen un libro solo), el Capítulo 12 presenta algoritmos comunes que aprovechan
diferentes funciones hash, como Bloom Filter, HyperLogLog y Count ­Min Sketch, que aprovechan las
propiedades de funciones hash personalizadas para extraer información de grandes cantidades. de
datos.
Puede encontrar muchos ejemplos de diferentes funciones hash en la hashlib de Python.
paquete. El paquete hashlib contiene algoritmos como estos:
» Algoritmos Hash Seguros (SHA): Estos algoritmos incluyen SHA1, SHA224,
SHA256, SHA384 y SHA512. Publicados por el Instituto Nacional de Estándares y
Tecnología (NIST) como estándar federal de procesamiento de información (FIPS)
de EE. UU., los algoritmos SHA brindan soporte para aplicaciones y protocolos de seguridad.
» Algoritmo MD5 de RSA: inicialmente diseñado para aplicaciones de seguridad, este
hash se convirtió en una forma popular de verificar archivos. Las sumas de
comprobación reducen los archivos a un único número que le permite determinar si
el archivo se modificó desde la creación del hash (le permite determinar si el archivo
que descargó no estaba dañado y no ha sido alterado por un hacker). Para garantizar
la integridad del archivo, basta con comprobar si la suma de comprobación MD5
de su copia corresponde al original comunicado por el autor del archivo.
Si hashlib no está disponible en su instalación de Python, puede instalar el paquete usando el comando
pip install hashlib desde un shell de comandos. Los algoritmos de hashlib funcionan bien para
aplicaciones simples cuando se usan solos.
DESCUBRIENDO LO INESPERADO
USOS DE HASHES
Además de los algoritmos detallados en este libro, otros algoritmos importantes se basan en
hashes. Por ejemplo, el algoritmo Hashing sensible a la localidad (LSH) se basa en una gran
cantidad de funciones hash para unir información aparentemente separada. Si se pregunta
cómo las empresas de marketing y los servicios de inteligencia reúnen diferentes fragmentos
de información basándose en nombres y direcciones que no son idénticos (por ejemplo, adivinar
que “Los Ángeles”, “Los Ángeles” y “Los Ángeles” se refieren todos a Los Ángeles), Ángeles) la
respuesta es LSH. LSH fragmenta la información para verificarla en partes y la digiere usando
muchas funciones hash, lo que da como resultado la producción de un resultado hash especial,
que es una dirección para un depósito que se usa para contener palabras similares. LSH es
bastante complejo en su implementación, pero consulte este material del Instituto Tecnológico de Massachusetts (MIT): h
www.mit.edu/~andoni/LSH/.
CAPÍTULO 7 Organización y búsqueda de datos 151
Machine Translated by Google
Sin embargo, puede combinar la salida de múltiples funciones hash cuando trabaje con
aplicaciones complejas que dependen de un gran conjunto de datos. Simplemente sume los
resultados de las distintas salidas después de haber realizado una multiplicación en una o más de ellas.
La suma de dos funciones hash tratadas de esta manera conserva las cualidades de las
funciones hash originales aunque el resultado sea diferente e imposible de recuperar como los
elementos originales de la suma. Usar este enfoque significa que tiene una función hash
completamente nueva para usar como receta hash secreta para algoritmos y aplicaciones.
El siguiente fragmento de código se basa en el paquete hashlib y en md5 y sha1.
algoritmos hash. Simplemente proporciona un número para usar en la multiplicación dentro de
la suma hash. (Debido a que los números son infinitos, tienes una función que puede producir
hashes infinitos).
desde hashlib importar md5, sha1
def hash_f(elemento, i, longitud):
""" Función para crear muchas funciones hash """
h1 = int(md5(element.encode('ascii')).hexdigest(),16)
h2 = int(sha1(element.encode('ascii')).hexdigest(),16)
retorno (h1 + i*h2) % longitud
imprimir (hash_f("CAT", 1, 10**5))
64018
imprimir (hash_f("CAT", 2, 10**5))
43738
Si se pregunta dónde encontrar otros usos de las tablas hash a su alrededor, consulte los
diccionarios de Python. Los diccionarios son, de hecho, tablas hash, aunque tienen una
manera inteligente de lidiar con las colisiones y no perderás tus datos porque dos claves hash
casualmente tienen el mismo resultado. El hecho de que el índice del diccionario utilice un
hash es también la razón de su rapidez a la hora de comprobar si hay una clave presente.
Además, el uso de un hash explica por qué no se pueden utilizar todos los tipos de datos como
clave. La clave que elijas debe ser algo que Python pueda convertir en un resultado hash. Las
listas, por ejemplo, no se pueden dividir porque son mutables; puedes cambiarlos agregando
o eliminando elementos. Sin embargo, si transformas tu lista en una cadena, puedes usarla
como clave para un diccionario en Python.
152 PARTE 2 Comprender la necesidad de ordenar y buscar
Machine Translated by Google
3 Explorando el
Mundo de gráficos
Machine Translated by Google
EN ESTA PARTE . . .
Descubra los conceptos básicos de gráficos que le ayudarán a dibujar,
medir y analizar gráficos.
Interactúe con gráficos para localizar nodos, ordenar elementos
del gráfico y encontrar el camino más corto.
Trabajar con las redes sociales en forma de gráfico.
Explore gráficos para encontrar patrones y tomar decisiones basadas
en esos patrones.
Utilice el algoritmo PageRank para calificar páginas web.
Machine Translated by Google
EN ESTE CAPÍTULO
» Definir por qué las redes son importantes
» Demostrar técnicas de dibujo de
gráficos.
» Considerando la funcionalidad del gráfico
» Usar formatos numéricos para representar
gráficos
Capítulo 8
Comprensión
Conceptos básicos de gráficos
conectados por una serie de aristas o arcos (según la representación).
Los grafos
son estructuras
una serie
de nodos
(o vértices)
conectados
Cuando
piensas enque
un presentan
gráfico, piensa
en una
estructura
como un
mapa,
donde cada ubicación en el mapa es un nodo y las calles son los bordes. Esta
presentación difiere de un árbol donde cada camino termina en un nodo hoja.
Recuerde del Capítulo 7 que un árbol podría parecer un organigrama o una jerarquía familiar.
Lo más importante es que las estructuras de los árboles en realidad parecen árboles y tienen
un comienzo y un final definidos. Este capítulo comienza ayudándole a comprender la
importancia de las redes, que son un tipo de gráfico comúnmente utilizado para todo tipo de propósitos.
Puedes representar gráficos de muchas formas, la mayoría de ellas abstractas. A menos
que seas muy bueno visualizando cosas en tu mente (la mayoría de las personas no lo
son), necesitas saber cómo dibujar un gráfico para poder verlo. La gente confía en su visión
para entender cómo funcionan las cosas. Trazar es el acto de convertir los números que
representan un gráfico en una visualización gráfica . Los lenguajes como Python destacan
en el trazado porque es una característica increíblemente importante. De hecho, es una
de las razones por las que este libro utiliza Python en lugar de otro lenguaje, como C (que
es bueno para realizar un conjunto de tareas completamente diferente).
Después de poder visualizar un gráfico, es importante saber qué hacer con la
representación gráfica. Este capítulo comienza midiendo la funcionalidad del gráfico.
CAPÍTULO 8 Comprensión de los conceptos básicos de gráficos 155
Machine Translated by Google
Haces cosas como contar los bordes y vértices para determinar cosas como la complejidad
del gráfico. Ver un gráfico también le permite realizar tareas como la centralización
informática con mayor facilidad. Por supuesto, usted se basa en lo que descubre en este
capítulo en el Capítulo 9.
La presentación numérica de un gráfico es importante, incluso si dificulta su comprensión.
La trama es para ti, pero la computadora no entiende realmente la trama (a pesar de haberla
dibujado para ti). Piense en la computadora como un pensador más abstracto. Teniendo en
cuenta la necesidad de presentar un gráfico en una forma que la computadora pueda
entender, este capítulo analiza tres técnicas para poner un gráfico en formato numérico:
matrices, representaciones dispersas y listas. Todas estas técnicas tienen ventajas y
desventajas, y las utilizará de maneras específicas en capítulos futuros (comenzando con
el Capítulo 9). También hay otras formas disponibles de poner un gráfico en formato
numérico, pero estos tres métodos le serán muy útiles para comunicarse con la
computadora.
Explicando la importancia de las redes
Una red es un tipo de gráfico que asocia nombres con los vértices (nodos o puntos), aristas
(arcos o líneas), o ambos. Asociar nombres con las características del gráfico reduce el
nivel de abstracción y facilita la comprensión del gráfico. Los datos que el gráfico modela se
vuelven reales en la mente de la persona que los ve, a pesar de que el gráfico es realmente
una abstracción del mundo real puesto en una forma que tanto los humanos como las
computadoras pueden entender de diferentes maneras. Las siguientes secciones le
ayudarán a comprender mejor la importancia de las redes para que pueda ver cómo su uso
en este libro simplifica la tarea de descubrir cómo funcionan los algoritmos y cómo puede
beneficiarse de su uso.
Considerando la esencia de un gráfico
Los gráficos aparecen como pares ordenados en la forma G = (V,E), donde G es el gráfico,
V es una lista de vértices y E es una lista de aristas que conectan los vértices. Una arista
es en realidad un par numérico que expresa los dos vértices que conecta. En consecuencia,
si tiene dos vértices que representan ciudades, Houston (que es igual a 1) y Dallas (que es
igual a 2), y desea conectarlos con una carretera, entonces crea una arista, Carretera, que
contiene un par de referencias de vértices. , Carretera = [Houston, Dallas]. La gráfica
aparecería como G = [(Houston, Dallas)], que simplemente dice que hay un primer vértice,
Houston, con una conexión con Dallas, el segundo vértice. Utilizando el orden de
presentación de los vértices, Houston es adyacente a Dallas; en otras palabras, un automóvil
saldría de Houston y entraría a Dallas.
156 PARTE 3 Explorando el mundo de los gráficos
Machine Translated by Google
Los gráficos vienen en varias formas. Un gráfico no dirigido (como se muestra en la Figura 8­1) es aquel
en el que el orden de las entradas de los bordes no importa. Un mapa de carreteras representaría un
gráfico no dirigido en la mayoría de los casos porque el tráfico puede viajar a lo largo de la carretera en
ambas direcciones.
FIGURA 8­1:
Presentar un
gráfico simple no
dirigido.
Un gráfico dirigido, como el que se muestra en la figura 8­2, es aquel en el que el orden de las entradas
de los bordes sí importa porque el flujo va de la primera entrada a la segunda. En este caso, la mayoría
de la gente llama arcos a los bordes para diferenciarlos de las entradas no dirigidas. Considere una
representación gráfica de una secuencia de semáforo donde Rojo es igual a 1, Amarillo es igual a 2 y
Verde es igual a 3. Los tres arcos necesarios para expresar la secuencia son: Ir = [Rojo, Verde],
Precaución = [Verde, Amarillo] y Detener = [Amarillo, Rojo]. El orden de las entradas es importante
porque el flujo desde Ir a Precaución y Detener es importante. Imagine el caos que se produciría si la
señal luminosa decidiera ignorar la naturaleza del gráfico dirigido de la secuencia.
Un tercer tipo esencial de gráfico que debes considerar es el gráfico mixto. Piense nuevamente en la hoja
de ruta. No siempre es cierto que el tráfico fluye en ambos sentidos en todas las carreteras. Al crear
algunos mapas, debes considerar la presencia de calles de un solo sentido. En consecuencia, necesita
subgrafos dirigidos y no dirigidos en el mismo gráfico, que es lo que se obtiene con un gráfico mixto.
Otro tipo de gráfico que debe considerar es el gráfico ponderado (que se muestra en la Figura 8­3), que
es un gráfico que tiene valores asignados a cada uno de los bordes o arcos.
Piense nuevamente en la hoja de ruta. Algunas personas quieren saber más que simplemente la dirección
a seguir; también quieren saber qué tan lejos está el próximo destino o cuánto tiempo dedicar para llegar
allí. Un gráfico ponderado proporciona este tipo de información y las ponderaciones se utilizan de muchas
maneras diferentes al realizar cálculos utilizando gráficos.
CAPÍTULO 8 Comprensión de los conceptos básicos de gráficos 157
Machine Translated by Google
FIGURA 8­2:
Creando la
versión dirigida
de la misma
grafico.
FIGURA 8­3:
Usar un gráfico
ponderado para
hacer las
cosas más realistas.
Junto con el gráfico ponderado, es posible que también necesite un gráfico etiquetado con
vértices al crear una hoja de ruta. Cuando se trabaja con un gráfico etiquetado con vértices,
cada vértice tiene un nombre asociado. Considere mirar un mapa de carreteras donde el
cartógrafo no haya etiquetado las ciudades. Sí, puedes ver los pueblos, pero no sabes
cuál es cuál sin etiquetas. Puede encontrar tipos de gráficos adicionales descritos en http://
web.cecs.pdx.edu/~sheard/course/Cs163/Doc/Graphs.html.
Encontrar gráficos en todas partes
Los gráficos pueden parecer una de esas funciones matemáticas esotéricas que te aburrían
en la escuela, pero en realidad son bastante interesantes porque los usas todo el tiempo.
158 PARTE 3 Explorando el mundo de los gráficos
Machine Translated by Google
sin pensar realmente en ello. Por supuesto, ayuda saber que normalmente no lidiarás con los
números detrás de los gráficos. Piensa en un mapa. Lo que ves es un gráfico, pero lo ves en
formato gráfico, con ciudades, carreteras y todo tipo de otras características. La cuestión es que
cuando ves un mapa, piensas en un mapa, no en un gráfico (pero tu GPS sí ve un gráfico, por lo
que siempre puede sugerir la ruta más corta a tu destino). Si comenzaras a mirar a tu alrededor,
encontrarías muchos elementos comunes que son gráficos pero que se llaman de otra manera.
Algunos gráficos no son de naturaleza visual, pero aun así no los verás como gráficos. Por
ejemplo, los sistemas de menú telefónico son una forma de gráfico direccional. De hecho, a
pesar de su aparente simplicidad, los gráficos telefónicos son en realidad algo complejos. Pueden
incluir bucles y todo tipo de otras estructuras interesantes. Algo que podría intentar es trazar el
gráfico de un sistema de menú en algún momento. Quizás se sorprenda de lo complejos que
pueden ser algunos de ellos.
Otra forma de sistema de menú aparece como parte de las aplicaciones. Para realizar tareas, la
mayoría de las aplicaciones lo guían a través de una serie de pasos en un tipo especial de
subaplicación llamada asistente. El uso de asistentes hace que las aplicaciones aparentemente
complejas sean mucho más fáciles de usar, pero para que los asistentes funcionen, el
desarrollador de la aplicación debe crear un gráfico que represente la serie de pasos.
Puede que le sorprenda descubrir que incluso las recetas de los libros de cocina son una especie
de gráfico (y crear una representación pictórica de las relaciones entre los ingredientes puede
resultar interesante). Cada ingrediente de la receta es un nodo. Los nodos se conectan utilizando
los bordes creados según las instrucciones para mezclar los ingredientes. Por supuesto, una
receta es sólo una especie de química, y los gráficos químicos muestran la relación entre los
elementos de una molécula. (Sí, la gente realmente está teniendo esta discusión; puede ver uno
de esos hilos en http://stackoverflow.com/questions/7749073/
representar­una­receta­de­cocina­en­una­base­de­datos­de­gráficos.)
El punto es que ves estos gráficos todo el tiempo, pero no los ves como gráficos, sino como algo
más, como una receta o una fórmula química.
Los gráficos pueden representar muchos tipos de relaciones entre objetos, lo que implica una
secuencia de orden, dependencia del tiempo o causalidad.
Mostrando el lado social de los gráficos.
Los gráficos tienen implicaciones sociales porque a menudo reflejan relaciones entre personas
en diversos entornos. Uno de los usos más obvios de los gráficos es el organigrama. Piénsalo.
Cada nodo es una persona diferente en la organización, con bordes que conectan los nodos
para mostrar las diversas relaciones entre los individuos. Lo mismo se aplica a todo tipo de
gráficos, como los que muestran antecedentes familiares. Sin embargo, en el primer caso, el
gráfico no está dirigido porque la comunicación fluye en ambos sentidos entre gerentes y
subordinados (aunque la naturaleza de la conversación difiere según la dirección). En el segundo
CAPÍTULO 8 Comprensión de los conceptos básicos de gráficos 159
Machine Translated by Google
En este caso, la gráfica está dirigida porque dos padres tienen hijos. El flujo muestra la dirección
de la herencia desde un miembro fundador hasta los hijos actuales.
Las redes sociales también se benefician del uso de gráficos. Por ejemplo, existe toda una
industria para analizar las relaciones entre tweets en Twitter (ver http://
twittertoolsbook.com/10­awesome­twitter­analytics­visualization­tools/ para ver un ejemplo de
algunas de estas herramientas). El análisis se basa en el uso de gráficos para descubrir las
relaciones entre tweets individuales.
Sin embargo, no es necesario mirar nada más arcano que el correo electrónico para ver los gráficos
utilizados para las necesidades sociales. El corpus de Enron incluye los 200.399 mensajes de
correo electrónico de 158 altos ejecutivos, arrojados a Internet por la Comisión Federal Reguladora
de Energía (FERC). Los científicos y académicos han utilizado este corpus para crear muchos
gráficos sociales que revelan cómo la séptima empresa más grande de los Estados Unidos tuvo
que declararse en quiebra en 2001 (consulte https://www.technologyreview.com/s/
515801/la­vida­inmortal­de­los­correos­electronicos­de­enron/ para aprender cómo este corpus
ha ayudado y realmente está ayudando a avanzar en el análisis de gráficos complejos).
Incluso tu computadora tiene gráficos sociales. No importa qué aplicación de correo electrónico
utilice, puede agrupar correos electrónicos de varias maneras, y estos métodos de agrupación
normalmente se basan en gráficos para proporcionar una estructura. Después de todo, tratar de
seguir el flujo de la discusión sin saber qué mensajes son respuestas a otros mensajes es una
causa perdida. Sí, podrías hacerlo, pero a medida que aumenta el número de mensajes, el esfuerzo
requiere cada vez más tiempo hasta que se desperdicia debido a las limitaciones de tiempo que
tiene la mayoría de las personas.
Comprender los subgrafos
Las relaciones representadas por gráficos pueden volverse bastante complejas. Por ejemplo, al
representar calles de una ciudad, la mayoría de las calles permiten el tráfico en ambas direcciones,
lo que hace que un gráfico no dirigido sea perfecto para fines de representación. Sin embargo,
algunas calles permiten el tráfico en una sola dirección, lo que significa que en este caso se
necesita un gráfico dirigido. La combinación de calles de doble sentido y de un solo sentido hace
que la representación utilizando un solo tipo de gráfico sea imposible (o al menos inconveniente).
Combinar gráficos dirigidos y no dirigidos en un solo gráfico significa que debe crear subgrafos
para representar cada tipo de gráfico y luego conectar los subgrafos en un gráfico más grande.
Algunos gráficos que contienen subgrafos son tan comunes que tienen nombres específicos, que
en este caso es un gráfico mixto.
Los subgrafos también son útiles para otros fines. Por ejemplo, es posible que desees analizar un
bucle dentro de un gráfico, lo que significa describir ese bucle como un subgrafo.
No necesita el gráfico completo, solo los nodos y aristas necesarios para realizar el análisis. Todo
tipo de disciplinas utilizan este enfoque. Sí, los desarrolladores lo usan para garantizar que partes
de una aplicación funcionen como se espera, pero los ingenieros de la ciudad también lo usan para
comprender la naturaleza del flujo de tráfico en una sección particularmente transitada de la ciudad.
160 PARTE 3 Explorando el mundo de los gráficos
Machine Translated by Google
Los profesionales médicos también utilizan subgrafos para comprender el flujo de sangre u otros líquidos
entre los órganos del cuerpo. Los órganos son los ganglios y los vasos sanguíneos son los bordes. De
hecho, muchos de estos gráficos están ponderados: es esencial saber cuánta sangre fluye, no solo qué
fluye.
Los gráficos complejos también pueden ocultar patrones que usted necesita conocer. Por ejemplo, el
mismo ciclo puede aparecer en varias partes del gráfico o es posible que vea el mismo ciclo en diferentes
gráficos. Al crear un subgráfico a partir del ciclo, puede realizar fácilmente comparaciones dentro del mismo
gráfico o entre gráficos para ver cómo se comparan. Por ejemplo, un biólogo podría querer comparar el
ciclo de mutación de un animal con el ciclo de mutación de otro animal. Para hacer esta comparación, el
biólogo necesitaría crear la representación como un subgrafo de los procesos de todo el animal. (Puede
ver una vista interesante de este uso particular de gráficos en http://www.sciencedirect.com/science/article/
pii/S1359027896000569.). El gráfico aparece cerca del comienzo del artículo como Figura 1.
Definición de cómo dibujar un gráfico
Algunas personas pueden visualizar datos directamente en sus mentes. Sin embargo, la mayoría de la
gente realmente necesita una presentación gráfica de los datos para poder entenderlos. Este punto queda
claro con el uso de gráficos en presentaciones comerciales. Podrías contarles a otros sobre las ventas del
año pasado presentando tablas de números. Después de un tiempo, la mayoría de tu audiencia se quedaría
dormida y nunca lograrías entender lo que querías decir. La razón es simple: las tablas de números son
precisas y presentan mucha información, pero no lo hacen de una manera que la gente entienda.
Trazar los datos y mostrar las cifras de ventas como un gráfico de barras ayuda a las personas a ver las
relaciones entre los números con mayor facilidad. Si desea señalar que las ventas aumentan cada año, un
gráfico de barras con barras de longitud creciente lo aclara rápidamente. Curiosamente, el uso del gráfico
presenta los datos de una manera menos precisa. Tratar de ver que la empresa ganó $3.400.026,15 el año
pasado y $3.552.215,82 este año mirando un gráfico de barras es casi imposible. Sí, la tabla mostraría esta
información, pero la gente realmente no necesita conocer ese nivel de detalle: simplemente necesitan ver
el aumento anual, el contraste de las ganancias de un año a otro. Sin embargo, su computadora está
interesada en los detalles, razón por la cual las tramas son para humanos y las matrices son para
computadoras.
Las siguientes secciones le ayudarán a descubrir las maravillas de la trama. Obtendrá una descripción
general rápida de cómo funcionan los gráficos con Python. Por supuesto, estos principios aparecen en
capítulos posteriores de forma más detallada. Estas secciones proporcionan un punto de partida para que
puedas comprender más fácilmente las tramas que se presentan más adelante.
CAPÍTULO 8 Comprensión de los conceptos básicos de gráficos 161
Machine Translated by Google
Distinguir los atributos clave
Antes de poder dibujar un gráfico, necesita conocer los atributos del gráfico. Como se
mencionó anteriormente, los gráficos constan de nodos (o vértices) y aristas (para
gráficos no dirigidos) o arcos (para gráficos dirigidos). Cualquier gráfico que quieras
dibujar contendrá estos elementos. Sin embargo, la forma en que represente estos
elementos depende en parte del paquete que elija utilizar. En aras de la simplicidad, el
libro se basa en una combinación de dos paquetes:
» NetworkX (https://networkx.github.io/): Contiene código para dibujar.
haciendo gráficos.
» matplotlib (http://matplotlib.org/): Proporciona acceso a todo tipo de
rutinas de dibujo, algunas de las cuales pueden mostrar gráficos creados por NetworkX.
Para usar paquetes en Python, debes importarlos. Cuando necesite utilizar paquetes
externos, debe agregar código especial, como las siguientes líneas de código que
brindan acceso a matplotlib y networkx. (Puede encontrar este código en el archivo A4D;
08; Draw Graph.ipynb en el sitio Dummies como parte del código descargable; consulte
la Introducción para obtener más detalles).
importar networkx como nx
importar matplotlib.pyplot como plt
%matplotlib en línea
La entrada en línea especial %matplotlib le permite ver sus gráficos directamente en
Notebook en lugar de como un gráfico externo. Usar esta entrada significa que puede
crear un Notebook con gráficos ya incluidos para no tener que ejecutar el código
nuevamente para ver los resultados que recibió en el pasado.
Ahora que tiene acceso a los paquetes, crea un gráfico. En este caso, un gráfico es una
especie de contenedor que contiene los atributos clave que definen el gráfico. Crear un
contenedor le permite dibujar el gráfico para poder verlo más tarde. El siguiente código
crea un objeto NetworkX Graph .
AGraph = nx.Graph()
A continuación viene agregar los atributos clave a AGraph . Debe agregar nodos y bordes
usando el siguiente código.
Nodos = rango(1,5)
Aristas = [(1,2), (2,3), (3,4), (4,5), (1,3), (1,5)]
Como se mencionó anteriormente, los bordes describen conexiones entre nodos. En este
caso, Nodes contiene valores del 1 al 5, por lo que Edges contiene conexiones entre esos
valores.
162 PARTE 3 Explorando el mundo de los gráficos
Machine Translated by Google
Por supuesto, los nodos y los bordes están ahí ahora y no aparecerán como parte de
AGraph. Debes ponerlos en el contenedor para verlos. Utilice el siguiente código para
agregar los nodos y los bordes a AGraph.
AGraph.add_nodes_from(Nodos)
AGraph.add_edges_from(Bordes)
El paquete NetworkX contiene todo tipo de funciones que puede utilizar para interactuar
con nodos y bordes individuales, pero el enfoque que se muestra aquí es la forma más
rápida de hacer las cosas. Aun así, es posible que desees agregar bordes adicionales
más adelante. Por ejemplo, es posible que desee agregar un borde entre 2 y 4, en cuyo
caso llamaría a la función AGraph.add_edge(2, 4) .
Dibujando el gráfico
Puedes interactuar de muchas formas con el objeto contenedor AGraph que creaste en
la sección anterior, pero muchas de esas formas de interactuar son abstractas y no muy
satisfactorias si eres una persona orientada visualmente. A veces es agradable ver lo
que contiene un objeto mirándolo. El siguiente código muestra el gráfico contenido en
AGraph:
nx.draw(AGraph, with_labels=True)
La función draw() proporciona varios argumentos que puede usar para decorar la
pantalla, como modificar el color del nodo usando el argumento node_color y el color del
borde usando el argumento edge_color . La Figura 8­4 muestra el gráfico contenido en
AGraph.
FIGURA 8­4:
Ver lo que
contiene un
gráfico hace que
sea más fácil de entender.
CAPÍTULO 8 Comprensión de los conceptos básicos de gráficos 163
Machine Translated by Google
DIFERENCIAS EN LA SALIDA DE CIFRAS
La Figura 8­4 muestra una salida típica. Sin embargo, su gráfico puede parecer ligeramente
diferente al que se muestra. Por ejemplo, el triángulo podría aparecer en la parte inferior en
lugar de arriba, o los ángulos entre los nodos podrían variar. Las conexiones entre los nodos
son las más importantes, por lo que las pequeñas diferencias en la apariencia real no son
importantes. Ejecutar el código varias veces demostraría que la orientación del gráfico
cambia, junto con los ángulos entre los bordes. Ves esta misma diferencia en otras capturas
de pantalla del libro. Siempre vea la imagen teniendo en cuenta las conexiones de los nodos,
en lugar de esperar una coincidencia precisa entre su salida y la salida del libro.
Funcionalidad del gráfico de medición
Una vez que puedas visualizar y comprender un gráfico, debes considerar qué partes del gráfico
son importantes. Después de todo, no querrás perder el tiempo realizando análisis de datos que
realmente no importan en el gran esquema de las cosas. Piense en alguien que esté analizando
el flujo de tráfico para mejorar el sistema de calles. Las intersecciones representan vértices y las
calles representan bordes por donde fluye el tráfico. Al saber cómo fluye el tráfico, es decir, qué
vértices y aristas reciben más tráfico, se puede empezar a pensar qué vías ampliar y cuáles
necesitan más reparación porque las utiliza más tráfico.
Sin embargo, no basta con mirar calles individuales. Un nuevo rascacielos puede traer consigo
mucho tráfico que afecte a toda una zona. El rascacielos representa un punto central alrededor del
cual el flujo de tráfico adquiere mayor importancia. Los vértices más importantes son los centrales
del nuevo rascacielos. Calculando la centralidad,
Los vértices más importantes de un gráfico pueden ayudarle a comprender qué partes del gráfico
requieren más atención. Las siguientes secciones analizan las cuestiones básicas que debe
considerar al medir la funcionalidad del gráfico, que es la capacidad del gráfico para modelar un
problema específico.
Contar aristas y vértices
A medida que los gráficos se vuelven más complejos, transmiten más información, pero también
se vuelven más difíciles de entender y manipular. El número de aristas y vértices de un gráfico
determina la complejidad del gráfico. Sin embargo, se utiliza la combinación de aristas y vértices
para contar la historia completa. Por ejemplo, puede tener un nodo que no esté conectado a los
otros nodos de ninguna manera. Es legal crear dicho nodo en un gráfico para representar un valor
que carece de conexiones con los demás. Usando el siguiente código, puede determinar fácilmente
que el nodo 6 no tiene conexiones con los demás.
164 PARTE 3 Explorando el mundo de los gráficos
Machine Translated by Google
porque carece de información de borde. (Puede encontrar este código en el archivo A4D; 08;
Graph Measurements.ipynb ).
importar networkx como nx
importar matplotlib.pyplot como plt
%matplotlib en línea
AGraph = nx.Graph()
Nodos = rango(1,5)
Aristas = [(1,2), (2,3), (3,4), (4,5), (1,3), (1,5)]
AGraph.add_nodes_from(Nodos)
AGraph.add_edges_from(Bordes)
AGraph.add_node(6)
ordenado (nx.connected_components (AGraph))
[{1, 2, 3, 4, 5}, {6}]
El resultado de este código muestra que los nodos del 1 al 5 están conectados y que el nodo 6
carece de conexión. Por supuesto, puedes remediar esta situación agregando otra ventaja
usando el siguiente código y luego verificando nuevamente:
AGraph.add_edge(1,6)
ordenado (nx.connected_components (AGraph))
[{1, 2, 3, 4, 5, 6}]
El resultado ahora muestra que cada uno de los nodos se conecta al menos a otro nodo. Sin
embargo, no sabes qué nodos tienen más conexiones. El recuento de aristas de un nodo en
particular es el grado. Cuanto mayor es el grado, más complejo se vuelve el nodo. Al conocer el
grado, puedes tener una idea de qué nodos son los más importantes. El siguiente código
muestra cómo obtener el grado para el gráfico de ejemplo.
nx.grado(AGraph).valores()
dict_values([4, 2, 3, 2, 2, 1])
Los valores de grado aparecen en el orden de los nodos, por lo que el nodo 1 tiene cuatro
conexiones y el nodo 6 tiene solo una conexión. En consecuencia, el nodo 1 es el más
importante, seguido del nodo 3, que tiene tres conexiones.
CAPÍTULO 8 Comprensión de los conceptos básicos de gráficos 165
Machine Translated by Google
USO DE ESPACIO EN BLANCO EN LA SALIDA
El resultado de este ejemplo aparece en dos líneas en el libro, aunque aparece en una
sola línea en Jupyter Notebook. La adición de espacios en blanco ayuda a que el
resultado aparezca en un tamaño legible en la página; no afecta la información real. Otros
ejemplos del libro también muestran resultados en varias líneas, incluso cuando aparecen
en una sola línea en Jupyter Notebook.
Al modelar datos del mundo real, como los tweets sobre un tema en particular, los nodos también
tienden a agruparse. Se podría pensar en esta tendencia como una especie de tendencia: lo que
la gente siente que es importante ahora. El término matemático elegante para esta tendencia es
agrupación, y medir esta tendencia ayuda a comprender qué grupo de nodos es más importante
en un gráfico. Aquí está el código que utiliza para medir la agrupación en clústeres para el gráfico
de ejemplo:
nx.clustering(AGraph)
{1: 0,16666666666666666, 2: 1,0, 3: 0,3333333333333333,
4: 0,0, 5: 0,0, 6: 0,0}
El resultado muestra que es más probable que los nodos se agrupen alrededor del nodo 2,
aunque el nodo 1 tenga el grado más alto. Esto se debe a que los nodos 1 y 3 tienen grados
altos y el nodo 2 está entre ellos.
La agrupación de gráficos ayuda a comprender los datos. La técnica ayuda a mostrar que hay
nodos en el gráfico que están mejor conectados y nodos que corren el riesgo de aislarse.
Cuando comprendes cómo se conectan los elementos en un gráfico, puedes determinar cómo
fortalecer su estructura o, por el contrario, destruirla. Durante la Guerra Fría, los científicos
militares tanto de Estados Unidos como del bloque soviético estudiaron la agrupación de gráficos
para comprender mejor cómo interrumpir la cadena de suministro del otro lado en caso de
conflicto.
Centralidad informática
La centralidad se presenta de diferentes formas porque la importancia a menudo depende de
diferentes factores. Los elementos importantes de un gráfico al analizar tweets diferirán de los
elementos importantes al analizar el flujo de tráfico. Afortunadamente, NetworkX le proporciona
varios métodos para calcular la centralidad. Por ejemplo, puede calcular la centralidad en función
de los grados de los nodos. El siguiente código utiliza el gráfico modificado de la sección anterior
del capítulo. (Puede encontrar este código en el archivo A4D; 08; Graph Centrality.ipynb ).
166 PARTE 3 Explorando el mundo de los gráficos
Machine Translated by Google
importar networkx como nx
importar matplotlib.pyplot como plt
%matplotlib en línea
AGraph = nx.Graph()
Nodos = rango(1,6)
Aristas = [(1,2), (2,3), (3,4), (4,5), (1,3), (1,5), (1,6)]
AGraph.add_nodes_from(Nodos)
AGraph.add_edges_from(Bordes)
nx.grado_centralidad(AGraph)
{1: 0,8, 2: 0,4, 3: 0,6000000000000001, 4: 0,4, 5: 0,4,
6: 0,2}
Los valores difieren según el número de conexiones para cada nodo. Debido a que el nodo
1 tiene cuatro conexiones (tiene el grado más alto), también tiene la centralidad más alta.
Puede ver cómo funciona esto trazando el gráfico usando una llamada a nx.draw(AGraph,
with_labels=True), como se muestra en la Figura 8­5.
FIGURA 8­5:
Trazar el gráfico
puede ayudarle a
ver la centralidad de
grados con mayor facilidad.
CAPÍTULO 8 Comprender los conceptos básicos de gráficos 167
Machine Translated by Google
De hecho, el nodo 1 está en el centro del gráfico con la mayor cantidad de conexiones.
El grado del nodo 1 garantiza que sea el más importante según la cantidad de conexiones.
Cuando trabaje con gráficos dirigidos, también puede usar in_title_centrality ()
y funciones out_title_centrality() para determinar el grado de centralidad según el tipo de
conexión en lugar de solo el número de conexiones.
Al trabajar con análisis de tráfico, es posible que necesite determinar qué ubicaciones
son centrales en función de su distancia a otros nodos. Aunque un centro comercial en
los suburbios puede tener todo tipo de conexiones, el hecho de que esté en los suburbios
puede reducir su impacto en el tráfico. Sin embargo, un supermercado en el centro de la
ciudad con pocas conexiones podría tener un gran impacto en el tráfico porque está
cerca de muchos otros nodos. Para ver cómo funciona esto, agregue otro nodo, 7, que
esté desconectado del gráfico. La centralidad de ese nodo es infinita porque ningún otro
nodo puede alcanzarlo. El siguiente código muestra cómo calcular la centralidad de
cercanía para los distintos nodos en el gráfico de ejemplo:
AGraph.add_node(7)
nx.closeness_centrality(AGraph)
{1: 0,6944444444444445,
2: 0,5208333333333334,
3: 0,5952380952380952,
4: 0,462962962962963,
5: 0,5208333333333334,
6: 0,4166666666666667,
7: 0,0}
El resultado muestra la centralidad de cada nodo en el gráfico en función de su cercanía
a todos los demás nodos. Observe que el nodo 7 tiene un valor de 0, lo que significa que
está a una distancia infinita de todos los demás nodos. Por otro lado, el nodo 1 tiene un
valor alto porque está cerca de todos los nodos con los que tiene conexión. Al calcular la
centralidad de cercanía, puede determinar qué nodos son los más importantes en función
de su ubicación.
Otra forma de centralidad a distancia es la intermediación. Supongamos que dirige una
empresa que transfiere mercancías por toda la ciudad. Le gustaría saber qué nodos
tienen el mayor efecto en estas transferencias. Quizás pueda enrutar algo de tráfico
alrededor de este nodo para que su operación sea más específica. Al calcular la
centralidad de intermediación, se determina el nodo que tiene el mayor número de
caminos cortos que llegan a él. Aquí está el código utilizado para realizar este cálculo
(con el nodo 7 desconectado todavía en su lugar):
nx.betweenness_centrality(AGraph)
{1: 0,36666666666666664,
168 PARTE 3 Explorando el mundo de los gráficos
Machine Translated by Google
2: 0,0,
3: 0,13333333333333333,
4: 0,03333333333333333,
5: 0,06666666666666667,
6: 0,0,
7: 0,0}
Como es de esperar, el nodo 7 no tiene ningún efecto en la transferencia entre otros nodos
porque no tiene conexiones con los otros nodos. Asimismo, debido a que el nodo 6 es un nodo
hoja con una sola conexión a otro nodo, no tiene ningún efecto en las transferencias. Mire
nuevamente la Figura 8­5. El subgrafo que consta de los nodos 1, 3, 4 y 5 tiene el mayor efecto
en la transferencia de elementos en este caso. No existe conexión entre los nodos 1 y 4, por lo
que los nodos 3 y 5 actúan como intermediarios. En este caso, el nodo 2 actúa como un nodo
hoja.
NetworkX le proporciona otras funciones de centralidad. Encontrará una lista completa
de estas funciones en http://networkx.readthedocs.io/en/
estable/referencia/algoritmos.centrality.html. La consideración importante es determinar cómo
desea calcular la importancia. Es esencial considerar la centralidad a la luz del tipo de
importancia que se desea otorgar a los vértices y aristas en un gráfico.
Poner un gráfico en formato numérico
La precisión es una parte importante del uso de algoritmos. Aunque demasiada precisión
oculta las imágenes generales a los humanos, las computadoras prosperan con los detalles. A
menudo, cuanto más detalles pueda proporcionar, mejores serán los resultados que reciba. Sin
embargo, la forma de ese detalle es importante. Para utilizar ciertos algoritmos, los datos que
proporcionas deben aparecer en ciertos formularios o el resultado que recibas no tendrá sentido
(contendrá errores o tendrá otros problemas).
Afortunadamente, NetworkX proporciona una serie de funciones para convertir su gráfico
en formularios que otros paquetes y entornos puedan usar. Estas funciones aparecen
en http://networkx.readthedocs.io/en/stable/reference/convert.html. Las siguientes
secciones muestran cómo presentar datos gráficos como NumPy (http://www.
numpy.org/) matriz, SciPy (https://www.scipy.org/) representación escasa y una lista estándar
de Python. Estas presentaciones se utilizan a medida que avanza el libro para trabajar con los
distintos algoritmos. (El código de las siguientes secciones aparece en el archivo A4D; 08;
Graph Conversion.ipynb y se basa en el gráfico que creó en la sección “Conteo de aristas y
vértices” del capítulo).
CAPÍTULO 8 Comprensión de los conceptos básicos de gráficos 169
Machine Translated by Google
Agregar un gráfico a una matriz
Con NetworkX, puede mover fácilmente su gráfico a una matriz NumPy y viceversa según
sea necesario para realizar diversas tareas. Utiliza NumPy para realizar todo tipo de tareas
de manipulación de datos. Al analizar los datos en un gráfico, es posible que vea patrones
que normalmente no serían visibles. Aquí está el código utilizado para convertir el gráfico
en una matriz que NumPy pueda entender:
importar networkx como nx
importar matplotlib.pyplot como plt
%matplotlib en línea
AGraph = nx.Graph()
Nodos = rango(1,6)
Aristas = [(1,2), (2,3), (3,4), (4,5), (1,3), (1,5), (1,6)]
AGraph.add_nodes_from(Nodos)
AGraph.add_edges_from(Bordes)
nx.to_numpy_matrix(AGraph)
matriz([[ 0., 1., 1., 0., 1., 1.],
[ 1., 0., 1., 0., 0., 0.],
[ 1., 1., 0., 1., 0., 0.],
[ 0., 0., 1., 0., 1., 0.],
[ 1., 0., 0., 1., 0., 0.],
[ 1., 0., 0., 0., 0., 0.]])
Las filas y columnas resultantes muestran dónde existen conexiones. Por ejemplo, no hay
conexión entre el nodo 1 y él mismo, por lo que la fila 1, columna 1, tiene un 0.
Sin embargo, hay una conexión entre el nodo 1 y el nodo 2, por lo que verá un 1 en la fila
1, columna 2 y en la fila 2, columna 1 (lo que significa que la conexión va en ambos
sentidos como una conexión no dirigida).
El tamaño de esta matriz se ve afectado por el número de nodos (la matriz tiene tantas filas
y columnas como nodos), y cuando crece, tiene muchos nodos que representar porque
el número total de celdas es el cuadrado de la matriz. número de nodos. Por ejemplo, no
se puede representar Internet utilizando una matriz de este tipo porque una estimación
conservadora calcula que en 10^10 sitios web, se necesitaría una matriz con 10^20 celdas
para almacenar su estructura, algo imposible con la informática actual. capacidad.
170 PARTE 3 Explorando el mundo de los gráficos
Machine Translated by Google
Además, el número de nodos afecta su contenido. Si n es el número de nodos, encontrará un
mínimo de (n−1) unos y un máximo de n(n−1) unos. El hecho de que el número de unos sea
pequeño o grande hace que el gráfico sea denso o escaso, y eso es relevante porque si la
conexión entre nodos es poca, como en el caso de los sitios web, existen soluciones más
eficientes para almacenar datos del gráfico. .
Usando representaciones dispersas
El paquete SciPy también realiza diversas tareas matemáticas, científicas y de ingeniería.
Al utilizar este paquete, puede confiar en una matriz escasa para contener los datos. Una matriz
dispersa es aquella en la que sólo aparecen las conexiones reales en la matriz; todas las demás
entradas no existen. El uso de una matriz dispersa ahorra recursos porque los requisitos de
memoria para una matriz dispersa son pequeños. Aquí está el código que utiliza para crear una
matriz dispersa de SciPy a partir de un gráfico NetworkX:
print(nx.to_scipy_sparse_matrix(AGraph))
(0, 1)
1
(0, 2)
1
(0, 4)
1
(0, 5)
1
(1, 0)
1
(1, 2)
1
(2, 0)
1
(2, 1)
1
(2, 3)
1
(3, 2)
1
(3, 4)
1
(4, 0)
1
(4, 3)
1
(5, 0)
1
Como puede ver, las entradas muestran las distintas coordenadas de los bordes. Cada
coordenada activa tiene un 1 asociado. Las coordenadas están basadas en 0. Esto significa que
(0, 1) en realidad se refiere a una conexión entre los nodos 1 y 2.
Usar una lista para contener un gráfico
Dependiendo de sus necesidades, es posible que también necesite la capacidad de crear un
diccionario de listas. Muchos desarrolladores utilizan este enfoque para crear código que
CAPÍTULO 8 Comprensión de los conceptos básicos de gráficos 171
Machine Translated by Google
Realiza diversas tareas de análisis en gráficos. Puede ver uno de esos ejemplos en https://
www.python.org/doc/essays/graphs/. El siguiente código muestra cómo crear un diccionario
de listas para el gráfico de ejemplo:
nx.to_dict_of_lists(AGraph)
{1: [2, 3, 5, 6], 2: [1, 3], 3: [1, 2, 4], 4: [3, 5],
5: [1, 4], 6: [1]}
Observe que cada nodo representa una entrada del diccionario, seguida de una lista de los
nodos a los que se conecta. Por ejemplo, el nodo 1 se conecta a los nodos 2, 3, 5 y 6.
172 PARTE 3 Explorando el mundo de los gráficos
Machine Translated by Google
EN ESTE CAPÍTULO
» Trabajar con gráficos
» Realizar tareas de clasificación
» Reducir el tamaño del árbol
» Localizar la ruta más corta entre dos puntos
Capítulo 9
Reconectando los puntos
formar una serie de tareas. Un gráfico es simplemente un conjunto de vértices,
Este capítulo
sobre conectados
cómo trabajarpor
conaristas,
gráficos.
Usaso gráficos
todos poner
los días
para
realizar
nodostrata
o puntos
arcos
líneas. Para
esta
definición
en términos más simples, cada vez que usas un mapa, usas un gráfico. El punto de
partida, los puntos intermedios y el destino son todos nodos. Estos nodos se conectan
entre sí con calles, que representan las líneas. El uso de gráficos le permite describir
relaciones de varios tipos. La razón por la que funcionan las configuraciones del Sistema
de Posicionamiento Global (GPS) es que puedes usar las matemáticas para describir las
relaciones entre los puntos en el mapa y las calles que los conectan. De hecho, cuando
termine este capítulo, comprenderá la base utilizada para crear un GPS (pero no
necesariamente la mecánica para hacerlo realidad). Por supuesto, el requisito
fundamental para utilizar un gráfico para crear un GPS es la capacidad de buscar
conexiones entre puntos en el mapa, como se analiza en la primera sección del capítulo.
Para que un gráfico tenga sentido, es necesario ordenar los nodos, como se describe en
la segunda sección del capítulo, para crear una organización específica. Sin organización,
tomar cualquier tipo de decisión se vuelve imposible. Un algoritmo podría terminar dando
vueltas en círculos o dando resultados inconvenientes. Por ejemplo, algunas de las
primeras configuraciones de GPS no encontraban correctamente la distancia más corta
entre dos puntos o, en ocasiones, terminaban enviando a alguien al lugar equivocado.
Parte de la razón de estos problemas es la necesidad de ordenar los datos para que pueda
verlos de la misma manera cada vez que el algoritmo atraviesa los nodos (proporcionándole
una ruta entre su hogar y su negocio).
Cuando ves un mapa, no miras la información en la esquina inferior derecha cuando en
realidad necesitas trabajar con ubicaciones y carreteras en la esquina superior izquierda.
CAPÍTULO 9 Reconectando los puntos 173
Machine Translated by Google
Una computadora no sabe que necesita buscar en un lugar específico hasta que usted le dice que lo haga.
Para centrar la atención en una ubicación específica, es necesario reducir el tamaño del gráfico, como se
describe en la tercera sección del capítulo.
Ahora que el problema está simplificado, un algoritmo puede encontrar la ruta más corta entre dos puntos,
como se describe en la cuarta sección del capítulo. Después de todo, no querrás pasar más tiempo del
necesario en el tráfico luchando entre tu casa y la oficina (y viceversa). El concepto de encontrar la ruta más
corta es un poco más complicado de lo que podría pensar, por lo que la cuarta sección analiza en detalle
algunos de los requisitos específicos para realizar tareas de enrutamiento.
Atravesar un gráfico de manera eficiente
Recorrer un gráfico significa buscar (visitar) cada vértice (nodo) en un orden específico.
El proceso de visitar un vértice puede incluir tanto leerlo como actualizarlo. A medida que recorre un gráfico, no
se descubre un vértice no visitado. Después de una visita, el vértice se descubre (porque acaba de visitarlo) o
se procesa (porque el algoritmo probó todas las aristas que parten de él). El orden de la búsqueda determina el
tipo de búsqueda realizada y hay muchos algoritmos disponibles para realizar esta tarea. Las siguientes
secciones analizan dos de estos algoritmos.
CONSIDERANDO EL REDUNDANCIA
Al atravesar un árbol, cada camino termina en un nodo de hoja para que sepas que has llegado al final
de ese camino. Sin embargo, cuando se trabaja con un gráfico, los nodos se interconectan de tal manera
que es posible que tenga que atravesar algunos nodos más de una vez para explorar el gráfico completo.
A medida que el gráfico se vuelve más denso, aumenta la posibilidad de visitar el mismo nodo más de
una vez. Los gráficos densos pueden aumentar considerablemente los requisitos tanto computacionales
como de almacenamiento.
Para reducir los efectos negativos de visitar un nodo más de una vez, es común marcar cada nodo
visitado de alguna manera para mostrar que el algoritmo lo ha visitado. Cuando el algoritmo detecta que
ha visitado un nodo en particular, puede simplemente omitir ese nodo y pasar al siguiente nodo en la
ruta. Marcar los nodos visitados reduce las penalizaciones de rendimiento inherentes a la redundancia.
Marcar los nodos visitados también permite verificar que la búsqueda esté completa. De lo contrario, un
algoritmo puede terminar en un bucle y continuar recorriendo el gráfico indefinidamente.
174 PARTE 3 Explorando el mundo de los gráficos
Machine Translated by Google
Creando el gráfico
Para ver cómo podría funcionar atravesar un gráfico, necesita un gráfico. Los ejemplos de esta
sección se basan en un gráfico común para que pueda ver cómo funcionan las dos técnicas.
El siguiente código muestra la lista de adyacencia que se encuentra al final del Capítulo 8. (Puede
encontrar este código en el archivo A4D; 09; Graph Traversing.ipynb en el sitio de Dummies
como parte del código descargable; consulte la Introducción para obtener más detalles).
gráfico = {'A': ['B', 'C'],
'B': ['A', 'C', 'D'],
'C': ['A', 'B', 'D', 'E'],
'D': ['B', 'C', 'E', 'F'],
'E': ['C', 'D', 'F'],
'F': ['D', 'E']}
El gráfico presenta una ruta bidireccional que va desde A, B, D y F en un lado (comenzando en
la raíz) y A, C, E y F en el segundo lado (nuevamente, comenzando en la raíz). También hay
conexiones (que actúan como posibles atajos) que van de B a C, de C a D y de D a E. El uso del
paquete NetworkX presentado en el Capítulo 8 le permite mostrar la adyacencia como una
imagen para que pueda ver cómo los vértices y aristas aparecen (ver Figura 9­1) usando el
siguiente código:
importar numpy como np
importar networkx como nx
importar matplotlib.pyplot como plt
%matplotlib en línea
Gráfico = nx.Graph()
para nodo en el gráfico:
Graph.add_nodes_from(nodo)
para borde en gráfico [nodo]:
Graph.add_edge(nodo,borde)
pos = {'A': [0,00, 0,50], 'B': [0,25, 0,75],
'C': [0,25, 0,25], 'D': [0,75, 0,75],
'E': [0,75, 0,25], 'F': [1,00, 0,50]}
nx.draw(Gráfico, pos, with_labels=True)
nx.draw_networkx(Gráfico, pos)
plt.mostrar()
CAPÍTULO 9 Reconectando los puntos 175
Machine Translated by Google
FIGURA 9­1:
Representando el
gráfico de ejemplo
de NetworkX.
Aplicar la búsqueda en amplitud
Una búsqueda en amplitud (BFS) comienza en la raíz del gráfico y explora cada
nodo que se adjunta a la raíz. Luego busca el siguiente nivel, explorando cada
nivel por turno hasta llegar al final. En consecuencia, en el gráfico de ejemplo, la
búsqueda explora de A a B y C antes de continuar para explorar D. BFS explora el
gráfico de forma sistemática, explorando los vértices alrededor del vértice inicial
de forma circular. Se comienza visitando todos los vértices a un solo paso del
vértice inicial; luego se aleja dos pasos, luego tres pasos, y así sucesivamente. El
siguiente código demuestra cómo realizar una búsqueda en amplitud.
def bfs(gráfico, inicio):
cola = [inicio]
en cola = lista()
ruta = lista()
mientras cola:
imprimir ('La cola es: %s' % cola)
vértice = cola.pop(0)
imprimir ('Procesando %s' % vértice)
para candidato en gráfico[vértice]:
si el candidato no está en la cola:
en cola.append(candidato)
cola.append(candidato)
ruta.append(vértice+'>'+candidato)
print ('Agregando %s a la cola'
% candidato)
vía de retorno
176 PARTE 3 Explorando el mundo de los gráficos
Machine Translated by Google
pasos = bfs(gráfico, 'A')
imprimir ('\nBFS:', pasos)
La cola es: ['A']
Procesamiento A
Agregando B a la cola
Agregando C a la cola
La cola es: ['B', 'C']
Procesamiento B
Agregar A a la cola
Agregando D a la cola
La cola es: ['C', 'A', 'D']
Procesamiento C
Agregando E a la cola
La cola es: ['A', 'D', 'E']
Procesamiento A
La cola es: ['D', 'E']
Procesamiento D
Agregando F a la cola
La cola es: ['E', 'F']
Procesamiento E
La cola es: ['F']
Procesamiento F
BFS: ['A>B', 'A>C', 'B>A', 'B>D', 'C>E', 'D>F']
El resultado muestra cómo busca el algoritmo. Está en el orden esperado: un nivel a la vez.
La mayor ventaja de usar BFS es que se garantiza que devolverá la ruta más corta entre dos
puntos como primera salida cuando se usa para buscar rutas.
El código de ejemplo utiliza una lista simple como cola. Como se describe en el Capítulo 4, una
cola es una estructura de datos de primero en entrar/primero en salir (FIFO) que funciona
como una línea en un banco, donde el primer artículo que se pone en la cola es también el
primer artículo que sale. Para este propósito, Python proporciona una estructura de datos aún
mejor llamada deque (pronunciado deck). Lo creas usando la función deque del paquete de
colecciones . Realiza inserciones y extracciones en tiempo lineal y puede usarlo como cola y
pila. Puede descubrir más sobre la función deque en https://
pymotw.com/2/collections/deque.html.
Aplicar la búsqueda en profundidad
Además de BFS, puede utilizar una búsqueda en profundidad (DFS) para descubrir los
vértices en un gráfico. Al realizar un DFS, el algoritmo comienza en la raíz del gráfico y
CAPÍTULO 9 Reconectando los puntos 177
Machine Translated by Google
luego explora cada nodo desde esa raíz por un único camino hasta el final. Luego
retrocede y comienza a explorar las rutas no tomadas en la ruta de búsqueda actual
hasta que llega a la raíz nuevamente. En ese momento, si hay otras rutas disponibles
para tomar desde la raíz, el algoritmo elige una y comienza la misma búsqueda
nuevamente. La idea es explorar cada camino por completo antes de explorar cualquier
otro camino. Para que esta técnica de búsqueda funcione, el algoritmo debe marcar
cada vértice que visita. De esta manera, sabe qué vértices requieren una visita y puede
determinar qué camino tomar a continuación. Usar BFS o DFS puede marcar la
diferencia según la forma en que necesites recorrer un gráfico. Desde el punto de vista
de la programación, la diferencia entre los dos algoritmos es cómo cada uno almacena
los vértices para explorar lo siguiente:
» Una cola para BFS, una lista que funciona según el principio FIFO. Recién
los vértices descubiertos no esperan mucho para ser procesados.
» Una pila para DFS, una lista que funciona según el último en entrar/primero en salir (LIFO)
principio.
El siguiente código muestra cómo crear un DFS:
def dfs(gráfico, inicio):
pila = [inicio]
padres = {inicio: inicio}
ruta = lista()
mientras se apila:
imprimir ('La pila es: %s' % pila)
vértice = pila.pop(­1)
imprimir ('Procesando %s' % vértice)
para candidato en gráfico[vértice]:
si el candidato no está en los padres:
padres[candidato] = vértice
pila.append(candidato)
print ('Agregando %s a la pila'
% candidato)
ruta.append(padres[vértice]+'>'+vértice)
ruta de retorno[1:]
pasos = dfs(gráfico, 'A')
imprimir ('\nDFS:', pasos)
La pila es: ['A']
Procesamiento A
Agregando B a la pila
Agregando C a la pila
178 PARTE 3 Explorando el mundo de los gráficos
Machine Translated by Google
La pila es: ['B', 'C']
Procesamiento C
Agregando D a la pila
Agregando E a la pila
La pila es: ['B', 'D', 'E']
Procesamiento E
Agregando F a la pila
La pila es: ['B', 'D', 'F']
Procesamiento F
La pila es: ['B', 'D']
Procesamiento D
La pila es: ['B']
Procesamiento B
DFS: ['A>C', 'C>E', 'E>F', 'C>D', 'A>B']
La primera línea de resultado muestra el orden de búsqueda real. Tenga en cuenta que la
búsqueda comienza en la raíz, como se esperaba, pero luego sigue por el lado izquierdo del
gráfico hasta el principio. El último paso es buscar la única rama del bucle que crea el gráfico en
este caso, que es D.
Tenga en cuenta que la salida no es la misma que para BFS. En este caso, la ruta de
procesamiento comienza con el nodo A y se mueve al lado opuesto del gráfico, al nodo F.
Luego, el código retrocede para buscar rutas pasadas por alto. Como se analizó, este
comportamiento depende del uso de una estructura de pila en lugar de una cola. Depender de
una pila significa que también se puede implementar este tipo de búsqueda mediante recursividad.
El uso de la recursividad haría que el algoritmo fuera más rápido, por lo que podría obtener
resultados más rápido que cuando se utiliza un BFS. La desventaja es que utiliza más memoria
cuando utiliza la recursividad.
Cuando su algoritmo usa una pila, usa el último resultado disponible (a diferencia de una cola,
donde usaría el primer resultado colocado en la cola). Las funciones recursivas producen un
resultado y luego se aplican usando ese mismo resultado.
Una pila hace exactamente lo mismo en una iteración: el algoritmo produce un resultado, el
resultado se coloca en una pila y luego el resultado se toma inmediatamente de la pila y se
procesa nuevamente.
Determinar qué aplicación utilizar
La elección entre BFS y DFS depende de cómo planea aplicar el resultado de la búsqueda. Los
desarrolladores suelen emplear BFS para localizar la ruta más corta entre dos puntos lo más
rápido posible. Esto significa que normalmente se utiliza BFS en aplicaciones como GPS, donde
encontrar la ruta más corta es primordial. Para los propósitos de este libro, también verá BFS
utilizado para el árbol de expansión, la ruta más corta y muchos otros algoritmos de minimización.
CAPÍTULO 9 Reconectando los puntos 179
Machine Translated by Google
Un DFS se centra en encontrar un camino completo antes de explorar cualquier otro camino. Lo utiliza cuando
necesita buscar en detalle, en lugar de hacerlo de forma general. Por esta razón, a menudo se ve DFS utilizado
en juegos, donde es importante encontrar una ruta completa. También es un enfoque óptimo para realizar
tareas como encontrar una solución a un laberinto.
A veces hay que decidir entre BFS y DFS según las limitaciones de cada técnica. BFS necesita mucha memoria
porque almacena sistemáticamente todas las rutas antes de encontrar una solución. Por otro lado, DFS necesita
menos memoria, pero no hay garantía de que encontrará la solución más corta y directa.
Ordenar los elementos del gráfico
La capacidad de buscar gráficos de manera eficiente depende de la clasificación. Después de todo, imagine ir a
una biblioteca y encontrar los libros colocados en el orden que la biblioteca quisiera colocar en los estantes.
Localizar un solo libro llevaría horas. Una biblioteca funciona porque los libros individuales aparecen en
ubicaciones específicas que los hacen fáciles de encontrar.
Las bibliotecas también exhiben otra propiedad que es importante cuando se trabaja con ciertos tipos de
gráficos. Al realizar una búsqueda de libros, comienza con una categoría específica, luego una fila de libros,
luego un estante en esa fila y finalmente el libro. Pasas de menos específico a más específico al realizar la
búsqueda, lo que significa que no vuelves a visitar los niveles anteriores. Por lo tanto, no terminarás en partes
extrañas de la biblioteca que no tienen nada que ver con el tema en cuestión.
GRÁFICOS CON BUCLES
A veces es necesario expresar un proceso de tal manera que se repita un conjunto de pasos.
Por ejemplo, al lavar el coche, se enjuaga, se enjabona y luego se vuelve a enjuagar.
Sin embargo, encuentras una mancha sucia, una zona que el jabón no limpió la primera vez. Para limpiar esa mancha,
la enjabonas nuevamente y la enjuagas nuevamente para verificar que la mancha haya desaparecido.
Desafortunadamente, es una mancha muy rebelde, por lo que repites el proceso nuevamente. De
hecho, repites los pasos de jabón y enjuague hasta que la mancha esté limpia. Eso es lo que hace un bucle;
crea una situación en la que un conjunto de pasos se repite de dos maneras:
• Cumple una condición específica: La mancha del auto ha desaparecido.
• Realiza un número específico de veces: este es el número de repeticiones que
realizar durante el ejercicio.
180 PARTE 3 Explorando el mundo de los gráficos
Machine Translated by Google
Las siguientes secciones revisan los gráficos acíclicos dirigidos (DAG), que son gráficos dirigidos finitos
que no tienen bucles. En otras palabras, comienzas desde una ubicación particular y sigues una ruta
específica hasta una ubicación final sin regresar nunca a la ubicación inicial. Cuando se utiliza la
clasificación topológica, un DAG siempre dirige los vértices anteriores a los vértices posteriores. Este tipo
de gráfico tiene todo tipo de usos prácticos, como horarios, y cada hito representa un hito en particular.
Trabajar en gráficos acíclicos dirigidos (DAG)
Los DAG son uno de los tipos de gráficos más importantes porque tienen muchos usos prácticos. Los
principios básicos de los DAG son que
» Siga un orden particular para que no pueda pasar de un vértice a otro y
volver al vértice inicial utilizando cualquier ruta.
» Proporcione una ruta específica de un vértice a otro para que pueda crear una
conjunto predecible de rutas.
Verá que los DAG se utilizan para muchas necesidades organizativas. Por ejemplo, un árbol genealógico
es un ejemplo de DAG. Incluso cuando la actividad no sigue un orden cronológico u otro orden primordial,
el DAG le permite crear rutas predecibles, lo que hace que los DAG sean más fáciles de procesar que
muchos otros tipos de gráficos con los que trabaja.
Sin embargo, los DAG pueden utilizar rutas opcionales. Imagina que estás preparando una hamburguesa.
El sistema de menú comienza con un fondo de bollo. Opcionalmente, puedes agregar condimentos al
fondo del pan o puedes pasar directamente a la hamburguesa en el pan. La ruta siempre termina con una
hamburguesa, pero tienes varios caminos para llegar a la hamburguesa. Una vez que tengas la
hamburguesa en su lugar, puedes optar por agregar queso o tocino antes de agregar la parte superior del
pan. El punto es que tomas un camino específico, pero cada camino puede conectar con el siguiente nivel
de varias maneras diferentes.
Hasta ahora, el capítulo le ha mostrado algunos tipos diferentes de configuraciones de gráficos, algunas
de las cuales pueden aparecer en combinación, como un gráfico denso, ponderado y dirigido:
» Dirigido: Los bordes tienen una sola dirección y pueden tener estas direcciones adicionales.
propiedades:
• Cíclico: Las aristas forman un ciclo que te lleva de regreso al vértice inicial después
habiendo visitado los vértices intermediarios.
• A­cíclico: Este gráfico carece de ciclos.
» No dirigido: las aristas conectan vértices en ambas direcciones.
CAPÍTULO 9 Reconectando los puntos 181
Machine Translated by Google
» Ponderado: Cada borde tiene un costo asociado, como tiempo, dinero o energía, que debes
pagar para atravesarlo.
» No ponderado: Todas las aristas no tienen coste o tienen el mismo coste.
» Denso: Un gráfico que tiene una gran cantidad de aristas en comparación con la cantidad
de vértices.
» Escaso: un gráfico que tiene una pequeña cantidad de aristas en comparación con la
cantidad de vértices.
Depender de la clasificación topológica
Un elemento importante de los DAG es que puede representar una gran variedad de
actividades utilizándolos. Sin embargo, algunas actividades requieren que abordes las tareas
en un orden específico. Aquí es donde entra en juego la clasificación topológica. La
clasificación topológica ordena todos los vértices de un gráfico en una línea con los bordes
directos apuntando de izquierda a derecha. Dispuesto de esta manera, el código puede
atravesar fácilmente el gráfico y procesar los vértices uno tras otro, en orden.
Cuando utiliza la clasificación topológica, organiza el gráfico de modo que cada vértice del
gráfico conduzca a un vértice posterior en la secuencia. Por ejemplo, al crear un cronograma
para la construcción de un rascacielos, no se comienza desde arriba y se avanza hacia abajo.
Comienzas con la base y avanzas hacia arriba. Cada piso puede representar un hito.
Cuando completes el segundo piso, no vas al tercero y luego rehaces el segundo piso. En
lugar de eso, pasas del tercer piso al cuarto piso, y así sucesivamente. Cualquier tipo de
programación que requiera moverse desde un punto de partida específico a un punto final
específico puede depender de un DAG con clasificación topológica.
La clasificación topológica puede ayudarle a determinar que su gráfico no tiene ciclos (porque
de lo contrario, no puede ordenar los bordes que conectan los vértices de izquierda a derecha;
al menos un nodo se referirá a un nodo anterior). Además, la clasificación topológica también
resulta útil en algoritmos que procesan gráficos complejos porque muestra el mejor orden
para procesarlos.
Puede obtener una clasificación topológica utilizando el algoritmo transversal DFS.
Simplemente observe el orden de procesamiento de los vértices por parte del algoritmo. En
el ejemplo anterior, la salida aparece en este orden: A, C, E, F, D y B. Siga la secuencia en la
Figura 9­1 y observará que la clasificación topológica sigue los bordes en el perímetro externo
del gráfico. Luego hace un recorrido completo: después de llegar al último nodo del tipo
topológico, estás a sólo un paso de A, el inicio de la secuencia.
182 PARTE 3 Explorando el mundo de los gráficos
Machine Translated by Google
Reducir a un árbol de expansión mínimo
Muchos de los problemas que resuelven los algoritmos dependen de definir un mínimo de
recursos a utilizar, como definir una forma económica de llegar a todos los puntos de un mapa.
Este problema fue fundamental a finales del siglo XIX y principios del XX, cuando comenzaron
a aparecer redes ferroviarias y eléctricas en muchos países, revolucionando el transporte y las
formas de vida. Utilizar empresas privadas para construir dichas redes era caro (requería mucho
tiempo y trabajadores). El uso de menos material y una menor fuerza laboral ofreció ahorros al
reducir las conexiones redundantes.
Es deseable cierta redundancia en redes críticas de transporte o energía, incluso cuando se
busca soluciones económicas. De lo contrario, si solo un método conecta la red, ésta se
interrumpe fácilmente accidentalmente o por un acto voluntario (como un acto de guerra),
interrumpiendo los servicios a muchos clientes.
En Moravia, al este de la República Checa, el matemático checo Otakar Borůvka encontró en
1926 una solución que permite construir una red eléctrica utilizando la menor cantidad de cables
posible. Su solución es bastante eficiente porque no sólo permite encontrar una manera de
conectar todos los pueblos de Moravia de la forma más económica posible, sino que además
tenía una complejidad temporal de O(m*log n), donde m es el número de aristas ( el cable
eléctrico) y n el número de vértices (los pueblos). Otros han mejorado la solución de Borůvka
desde entonces. (De hecho, los expertos en algoritmos lo olvidaron parcialmente y luego lo
redescubrieron). Aunque los algoritmos que se encuentran en los libros están mejor diseñados
y son más fáciles de entender (los de Prim y Krus­kal), no logran mejores resultados en
términos de tiempo. complejidad.
Un árbol de expansión mínimo define el problema de encontrar la forma más económica de
realizar una tarea. Un árbol de expansión es la lista de aristas necesarias para conectar todos
los vértices en un gráfico no dirigido. Un solo gráfico podría contener múltiples árboles de
expansión, dependiendo de la disposición del gráfico, y determinar cuántos árboles contiene es
una cuestión compleja. Cada camino que puede seguir desde el principio hasta el final en un
gráfico es otro árbol de expansión. El árbol de expansión visita cada vértice sólo una vez; no
realiza un bucle ni hace nada para repetir los elementos de la ruta.
Cuando trabajas en un gráfico no ponderado, los árboles de expansión tienen la misma longitud.
En los gráficos no ponderados, todos los bordes tienen la misma longitud y el orden en que los
visita no importa porque la ruta de ejecución es siempre la misma. Todos los árboles de
expansión posibles tienen el mismo número de aristas, n­1 aristas (n es el número de vértices),
de exactamente la misma longitud. Además, cualquier algoritmo de recorrido de gráficos, como
BFS o DFS, es suficiente para encontrar uno de los posibles árboles de expansión.
Las cosas se vuelven complicadas cuando se trabaja con un gráfico ponderado con aristas de
diferentes longitudes. En este caso, de los muchos árboles de expansión posibles, algunos, o
sólo uno, tienen la longitud mínima posible. Un árbol de expansión mínimo es el que abarca
CAPÍTULO 9 Reconectando los puntos 183
Machine Translated by Google
árbol que garantiza un camino con el menor peso de borde posible. Un gráfico no dirigido generalmente
contiene solo un árbol de expansión mínimo, pero, nuevamente, depende de la configuración. Piense
en los árboles de expansión mínima de esta manera: cuando mira un mapa, ve varios caminos para
llegar del punto A al punto B. Cada camino tiene lugares donde debe girar o cambiar de camino, y
cada uno de estos cruces es un vértice. La distancia entre vértices representa el peso del borde.
Generalmente, un camino entre el punto A y el punto B proporciona la ruta más corta.
Sin embargo, los árboles de cobertura mínima no siempre tienen por qué considerar lo obvio. Por
ejemplo, al considerar mapas, es posible que no le interese la distancia; en su lugar, es posible que
desee considerar el tiempo, el consumo de combustible o muchas otras necesidades.
Cada una de estas necesidades podría tener un árbol de expansión mínimo completamente diferente.
Con esto en mente, las siguientes secciones lo ayudarán a comprender mejor los árboles de expansión
mínima y demostrarán cómo resolver el problema de calcular el peso de borde más pequeño para
cualquier problema determinado. Para demostrar una solución de árbol de expansión mínima usando
Python, el siguiente código actualiza el gráfico anterior agregando pesos de borde. (Puede encontrar
este código en A4D; 09; Árbol de expansión mínima.
archivo ipynb en el sitio Dummies como parte del código descargable; consulte la Introducción para
obtener más detalles).
importar numpy como np
importar networkx como nx
importar matplotlib.pyplot como plt
%matplotlib en línea
gráfico = {'A': {'B':2, 'C':3},
'B': {'A':2, 'C':2, 'D':2},
'C': {'A':3, 'B':2, 'D':3, 'E':2},
'D': {'B':2, 'C':3, 'E':1, 'F':3},
'E': {'C':2, 'D':1, 'F':1},
'F': {'D':3, 'E':1}}
Gráfico = nx.Graph()
para nodo en el gráfico:
Graph.add_nodes_from(nodo)
para borde, peso en gráfico[nodo].items():
Graph.add_edge(nodo,borde, peso=peso)
pos = {'A': [0,00, 0,50], 'B': [0,25, 0,75],
'C': [0,25, 0,25], 'D': [0,75, 0,75],
'E': [0,75, 0,25], 'F': [1,00, 0,50]}
etiquetas = nx.get_edge_attributes(Gráfico,'peso')
nx.draw(Gráfico, pos, with_labels=True)
184 PARTE 3 Explorando el mundo de los gráficos
Machine Translated by Google
nx.draw_networkx_edge_labels(Gráfico, pos,
edge_labels=etiquetas)
nx.draw_networkx(Gráfico,pos)
plt.mostrar()
La Figura 9­2 muestra que ahora todas las aristas tienen un valor. Este valor puede representar
algo como tiempo, combustible o dinero. Los gráficos ponderados pueden representar muchos
posibles problemas de optimización que ocurren en el espacio geográfico (como el movimiento entre
ciudades) porque representan situaciones en las que se puede ir y venir desde un vértice.
FIGURA 9­2:
El gráfico de
ejemplo se vuelve
ponderado.
Curiosamente, todas las aristas tienen pesos positivos en este ejemplo. Sin embargo, los gráficos
ponderados pueden tener pesos negativos en algunos bordes. Muchas situaciones aprovechan las
ventajas negativas. Por ejemplo, son útiles cuando se puede ganar y perder al moverse entre
vértices, como ganar o perder dinero al transportar o intercambiar mercancías, o liberar energía en
un proceso químico.
No todos los algoritmos son adecuados para manejar flancos negativos. Es importante tener en
cuenta aquellos que pueden funcionar sólo con pesos positivos.
Descubrir los algoritmos correctos a utilizar
Puede encontrar muchos algoritmos diferentes para utilizar para crear un árbol de expansión mínimo.
Los más comunes son los algoritmos codiciosos, que se ejecutan en tiempo polinomial. El tiempo
polinomial es una potencia del número de aristas, como O(n2) u O(n3) (consulte la Parte 5 para
obtener información adicional sobre el tiempo polinomial). Los principales factores que afectan la
velocidad de ejecución de dichos algoritmos involucran el proceso de toma de decisiones, es decir,
CAPÍTULO 9 Reconectando los puntos 185
Machine Translated by Google
si un borde particular pertenece al árbol de expansión mínimo o si el peso total mínimo del árbol
resultante excede un cierto valor. Con esto en mente, estos son algunos de los algoritmos disponibles
para resolver un árbol de expansión mínimo:
» Borůvka: Inventado por Otakar Borůvka en 1926 para resolver el problema de
encontrar la forma óptima de suministrar electricidad en Moravia. El algoritmo se basa en una serie de
etapas en las que identifica los bordes con menor peso en cada etapa. Los cálculos comienzan observando
los vértices individuales, encontrando el peso más pequeño para ese vértice y luego combinando
caminos para formar bosques de árboles individuales hasta crear un camino que combine todos los
bosques con el peso más pequeño.
» Prim: originalmente inventado por Jarnik en 1930, Prim lo redescubrió en 1957. Este algoritmo comienza con
un vértice arbitrario y hace crecer el árbol de expansión mínima un borde a la vez eligiendo siempre
el borde con el menor peso.
» Kruskal's: Desarrollado por Joseph Kruskal en 1956, utiliza un enfoque que
combina el algoritmo de Borůvka (creando bosques de árboles individuales) y el algoritmo de Prim
(buscando el borde mínimo para cada vértice y construyendo los bosques un borde a la vez).
» Eliminación inversa: en realidad se trata de una inversión del algoritmo de Kruskal. no lo es
comúnmente utilizado.
Estos algoritmos utilizan un enfoque codicioso. Los algoritmos codiciosos aparecen en el Capítulo 2
entre las familias de algoritmos, y se verán en detalle en el Capítulo 15. En un enfoque codicioso, el
algoritmo llega gradualmente a una solución tomando, de manera irreversible, la mejor decisión
disponible en cada paso. Por ejemplo, si necesita el camino más corto entre muchos vértices, un
algoritmo codicioso toma los bordes más cortos entre los disponibles entre todos los vértices.
Introduciendo colas prioritarias
Más adelante en este capítulo, verá cómo implementar el algoritmo de Prim y Kruskal para un árbol
de expansión mínimo, y el algoritmo de Dijkstra para el camino más corto en un gráfico usando
Python. Sin embargo, antes de poder hacer eso, necesita un método para encontrar los bordes con
el peso mínimo entre un conjunto de bordes. Una operación de este tipo implica realizar pedidos, y
ordenar elementos cuesta tiempo. Es una operación compleja, como se describe en el Capítulo 7.
Debido a que los ejemplos reordenan repetidamente los bordes, una estructura de datos llamada
cola de prioridad resulta útil.
Las colas de prioridad se basan en estructuras de datos basadas en árboles de montón que permiten
ordenar rápidamente los elementos cuando los inserta dentro del montón. Al igual que el sombrero
mágico del mago, los montones de prioridad almacenan los bordes con sus pesos y están
inmediatamente listos para proporcionarle el borde insertado cuyo peso es el mínimo entre esos almacenes.
186 PARTE 3 Explorando el mundo de los gráficos
Descargar