├── .gitignore ├── LICENSE ├── README.md ├── analisis_complejidad ├── cuadrados_minimos.ipynb ├── requirements.txt └── util.py ├── backtracking ├── coloreo.py ├── countries.py ├── nreinas.py ├── sudoku.py ├── sudoku_16_experto.txt ├── sudoku_16_facil.txt ├── sudoku_dificil.txt ├── sudoku_experto.txt └── sudoku_facil.txt ├── dyc └── puntos_cercanos.py ├── flujo └── carlos.py ├── mochila ├── greedy.py ├── mochila.py └── pd.py ├── pl ├── ejemplo1.py ├── ejemplo2.py ├── mochila.py ├── mochila.txt ├── scaloneta.py ├── scaloneta_10.txt ├── scaloneta_100.txt └── scaloneta_3.txt └── utils ├── __init__.py └── optional.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | .idea/* 7 | .DS_STORE 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # code generated files 135 | backtracking/country_polygons.json 136 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Martín Buchwald 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ejemplos-tda 2 | Ejemplos para mostrar en clase de Teoría de Algoritmos I 3 | -------------------------------------------------------------------------------- /analisis_complejidad/requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib 2 | seaborn 3 | numpy 4 | scipy 5 | -------------------------------------------------------------------------------- /analisis_complejidad/util.py: -------------------------------------------------------------------------------- 1 | from concurrent.futures import ProcessPoolExecutor, as_completed 2 | import time 3 | import os 4 | 5 | # Este parámetro controla cuantas veces se ejecuta el algoritmo para cada 6 | # tamaño. Esto es conveniente para reducir el error estadístico en la medición 7 | # de tiempos. Al finalizar las ejecuciones, se promedian los tiempos obtenidos 8 | RUNS_PER_SIZE = 10 9 | 10 | # Ajustar este valor si se quiere usar más de un proceso para medir los tiempos 11 | # de ejecución, o None para usar todos los procesadores disponibles. Si se usan 12 | # varios procesos, tener cuidado con el uso de memoria del sistema. 13 | MAX_WORKERS = max(1, (os.cpu_count() or 0) // 4) 14 | 15 | 16 | def _time_run(algorithm, *args): 17 | start = time.time() 18 | algorithm(*args) 19 | return time.time() - start 20 | 21 | 22 | def time_algorithm(algorithm, sizes, get_args): 23 | futures = {} 24 | total_times = {i: 0 for i in sizes} 25 | 26 | # Usa un ProcessPoolExecutor para ejecutar las mediciones en paralelo 27 | # (el ThreadPoolExecutor no sirve por el GIL de Python) 28 | with ProcessPoolExecutor(MAX_WORKERS) as p: 29 | for i in sizes: 30 | for _ in range(RUNS_PER_SIZE): 31 | futures[p.submit(_time_run, algorithm, *get_args(i))] = i 32 | 33 | for f in as_completed(futures): 34 | result = f.result() 35 | i = futures[f] 36 | total_times[i] += result 37 | 38 | return {s: t / RUNS_PER_SIZE for s, t in total_times.items()} 39 | -------------------------------------------------------------------------------- /backtracking/coloreo.py: -------------------------------------------------------------------------------- 1 | import networkx as nx 2 | from countries import show_map 3 | 4 | map = show_map(2) 5 | 6 | def crear_mapa(): 7 | g = nx.Graph() 8 | g.add_nodes_from(["Argentina", "Brasil", "Uruguay", "Chile", "Perú", "Paraguay", "Bolivia", "Ecuador", "Venezuela", 9 | "Colombia", "Surinam", "Guyana", "Guyana Francesa"]) 10 | g.add_edges_from([("Argentina", "Uruguay"), ("Argentina", "Chile"), ("Argentina", "Bolivia"), 11 | ("Argentina", "Brasil"), ("Argentina", "Paraguay"), ("Brasil", "Uruguay"), ("Brasil", "Paraguay"), 12 | ("Brasil", "Bolivia"), ("Brasil", "Surinam"), ("Brasil", "Guyana Francesa"), ("Brasil", "Guyana"), 13 | ("Brasil", "Venezuela"), ("Brasil", "Colombia"), ("Brasil", "Perú"), ("Chile", "Bolivia"), 14 | ("Chile", "Perú"), ("Paraguay", "Bolivia"), ("Perú", "Bolivia"), ("Ecuador", "Perú"), 15 | ("Ecuador", "Colombia"), ("Colombia", "Perú"), ("Colombia", "Venezuela"), ("Venezuela", "Guyana"), 16 | ("Surinam", "Guyana"), ("Surinam", "Guyana Francesa")]) 17 | return g 18 | 19 | 20 | def es_compatible(grafo, colores, v): 21 | for w in grafo.neighbors(v): 22 | if w in colores and colores[w] == colores[v]: 23 | return False 24 | return True 25 | 26 | 27 | def _coloreo_rec(grafo, k, colores, v): 28 | map.update(colores, v, "Elijo un vértice sin colorear") 29 | 30 | for color in range(k): 31 | colores[v] = color 32 | 33 | map.update(colores, v, "Elijo un color y avanzo si puedo") 34 | if not es_compatible(grafo, colores, v): 35 | map.update(colores, v, "Solución parcial inválida: elijo otro color") 36 | continue 37 | 38 | correcto = True 39 | for w in grafo.neighbors(v): 40 | if w in colores: 41 | continue 42 | map.update(colores, v, "Solución parcial válida: llamo recursivamente") 43 | if not _coloreo_rec(grafo, k, colores, w): 44 | correcto = False 45 | map.update(colores, v, "Solución parcial inválida: elijo otro color") 46 | break 47 | if correcto: 48 | return True 49 | del colores[v] 50 | map.update(colores, v, "Solución parcial inválida: vuelvo") 51 | return False 52 | 53 | 54 | def coloreo(grafo, k): 55 | colores = {} 56 | if _coloreo_rec(grafo, k, colores, "Argentina"): 57 | map.update(colores, "", "Solución encontrada") 58 | print(colores) 59 | return True 60 | else: 61 | map.update(colores, "", "No se encontró solución") 62 | print(colores) 63 | return False 64 | 65 | 66 | if __name__ == "__main__": 67 | print(coloreo(crear_mapa(), 4)) 68 | map.wait_for_close() 69 | -------------------------------------------------------------------------------- /backtracking/countries.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import Any, Callable 3 | from itertools import chain 4 | from dataclasses import dataclass 5 | import pathlib 6 | 7 | import requests 8 | from matplotlib.axes import Axes 9 | import matplotlib.pyplot as plt 10 | from matplotlib.collections import LineCollection 11 | from matplotlib.widgets import Slider 12 | from mpl_toolkits.basemap import Basemap # type: ignore 13 | 14 | POLYGONS_PATH = pathlib.Path(__file__).parent / "country_polygons.json" 15 | 16 | countries_map = { 17 | "Argentina": "Argentina", 18 | "Bolivia": "Bolivia", 19 | "Brazil": "Brasil", 20 | "Chile": "Chile", 21 | "Colombia": "Colombia", 22 | "Ecuador": "Ecuador", 23 | "Guyana": "Guyana", 24 | "France": "Guyana Francesa", 25 | "Paraguay": "Paraguay", 26 | "Peru": "Perú", 27 | "Suriname": "Surinam", 28 | "Uruguay": "Uruguay", 29 | "Venezuela": "Venezuela", 30 | } 31 | 32 | colors_list = ["red", "blue", "green", "purple", "orange"] 33 | 34 | 35 | def get_polygons() -> dict[str, list[Any]]: 36 | if POLYGONS_PATH.exists(): 37 | with open(POLYGONS_PATH, "r") as f: 38 | return json.load(f) 39 | 40 | # get country shapefiles with request 41 | url = "https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson" 42 | response = requests.get(url) 43 | response.raise_for_status() 44 | polygons_gen = ( 45 | (x["properties"]["ADMIN"], x["geometry"]["coordinates"]) 46 | for x in response.json()["features"] 47 | ) 48 | countries_filter = set( 49 | chain( 50 | countries_map, 51 | ("Falkland Islands", "South Georgia and South Sandwich Islands"), 52 | ) 53 | ) 54 | country_polygons = {x: y for x, y in polygons_gen if x in countries_filter} 55 | country_polygons["Argentina"] += country_polygons["Falkland Islands"] 56 | country_polygons["Argentina"] += country_polygons[ 57 | "South Georgia and South Sandwich Islands" 58 | ] 59 | with open(POLYGONS_PATH, "w") as f: 60 | json.dump(country_polygons, f) 61 | return country_polygons 62 | 63 | 64 | def get_lines( 65 | ax: Axes, m: Basemap, polygons: dict[str, list[Any]] 66 | ) -> dict[str, LineCollection]: 67 | country_lines = {} 68 | for country in countries_map: 69 | segments = [] 70 | if country not in polygons: 71 | print(f"Country {country} not found on geo data") 72 | continue 73 | for polygon in polygons[country]: 74 | for coords in polygon: 75 | lon, lat = zip(*coords) 76 | x, y = m(lon, lat) 77 | segments.append(list(zip(x, y))) 78 | lines = LineCollection(segments, antialiaseds=(1,)) 79 | lines.set_facecolor("white") 80 | lines.set_edgecolor("k") 81 | lines.set_linewidth(0.3) 82 | ax.add_collection(lines) 83 | country_lines[country] = lines 84 | return country_lines 85 | 86 | 87 | @dataclass 88 | class MapController: 89 | update: Callable[[dict[str, int], str, str], None] = lambda *_: None 90 | wait_for_close: Callable[[], None] = lambda: None 91 | __delay_slider: Slider | None = None 92 | 93 | 94 | def show_map(initial_delay: float | None = None) -> MapController: 95 | if initial_delay is None: 96 | return MapController() 97 | fig = plt.figure() 98 | m = Basemap( 99 | projection="merc", llcrnrlat=-60, urcrnrlat=20, llcrnrlon=-90, urcrnrlon=-30 100 | ) 101 | width = 5 102 | fig.set_size_inches(width, width * m.aspect) 103 | m.drawcountries(linewidth=0) 104 | ax = plt.subplot() 105 | polygons = get_polygons() 106 | lines = get_lines(ax, m, polygons) 107 | fig.tight_layout() 108 | plt.show(block=False) 109 | 110 | title = plt.title("") 111 | label = plt.text(0.01, 0.025, "", fontsize=12, transform=ax.transAxes) 112 | closed = False 113 | delay: float = initial_delay 114 | 115 | def update(colors: dict[str, int], title_txt: str, label_txt: str) -> None: 116 | if closed: 117 | return 118 | for country, line in lines.items(): 119 | mapped = countries_map[country] 120 | color = colors_list[colors[mapped]] if mapped in colors else "white" 121 | line.set_facecolor(color) 122 | title.set_text(title_txt) 123 | label.set_text(label_txt) 124 | plt.pause(delay) 125 | 126 | def wait_for_close() -> None: 127 | if closed: 128 | return 129 | plt.show() 130 | 131 | def on_close(_: Any) -> None: 132 | nonlocal closed 133 | closed = True 134 | plt.close() 135 | 136 | def on_slider_change(val: float) -> None: 137 | nonlocal delay 138 | delay = val 139 | 140 | ax_slider = fig.add_axes((0.3, 0.9, 0.4, 0.05)) 141 | delay_slider = Slider( 142 | ax=ax_slider, 143 | label="Step delay", 144 | valmin=0.001, 145 | valmax=5, 146 | valinit=delay, 147 | orientation="horizontal", 148 | valfmt="%1.2fs", 149 | ) 150 | 151 | delay_slider.on_changed(on_slider_change) 152 | fig.canvas.mpl_connect("close_event", on_close) 153 | return MapController(update, wait_for_close, delay_slider) 154 | -------------------------------------------------------------------------------- /backtracking/nreinas.py: -------------------------------------------------------------------------------- 1 | import networkx as nx 2 | import time 3 | 4 | DIMENSION = 16 5 | FUERZA_BRUTA = False 6 | 7 | 8 | def n_reinas(n): 9 | casillero = lambda i, j: str(i + 1) + chr(ord('a') + j) 10 | g = nx.Graph() 11 | for i in range(n): 12 | for j in range(n): 13 | g.add_node(casillero(i, j)) 14 | 15 | # Agrego adyacencia por fila 16 | for i in range(n): 17 | for j in range(n): 18 | for k in range(j + 1, n): 19 | g.add_edge(casillero(i, j), casillero(i, k)) 20 | # Agrego por columnas 21 | for j in range(n): 22 | for i in range(n): 23 | for k in range(i+1, n): 24 | g.add_edge(casillero(i, j), casillero(k, j)) 25 | 26 | # agrego por diagonales 27 | for i in range(n): 28 | for j in range(n): 29 | for k in range(i): 30 | if k < j: 31 | g.add_edge(casillero(i, j), casillero(i - k - 1, j - k - 1)) 32 | if k + j + 1 < n: 33 | g.add_edge(casillero(i, j), casillero(i - k - 1, j + k + 1)) 34 | return g 35 | 36 | 37 | def es_compatible(grafo, puestos, ultimo_puesto): 38 | for w in puestos: 39 | if ultimo_puesto == w: 40 | continue 41 | if grafo.has_edge(ultimo_puesto, w): 42 | return False 43 | return True 44 | 45 | 46 | def es_compatible_viendo_todos(grafo, puestos, ultimo_puesto): 47 | for v in puestos: 48 | for w in puestos: 49 | if v == w: 50 | continue 51 | if grafo.has_edge(v, w): 52 | return False 53 | return True 54 | 55 | 56 | def _ubicacion_FB(grafo, vertices, v_actual, puestos, n): 57 | if v_actual == len(grafo): 58 | return False 59 | if len(puestos) == n: 60 | return es_compatible_viendo_todos(grafo, puestos) 61 | # Mis opciones son poner acá, o no 62 | puestos.add(vertices[v_actual]) 63 | if _ubicacion_FB(grafo, vertices, v_actual + 1, puestos, n): 64 | return True 65 | puestos.remove(vertices[v_actual]) 66 | return _ubicacion_FB(grafo, vertices, v_actual + 1, puestos, n) 67 | 68 | 69 | def _ubicacion_BT(grafo, vertices, v_actual, puestos, n): 70 | if v_actual == len(grafo): 71 | return False 72 | if len(puestos) == n: 73 | return True 74 | 75 | # Mis opciones son poner acá, o no 76 | puestos.add(vertices[v_actual]) 77 | if es_compatible(grafo, puestos, vertices[v_actual]) and _ubicacion_BT(grafo, vertices, v_actual + 1, puestos, n): 78 | return True 79 | puestos.remove(vertices[v_actual]) 80 | return _ubicacion_BT(grafo, vertices, v_actual + 1, puestos, n) 81 | 82 | 83 | def ubicacion(grafo, n): 84 | puestos = set() 85 | vertices = list(grafo.nodes) 86 | if FUERZA_BRUTA: 87 | _ubicacion_FB(grafo, vertices, 0, puestos, n) 88 | else: 89 | _ubicacion_BT(grafo, vertices, 0, puestos, n) 90 | return puestos 91 | 92 | 93 | def _ubicacion_BT_todos(grafo, vertices, v_actual, puestos, n): 94 | if v_actual == len(grafo) and len(puestos) != n: 95 | return [] 96 | if len(puestos) == n: 97 | return [set(puestos)] if es_compatible_viendo_todos(grafo, puestos) else [] 98 | 99 | if not es_compatible_viendo_todos(grafo, puestos): 100 | return [] 101 | 102 | # Mis opciones son poner acá, o no 103 | puestos.add(vertices[v_actual]) 104 | soluciones_con = _ubicacion_BT_todos(grafo, vertices, v_actual + 1, puestos, n) 105 | puestos.remove(vertices[v_actual]) 106 | soluciones_sin = _ubicacion_BT_todos(grafo, vertices, v_actual + 1, puestos, n) 107 | return soluciones_con + soluciones_sin 108 | 109 | 110 | def ubicacion_todos(grafo, n): 111 | return _ubicacion_BT_todos(grafo, grafo.keys(), 0, set(), n) 112 | 113 | 114 | if __name__ == "__main__": 115 | inicio = time.time() 116 | print(ubicacion(n_reinas(DIMENSION), DIMENSION)) # https://drive.google.com/file/d/1_j6XaxVGBtJiEmtORGmZimuOvvwJG5ad/view?usp=sharing 117 | fin = time.time() 118 | print(int((fin - inicio) * 1000), "mili sec") 119 | -------------------------------------------------------------------------------- /backtracking/sudoku.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | NULL_ELEM = 0 4 | PIOLA = True 5 | ALTERNATIVAS = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G"} 6 | # ALTERNATIVAS = {"1", "2", "3", "4", "5", "6", "7", "8", "9"} 7 | TAM = len(ALTERNATIVAS) 8 | DIV_CUADRANTE = int(math.sqrt(TAM)) 9 | 10 | 11 | def resolver_sudoku_piola(matriz): 12 | faltantes = set() 13 | for i in range(TAM): 14 | for j in range(TAM): 15 | if matriz[i][j] == NULL_ELEM: 16 | faltantes.add((i, j)) 17 | if not _resolver_sudoku_bt(matriz, faltantes): 18 | return None 19 | else: 20 | return matriz 21 | 22 | 23 | def alternativas_validas(matriz, pos): 24 | alternativas = set(ALTERNATIVAS) 25 | posi, posj = pos 26 | # Me fijo por fila: 27 | for i in range(TAM): 28 | if i != posi and matriz[i][posj] != NULL_ELEM and matriz[i][posj] in alternativas: 29 | alternativas.remove(matriz[i][posj]) 30 | # Me fijo por columna: 31 | for j in range(TAM): 32 | if j != posj and matriz[posi][j] != NULL_ELEM and matriz[posi][j] in alternativas: 33 | alternativas.remove(matriz[posi][j]) 34 | # Me fijo por cuadrante: 35 | ini_i, fin_i, ini_j, fin_j = posiciones_cuadrante(pos) 36 | for i in range(ini_i, fin_i): 37 | for j in range(ini_j, fin_j): 38 | if (i != posi or j != posj) and matriz[i][j] != NULL_ELEM and matriz[i][j] in alternativas: 39 | alternativas.remove(matriz[i][j]) 40 | return alternativas 41 | 42 | 43 | def posiciones_cuadrante(pos): 44 | sectori, sectorj = pos[0] // DIV_CUADRANTE, pos[1] // DIV_CUADRANTE 45 | return sectori * DIV_CUADRANTE, (sectori + 1) * DIV_CUADRANTE, sectorj * DIV_CUADRANTE, (sectorj + 1) * DIV_CUADRANTE 46 | 47 | 48 | def _resolver_sudoku_bt(matriz, faltantes): 49 | if len(faltantes) == 0: 50 | return True 51 | # de las faltantes nos quedamos con la que menos alternativas le falten: 52 | alternativas_por_faltante = {pos: alternativas_validas(matriz, pos) for pos in faltantes} 53 | siguiente = min(faltantes, key=lambda pos: len(alternativas_por_faltante[pos])) 54 | i, j = siguiente 55 | alternativas_siguiente = alternativas_por_faltante[siguiente] 56 | if len(alternativas_siguiente) == 0: 57 | return False 58 | faltantes.remove(siguiente) 59 | for alternativa in alternativas_siguiente: 60 | matriz[i][j] = alternativa 61 | if _resolver_sudoku_bt(matriz, faltantes): 62 | return True 63 | faltantes.add(siguiente) 64 | matriz[i][j] = NULL_ELEM 65 | return False 66 | 67 | 68 | def resolver_sudoku_naive(matriz): 69 | if _resolver_sudoku_naive_bt(matriz, (0, 0)): 70 | return matriz 71 | else: 72 | return None 73 | 74 | 75 | def _resolver_sudoku_naive_bt(matriz, actual): 76 | i, j = actual 77 | if i == TAM: 78 | return True 79 | siguiente = siguiente_de(actual) 80 | if matriz[i][j] != NULL_ELEM: 81 | return _resolver_sudoku_naive_bt(matriz, siguiente) 82 | for alternativa in alternativas_validas(matriz, actual): 83 | matriz[i][j] = alternativa 84 | if _resolver_sudoku_naive_bt(matriz, siguiente): 85 | return True 86 | matriz[i][j] = NULL_ELEM 87 | return False 88 | 89 | 90 | def siguiente_de(actual): 91 | i, j = actual 92 | siguientei = i if j < TAM - 1 else i + 1 93 | siguientej = j + 1 if j < TAM - 1 else 0 94 | return siguientei, siguientej 95 | 96 | 97 | def main(path): 98 | matriz = [] 99 | with open(path) as f: 100 | for l in f: 101 | matriz.append(list(map(lambda v: NULL_ELEM if len(v.strip()) == 0 else v, l.strip().split("|")))) 102 | if PIOLA: 103 | resuelto = resolver_sudoku_piola(matriz) 104 | else: 105 | resuelto = resolver_sudoku_naive(matriz) 106 | if resuelto is None: 107 | print("No hay solucion") 108 | else: 109 | for fila in resuelto: 110 | print(fila) 111 | 112 | 113 | if __name__ == "__main__": 114 | main("sudoku_16_experto.txt") 115 | -------------------------------------------------------------------------------- /backtracking/sudoku_16_experto.txt: -------------------------------------------------------------------------------- 1 | | | |A| |B|E| | |2|D| |F| | | 2 | 4| |7| | |G|D| | |3|F| | |6| |8 3 | |D| | |F| |A| | |7| |B| | |C| 4 | 8| |B| | | | |C|1| | | | |3| |A 5 | G| | |C|6|D| | | | |8|3|7| | |E 6 | B| | |7| | |5|3|D|6| | |9| | |C 7 | | | |D|2|E|F| | |1|C|7|4| | | 8 | 3|1| |5|C| |9|7|E|B| |A|G| |6|D 9 | 2|E| | | | | |4|F| | | | | |7|B 10 | 1|C|G|8| | | | | | | | |6|2|D|9 11 | | | |4| | |G| | |8| | |E| | | 12 | 7| | | | |2| | | | |9| | | | |4 13 | | | | |A| |4|5|3|C| |6| | | | 14 | F| |5|2|G|6| |E|8| |B|4|C|9| |1 15 | | | |B|7| |2| | |A| |E|3| | | 16 | E| | | | | |1|B|2|9| | | | | |7 17 | -------------------------------------------------------------------------------- /backtracking/sudoku_16_facil.txt: -------------------------------------------------------------------------------- 1 | F|G|8|B|2| | | | |D|E|6|A| |4| 2 | A|3| |4| | | |G| | | |7| | |1| 3 | 9| |D| |3|7| | | | |4| |8|E|5| 4 | 2|7| |6|A| |C| |9|5| | |F| |3|D 5 | 3| |A|G|C|B| | |5| |D|E|9|6| |F 6 | | |1| |4|3|G| |F|9| |B| | | |8 7 | | | |5| |8|2|E| |4|6| | |G|A|B 8 | |B| | | | |9|6| | | | | |1|E| 9 | | | |E|B|A| | | | | | | | | | 10 | 5| | |8| |6|7| | | | | | | | | 11 | G| |6| |9| |F| | | | | | | | | 12 | 4|C| | |G|1| | | | | | | | | | 13 | B| |4|9|7| | | |1|G|2|5| | |6| 14 | | |F| |1| |B|9|6| | | | | |G|3 15 | E|6|2|3| | |A|F|7| |9|4|1|C|D|5 16 | | | |A|6|2|E| |D|3| | | |8|B| 17 | -------------------------------------------------------------------------------- /backtracking/sudoku_dificil.txt: -------------------------------------------------------------------------------- 1 | 8|4|5| | | | | |1 2 | | | | | |3| | |6 3 | 6| | | | |4| |5| 4 | |5|1| |7| | | | 5 | |7| | |4| | |9| 6 | | |6| | | |8|4| 7 | | |4|8| | |6|1| 8 | |6| |1| | |7|8| 9 | | |3| | | | | | 10 | -------------------------------------------------------------------------------- /backtracking/sudoku_experto.txt: -------------------------------------------------------------------------------- 1 | 5| | | | |2| | | 2 | | | | | | |3|9| 3 | 2| |8|4| |6| | | 4 | |9| |5|3| | | | 5 | |6|5| | | | | |8 6 | |8| | | | |9| |3 7 | 6| |4| | | |7| | 8 | | | | | | |2|1| 9 | | | |2|7|4| | | 10 | -------------------------------------------------------------------------------- /backtracking/sudoku_facil.txt: -------------------------------------------------------------------------------- 1 | 3| | | | | | |7| 2 | 4|9| |3| | |2|5| 3 | 7| | | |8|9|4|1| 4 | | | |2| |7|8| | 5 | 8|3|7|6| | | | | 6 | | | | | |1|6|3| 7 | |2| |7|6|8| | |4 8 | 5| |6| | | |7| |1 9 | | |8| |1| | | |2 10 | -------------------------------------------------------------------------------- /dyc/puntos_cercanos.py: -------------------------------------------------------------------------------- 1 | import random 2 | import math 3 | 4 | def generar_puntos(n): 5 | generadosx = set() 6 | generadosy = set() 7 | resultado = [] 8 | for i in range(n): 9 | while True: 10 | x, y = random.randint(0, 100), random.randint(0, 100) 11 | if x not in generadosx and y not in generadosy: 12 | resultado.append((x, y)) 13 | generadosx.add(x) 14 | generadosy.add(y) 15 | break 16 | return resultado 17 | 18 | 19 | def distancia(x, y): 20 | return math.sqrt((x[0] - y[0])**2 + (x[1] - y[1])**2) 21 | 22 | def comparacion_3(px): 23 | if len(px) == 2: 24 | return px[0], px[1] 25 | d01 = distancia(px[0], px[1]) 26 | d02 = distancia(px[0], px[2]) 27 | d12 = distancia(px[1], px[2]) 28 | if d01 <= d02 and d01 <= d12: 29 | return px[0], px[1] 30 | elif d02 <= d01 and d02 <= d12: 31 | return px[0], px[2] 32 | else: 33 | return px[1], px[2] 34 | 35 | 36 | def construir_qyry(py, x_quiebre): 37 | qy = [] 38 | ry = [] 39 | for punto in py: 40 | if punto[0] < x_quiebre: 41 | qy.append(punto) 42 | else: 43 | ry.append(punto) 44 | return qy, ry 45 | 46 | 47 | def construir_sy(py, x_quiebre, d): 48 | sy = [] 49 | for punto in py: 50 | if abs(punto[0] - x_quiebre) < d: 51 | sy.append(punto) 52 | return sy 53 | 54 | def puntos_mas_cercanos_dyc(px, py): 55 | if len(px) <= 3: 56 | return comparacion_3(px) 57 | mitad = len(px) // 2 58 | qx = px[:mitad] 59 | rx = px[mitad:] 60 | qy, ry = construir_qyry(py, px[mitad][0]) 61 | q0, q1 = puntos_mas_cercanos_dyc(qx, qy) 62 | r0, r1 = puntos_mas_cercanos_dyc(rx, ry) 63 | if distancia(q0, q1) < distancia(r0, r1): 64 | d = distancia(q0, q1) 65 | min0 = q0 66 | min1 = q1 67 | else: 68 | d = distancia(r0, r1) 69 | min0 = r0 70 | min1 = r1 71 | sy = construir_sy(py, px[mitad][0], d) 72 | for i in range(len(sy)): 73 | for j in range(i+1, min(i+16, len(sy))): 74 | if distancia(sy[i], sy[j]) < d: 75 | d = distancia(sy[i], sy[j]) 76 | min0 = sy[i] 77 | min1 = sy[j] 78 | return min0, min1 79 | 80 | 81 | def mas_cercanos(puntos): 82 | px = sorted(puntos, key=lambda p: p[0]) 83 | py = sorted(puntos, key=lambda p: p[1]) 84 | p0, p1 = puntos_mas_cercanos_dyc(px, py) 85 | return p0, p1 86 | 87 | 88 | puntos = generar_puntos(32) 89 | print(puntos) 90 | p0, p1 = mas_cercanos(puntos) 91 | print(p0, p1, distancia(p0, p1)) 92 | -------------------------------------------------------------------------------- /flujo/carlos.py: -------------------------------------------------------------------------------- 1 | 2 | def crear_red(calles, casa, escuela): 3 | red = Grafo(es_dirigido=True) 4 | for esquina in calles: 5 | red.agregar_vertice(esquina) 6 | visitados = set() 7 | ficticios = {} 8 | for esquina in calles: 9 | for conectada in calles.adyacentes(esquina): 10 | if conectada in visitados: 11 | continue 12 | elif esquina == casa or conectada == escuela: 13 | red.arista(esquina, conectada, peso=1) 14 | elif esquina == escuela or conectada == casa: 15 | red.arista(conectada, esquina, peso=1) 16 | else: 17 | red.agregar_vertice(esquina + conectada) 18 | red.arista(esquina, conectada, peso=1) 19 | red.arista(conectada, esquina + conectada, peso=1) 20 | red.arista(esquina + conectada, esquina, peso=1) 21 | ficticios[esquina + conectada] = (esquina, conectada) 22 | visitados.add(esquina) 23 | return red, ficticios 24 | 25 | def carlos(calles, cant_hijos, casa, escuela): 26 | red, ficticios = crear_red(calles, casa, escuela) 27 | flujo = flujoMaximo(red) 28 | cantCaminos = 0 29 | for conectada in red.adyacentes(casa): 30 | if flujo[casa][conectada] > 0: 31 | cantCaminos += 1 32 | if cantCaminos < cant_hijos: 33 | return None 34 | 35 | # corregimos los flujos para que se cancelen si van ida y vuelta: 36 | for ficticio in ficticios: 37 | v, w = ficticios[ficticio] 38 | if flujo[v][w] == 1 and flujo[ficticio][v] == 1: 39 | flujo[v][w] = 0 40 | flujo[w][ficticio] = 0 41 | flujo[ficticio][v] = 0 42 | 43 | caminos = [] 44 | for i in range(cant_hijos): 45 | camino = [] 46 | camino.append(casa) 47 | v = casa 48 | while v != escuela: 49 | for w in red.adyacentes(v): 50 | if flujo[v][w] == 1: 51 | flujo[v][w] = 0 52 | v = w 53 | if v not in ficticios: 54 | camino.append(v) 55 | break 56 | caminos.append(camino) 57 | return caminos -------------------------------------------------------------------------------- /mochila/greedy.py: -------------------------------------------------------------------------------- 1 | IDX_VALOR = 0 2 | IDX_PESO = 1 3 | 4 | 5 | def ordenar_por_mayor_valor(elementos): 6 | return sorted(elementos, key=lambda e: e[IDX_VALOR], reverse=True) 7 | 8 | 9 | def ordenar_por_menor_peso(elementos): 10 | return sorted(elementos, key=lambda e: e[IDX_PESO]) 11 | 12 | 13 | def ordenar_por_mayor_relacion_valor_peso(elementos): 14 | return sorted(elementos, key=lambda e: e[IDX_VALOR]/e[IDX_PESO], reverse=True) 15 | 16 | 17 | def mochila_greedy(elementos, W, ordenamiento): 18 | elementos_ord = ordenamiento(elementos) 19 | capacidad_usada = 0 20 | valor_obtenido = 0 21 | for elem in elementos_ord: 22 | if elem[IDX_PESO] + capacidad_usada > W: 23 | continue 24 | capacidad_usada += elem[IDX_PESO] 25 | valor_obtenido += elem[IDX_VALOR] 26 | return valor_obtenido 27 | -------------------------------------------------------------------------------- /mochila/mochila.py: -------------------------------------------------------------------------------- 1 | import random 2 | import sys 3 | import greedy 4 | import pd 5 | 6 | 7 | def main(n, W): 8 | elementos = [(random.randint(1, 100), random.randint(1, W)) for _ in range(n)] 9 | print("Solucion por Algoritmo Greedy maximizando valor:", 10 | greedy.mochila_greedy(elementos, W, greedy.ordenar_por_mayor_valor)) 11 | print("Solucion por Algoritmo Greedy minimizando peso:", 12 | greedy.mochila_greedy(elementos, W, greedy.ordenar_por_menor_peso)) 13 | print("Solucion por Algoritmo Greedy maximizando v/w:", 14 | greedy.mochila_greedy(elementos, W, greedy.ordenar_por_mayor_relacion_valor_peso)) 15 | print("Solucion exacta:", pd.mochila_pd(elementos, W)) 16 | 17 | 18 | if __name__ == "__main__": 19 | if len(sys.argv) > 2: 20 | n = int(sys.argv[1]) 21 | w = int(sys.argv[2]) 22 | else: 23 | n = 100 24 | w = 100 25 | main(n, w) 26 | -------------------------------------------------------------------------------- /mochila/pd.py: -------------------------------------------------------------------------------- 1 | IDX_VALOR = 0 2 | IDX_PESO = 1 3 | 4 | 5 | def mochila_pd(elementos, W): 6 | matriz = [[0 for j in range(W + 1)] for i in range(len(elementos) + 1)] 7 | for i in range(1, len(elementos) + 1): 8 | elem = elementos[i-1] 9 | for j in range(1, W + 1): 10 | if elem[IDX_PESO] > j: 11 | matriz[i][j] = matriz[i-1][j] 12 | else: 13 | matriz[i][j] = max(matriz[i-1][j], matriz[i-1][j - elem[IDX_PESO]] + elem[IDX_VALOR]) 14 | return matriz[len(elementos)][W] 15 | -------------------------------------------------------------------------------- /pl/ejemplo1.py: -------------------------------------------------------------------------------- 1 | import pulp 2 | 3 | 4 | def ejemplo(): 5 | x = pulp.LpVariable("x") 6 | y = pulp.LpVariable("y") 7 | problem = pulp.LpProblem("products", pulp.LpMaximize) 8 | problem += 3 * x >= y 9 | problem += x + 2 * y <= 14 10 | problem += x - y <= 2 11 | problem += 5 * x + 3 * y 12 | problem.solve() 13 | return pulp.value(x), pulp.value(y) 14 | 15 | 16 | if __name__ == "__main__": 17 | x, y = ejemplo() 18 | print(x, y) 19 | -------------------------------------------------------------------------------- /pl/ejemplo2.py: -------------------------------------------------------------------------------- 1 | import pulp 2 | 3 | 4 | def ejemplo(): 5 | problem = pulp.LpProblem("products", pulp.LpMinimize) 6 | vp = pulp.LpVariable("vp") 7 | ss = pulp.LpVariable("ss") 8 | problem += vp <= 40 9 | problem += ss <= 30 10 | problem += 5 * ss >= 50 # mostrar cambiando a 51 11 | problem += vp + 2 * ss >= 50 # mostrar cambiando a 51 12 | problem += 1 * vp + 6 * ss 13 | problem.solve() 14 | return pulp.value(vp), pulp.value(ss) 15 | 16 | 17 | def ejemplo_cajas_enteras(): 18 | vp = pulp.LpVariable("vp") 19 | ss = pulp.LpVariable("ss", cat="Integer") 20 | problem = pulp.LpProblem("products", pulp.LpMinimize) 21 | problem += vp <= 40 22 | problem += ss <= 30 23 | problem += 5 * ss >= 51 24 | problem += vp + 2 * ss >= 51 25 | problem += 1 * vp + 6 * ss 26 | problem.solve() 27 | return pulp.value(vp), pulp.value(ss) 28 | 29 | 30 | if __name__ == "__main__": 31 | x, y = ejemplo() 32 | print("Cantidad de kgr a Valle Patagua:", x, " - Cantidad de cajas de Salud Sustentable:", y) 33 | print("Costo:", x + 6 * y) 34 | -------------------------------------------------------------------------------- /pl/mochila.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import pulp 4 | from pulp import LpAffineExpression as Sumatoria 5 | 6 | 7 | def cargar_mochila(ruta): 8 | w = [] 9 | v = [] 10 | with open(ruta) as f: 11 | W = int(f.readline().strip()) 12 | for l in f: 13 | vi, wi = l.strip().split(",") 14 | w.append(int(wi)) 15 | v.append(int(vi)) 16 | return v, w, W 17 | 18 | 19 | def mochila_variable(v: List[int], w: List[int], W: int): 20 | y = [] 21 | for i in range(len(v)): 22 | y.append(pulp.LpVariable("y" + str(i), cat="Binary")) 23 | 24 | problem = pulp.LpProblem("products", pulp.LpMaximize) 25 | problem += Sumatoria([(y[i], w[i]) for i in range(len(y))]) <= W 26 | problem += Sumatoria([(y[i], v[i]) for i in range(len(y))]) 27 | 28 | problem.solve() 29 | return list(map(lambda yi: pulp.value(yi), y)) 30 | 31 | 32 | if __name__ == "__main__": 33 | valores, pesos, W = cargar_mochila("mochila.txt") 34 | y = mochila_variable(valores, pesos, W) 35 | print(y) 36 | print("Peso usado:", sum([pesos[i] * y[i] for i in range(len(y))])) 37 | print("Valor obtenido:", sum([valores[i] * y[i] for i in range(len(y))])) 38 | -------------------------------------------------------------------------------- /pl/mochila.txt: -------------------------------------------------------------------------------- 1 | 19 2 | 10,6 3 | 1,1 4 | 8,3 5 | 100,100 6 | 6,4 7 | 11,2 8 | 7,8 9 | 2,7 10 | 11,9 11 | -------------------------------------------------------------------------------- /pl/scaloneta.py: -------------------------------------------------------------------------------- 1 | import pulp 2 | TIEMPO_SCALONI = 0 3 | TIEMPO_AYUD = 1 4 | 5 | 6 | def cargar(path): 7 | resul = [] 8 | with open(path) as f: 9 | f.readline() 10 | for l in f: 11 | si, ai = l.strip().split(",") 12 | resul.append((int(si), int(ai))) 13 | return resul 14 | 15 | 16 | def filter_not_none(col): 17 | return list(filter(lambda i: i is not None, col)) 18 | 19 | 20 | def sort_by_p_i(equipos, p): 21 | nuevo = equipos[:] 22 | for i in range(len(equipos)): 23 | nuevo[int(p[i].value())] = equipos[i] 24 | return nuevo 25 | 26 | 27 | def orden_pl(equipos): 28 | problem = pulp.LpProblem("products", pulp.LpMinimize) 29 | 30 | y = [] 31 | for i in range(len(equipos)): 32 | y.append([]) 33 | for j in range(len(equipos)): 34 | if i == j: 35 | y[i].append(None) # para que no moleste al indexar 36 | else: 37 | y[i].append(pulp.LpVariable("y_" + str(i) + "," + str(j), cat="Binary")) 38 | 39 | for i in range(len(equipos)): 40 | problem += pulp.lpSum(filter_not_none(y[i])) <= 1 41 | 42 | for j in range(len(equipos)): 43 | problem += pulp.lpSum(filter_not_none([y[i][j] for i in range(len(y))])) <= 1 44 | 45 | p = [] 46 | for i in range(len(equipos)): 47 | p_i = pulp.LpAffineExpression(filter_not_none([(y[i][j], j) if y[i][j] is not None else None for j in range(len(y))])) 48 | p.append(p_i) 49 | 50 | z = [] 51 | M = len(equipos) + 1 52 | for i in range(len(equipos)): 53 | z.append([]) 54 | for k in range(len(equipos)): 55 | if i == k: 56 | z[i].append(None) # para que no molesten los rangos 57 | continue 58 | z[i].append(pulp.LpVariable("z_" + str(i) + "," + str(k), cat="Binary")) 59 | problem += p[i] >= p[k] + 1 - M * (1 - z[i][k]) 60 | problem += p[k] >= p[i] + 1 - M * z[i][k] 61 | 62 | t = [] 63 | for i in range(len(equipos)): 64 | t_i = pulp.LpAffineExpression(filter_not_none([(z[i][k], equipos[k][TIEMPO_SCALONI]) if i != k else None for k in range(len(y))])) + \ 65 | equipos[i][TIEMPO_SCALONI] + equipos[i][TIEMPO_AYUD] 66 | t.append(t_i) 67 | 68 | resul = pulp.LpVariable("resultado") 69 | for t_i in t: 70 | problem += resul >= t_i 71 | 72 | problem += resul 73 | problem.solve() 74 | 75 | return sort_by_p_i(equipos, p) 76 | 77 | 78 | def tiempo_consumido(elems): 79 | tiempos_finales = [] 80 | t_scaloni = 0 81 | for si, ai in elems: 82 | t_scaloni += si 83 | tiempos_finales.append(t_scaloni + ai) 84 | return max(tiempos_finales) 85 | 86 | 87 | if __name__ == "__main__": 88 | # Optimo para 3: 10; opt para 10: 29; opt para 100: 5223 89 | elems = cargar("scaloneta_100.txt") 90 | print(tiempo_consumido(elems)) 91 | print(tiempo_consumido(orden_pl(elems))) 92 | -------------------------------------------------------------------------------- /pl/scaloneta_10.txt: -------------------------------------------------------------------------------- 1 | S_i,A_i 2 | 1,3 3 | 5,1 4 | 4,8 5 | 4,3 6 | 1,5 7 | 2,9 8 | 2,2 9 | 2,4 10 | 1,6 11 | 6,5 12 | -------------------------------------------------------------------------------- /pl/scaloneta_100.txt: -------------------------------------------------------------------------------- 1 | S_i,A_i 2 | 44,97 3 | 62,79 4 | 43,17 5 | 72,13 6 | 37,15 7 | 35,80 8 | 80,40 9 | 43,15 10 | 36,37 11 | 70,54 12 | 16,81 13 | 31,92 14 | 42,18 15 | 86,53 16 | 90,61 17 | 45,10 18 | 20,20 19 | 82,83 20 | 33,12 21 | 32,70 22 | 83,47 23 | 61,59 24 | 16,45 25 | 85,61 26 | 15,31 27 | 54,79 28 | 78,66 29 | 91,55 30 | 96,58 31 | 25,61 32 | 44,76 33 | 56,52 34 | 70,18 35 | 17,42 36 | 42,90 37 | 72,45 38 | 41,14 39 | 91,38 40 | 29,53 41 | 86,23 42 | 16,97 43 | 58,65 44 | 82,34 45 | 75,49 46 | 67,47 47 | 57,83 48 | 57,95 49 | 92,46 50 | 63,90 51 | 86,36 52 | 64,73 53 | 17,21 54 | 62,72 55 | 41,83 56 | 34,89 57 | 35,72 58 | 53,51 59 | 70,16 60 | 41,60 61 | 58,39 62 | 72,45 63 | 43,78 64 | 34,16 65 | 13,61 66 | 69,34 67 | 78,60 68 | 19,79 69 | 10,85 70 | 58,32 71 | 97,29 72 | 51,40 73 | 21,69 74 | 76,14 75 | 23,45 76 | 99,91 77 | 86,70 78 | 16,37 79 | 62,35 80 | 38,43 81 | 100,27 82 | 99,86 83 | 13,44 84 | 21,89 85 | 66,100 86 | 34,93 87 | 26,47 88 | 37,72 89 | 65,22 90 | 22,68 91 | 16,19 92 | 47,39 93 | 41,57 94 | 23,70 95 | 69,99 96 | 44,82 97 | 51,60 98 | 25,94 99 | 50,61 100 | 83,22 101 | 37,47 102 | -------------------------------------------------------------------------------- /pl/scaloneta_3.txt: -------------------------------------------------------------------------------- 1 | S_i,A_i 2 | 3,3 3 | 5,1 4 | 1,8 5 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algoritmos-rw/tda_ejemplos/7beb1a31eb7130f4f57782c4afed8341e44f1391/utils/__init__.py -------------------------------------------------------------------------------- /utils/optional.py: -------------------------------------------------------------------------------- 1 | 2 | def empty(): 3 | return Optional(None) 4 | 5 | 6 | def of(value): 7 | return Optional(value) 8 | 9 | 10 | class Optional: 11 | """Representa un valor que bien puede estar presente, o no estar, y que se puedan 12 | realizar operaciones sobre este sin tener que estar llenando el código de ifs.""" 13 | 14 | def __init__(self, value): 15 | """Crea el Optional con el valor asociado. No debería ser usado. Usar las funciones of y empty""" 16 | self.value = value 17 | 18 | def is_present(self): 19 | """Determina si el valor está presente, o no""" 20 | return self.value is not None 21 | 22 | def is_empty(self): 23 | """Determina si el Optional está vacío (empty)""" 24 | return self.value is None 25 | 26 | def get(self): 27 | """Obtiene el valor del Optional, si está presente. Sino, lanza una ValueError.""" 28 | if self.is_empty(): 29 | raise ValueError("Optional is empty") 30 | return self.value 31 | 32 | def map(self, mapfn): 33 | """Crea un nuevo Optional con el valor de haber aplicar la mapfn al valor de este optional. 34 | Si el Optional está vacío, simplemente devuelve un Optional Vacío.""" 35 | if self.is_empty(): 36 | return empty() 37 | return of(mapfn(self.value)) 38 | 39 | def flatmap(self, mapfn): 40 | """Similar a map, pero pensado para el caso que la mapfn devuelve un Optional. Si se usara map en ese caso, 41 | se tendría como resultado un Optional de un Optional, lo cual puede ser muy molesto para trabajar. En este 42 | caso, si el Optional tiene valor, se devuelve directamente el resultado de aplicar la mapfn, teniendo 43 | directamente ese Optional.""" 44 | if self.is_empty(): 45 | return empty() 46 | return mapfn(self.value) 47 | 48 | def or_else(self, default_value): 49 | """Devuelve el valor de este optional si es que hay, y sino devuelve el valor default.""" 50 | return self.value if self.is_present() else default_value 51 | 52 | def if_present(self, applyfn): 53 | """Si el valor se encuentra presente, aplica la función sobre el valor. Si el Optional está vacio, no se 54 | hace nada""" 55 | if self.is_present(): 56 | applyfn(self.value) 57 | 58 | def __repr__(self): 59 | if self.is_empty(): 60 | return "Empty" 61 | else: 62 | return "Optional[" + repr(self.value) + "]" 63 | --------------------------------------------------------------------------------