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