├── .gitignore ├── README.md ├── package-lock.json ├── package.json └── src ├── 2021 ├── README.md ├── exercices │ ├── day01.js │ ├── day02.js │ ├── day03.js │ ├── day04.js │ ├── day05.js │ ├── day06.js │ ├── day07.js │ ├── day08.js │ ├── day09.js │ ├── day10.js │ ├── day11.js │ ├── day12.js │ ├── day13.js │ ├── day14.js │ ├── day15.js │ ├── day16.js │ ├── day17.js │ ├── day18.js │ ├── day19.js │ ├── day20.js │ ├── day21.js │ ├── day22.js │ ├── day23.js │ ├── day24.js │ └── day25.js └── tests │ ├── day01.test.js │ ├── day02.test.js │ ├── day03.test.js │ ├── day04.test.js │ ├── day05.test.js │ ├── day06.test.js │ ├── day07.test.js │ ├── day08.test.js │ ├── day09.test.js │ ├── day10.test.js │ ├── day11.test.js │ ├── day12.test.js │ ├── day13.test.js │ ├── day14.test.js │ ├── day15.test.js │ ├── day16.test.js │ ├── day17.test.js │ ├── day18.test.js │ ├── day19.test.js │ ├── day20.test.js │ ├── day21.test.js │ ├── day22.test.js │ ├── day23.test.js │ ├── day24.test.js │ └── day25.test.js └── 2022 └── exercices ├── day01.js ├── day02.js ├── day03.js ├── day04.js ├── day05.js ├── day06.js ├── day07.js ├── day08.js ├── day09.js ├── day10.js ├── day11.js ├── day12.js ├── day13.js ├── day14.js ├── day15.js ├── day16.js ├── day17.js ├── day18.js ├── day19.js └── day20.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | .idea 107 | .vscode 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🎄My 2022 adventjs commented solutions🎄 2 | 3 | adventjs.dev is a platform developed by where you can practice your JavaScript skills via challenges as an advent calendar. This repository contains my proposed solutions for all the challenges. 4 | 5 | As I consider that if you are reading this you may be in the search of some help for succeeding on a challenge, all my solutions are described so you can understand what I implemented. There are some challenges with more than one solution (for example, an iterative and a functional one). 6 | 7 | The solutions descriptions contain a title for identifying it, the calculated time complexity, an explanation step by step and, some author comments about it. 8 | 9 | If you think you got into a better solution than the one I propose, please let me know, so I can include your solution (all the credits will be yours, of course). 10 | 11 | ## Index of challenges 12 | 13 | 14 | | Day | Code + Description | Test | 15 | | :---: | :-----------------------------------------------------------------------------------------------------------------------: | :---: | 16 | | 1 | [¡Automatizando envolver regalos de navidad!](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day01.js) | ✅ | 17 | | 2 | [Nadie quiere hacer horas extra](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day02.js) | ✅ | 18 | | 3 | [How many packs of gifts can Santa carry?](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day03.js) | ✅ | 19 | | 4 | [Box inside a box and another...](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day04.js) | ✅ | 20 | | 5 | [Optimizing Santa's trips](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day05.js) | ✅ | 21 | | 6 | [Creating xmas decorations](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day06.js) | ✅ | 22 | | 6 | [Doing gifts inventory](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day07.js) | ✅ | 23 | | 8 | [¡Necesitamos un mecánico!](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day08.js) | ✅ | 24 | | 9 | [Las locas luces de Navidad](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day09.js) | ✅ | 25 | | 10 | [El salto del trineo de Papá Noel](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day10.js) | ✅ | 26 | | 11 | [Papá Noel es Scrum Master](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day11.js) | ✅ | 27 | | 12 | [Trineos eléctricos, ¡guay!](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day12.js) | ✅ | 28 | | 13 | [Backup de los archivos de Papá Noel](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day13.js) | ✅ | 29 | | 14 | [El mejor camino](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day14.js) | ✅ | 30 | | 15 | [Decorando el árbol de Navidad](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day15.js) | ✅ | 31 | | 16 | [Arreglando las cartas de Papá Noel](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day16.js) | ✅ | 32 | | 17 | [Llevando los regalos en sacos](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day17.js) | ✅ | 33 | | 18 | [¡Nos quedamos sin tinta!](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day18.js) | ✅ | 34 | | 19 | [Ordenando los regalos](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day19.js) | ✅ | 35 | | 20 | [Más viajes retadores](https://github.com/arialdev/adventjs/blob/main/src/2022/exercices/day20.js) | ✅ | 36 | | 21 | - | - | 37 | | 22 | - | - | 38 | | 23 | - | - | 39 | | 24 | - | - | 40 | | 25 | - | - | 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adventjs", 3 | "version": "2.0.0", 4 | "description": "Personal repository for adventjs.dev", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/arialdev/adventjs.git" 12 | }, 13 | "keywords": [ 14 | "adventjs", 15 | "advent", 16 | "JavaScript", 17 | "Gamification", 18 | "Solutions" 19 | ], 20 | "author": "arialdev", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/arialdev/adventjs/issues" 24 | }, 25 | "homepage": "https://github.com/arialdev/adventjs#readme", 26 | "devDependencies": { 27 | "jest": "^27.4.5" 28 | }, 29 | "type": "module", 30 | "private": true 31 | } -------------------------------------------------------------------------------- /src/2021/README.md: -------------------------------------------------------------------------------- 1 | # 🎄My 2021 adventjs commented solutions🎄 2 | 3 | adventjs.dev is a platform developed by where you can practice your JavaScript skills via challenges as an advent calendar. This repository contains my proposed solutions for all the challenges. 4 | 5 | As I consider that if you are reading this you may be in the search of some help for succeeding on a challenge, all my solutions are described so you can understand what I implemented. There are some challenges with more than one solution (for example, an iterative and a functional one). 6 | 7 | The solutions descriptions contain a title for identifying it, the calculated time complexity, an explanation step by step and, some author comments about it. 8 | 9 | If you think you got into a better solution than the one I propose, please let me know, so I can include your solution (all the credits will be yours, of course). 10 | 11 | ## Index of challenges 12 | 13 | 14 | | Day | Code + Description | Test | 15 | | :---: | :---------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------: | 16 | | 1 | [Contando ovejas para dormir](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day01.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day01.test.js) | 17 | | 2 | [¡Ayuda al elfo a listar los regalos!](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day02.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day02.test.js) | 18 | | 3 | [El Grinch quiere fastidiar la Navidad](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day03.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day03.test.js) | 19 | | 4 | [¡Es hora de poner la navidad en casa!](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day04.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day04.test.js) | 20 | | 5 | [Contando los días para los regalos](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day05.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day05.test.js) | 21 | | 6 | [Rematando los exámenes finales](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day06.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day06.test.js) | 22 | | 7 | [Buscando en el almacén...](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day07.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day07.test.js) | 23 | | 8 | [La locura de las criptomonedas](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day08.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day08.test.js) | 24 | | 9 | [Agrupando cosas automáticamente](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day09.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day09.test.js) | 25 | | 10 | [La máquina de cambio](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day10.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day10.test.js) | 26 | | 11 | [¿Vale la pena la tarjeta fidelidad del cine?](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day11.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day11.test.js) | 27 | | 12 | [La ruta perfecta para dejar los regalos](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day12.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day12.test.js) | 28 | | 13 | [Envuelve regalos con asteriscos](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day13.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day13.test.js) | 29 | | 14 | [En busca del reno perdido](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day14.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day14.test.js) | 30 | | 15 | [El salto perfecto](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day15.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day15.test.js) | 31 | | 16 | [Descifrando los números...](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day16.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day16.test.js) | 32 | | 17 | [La locura de enviar paquetes en esta época](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day17.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day17.test.js) | 33 | | 18 | [El sistema operativo de Santa Claus](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day18.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day18.test.js) | 34 | | 19 | [¿Qué deberíamos aprender en Platzi?](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day19.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day19.test.js) | 35 | | 20 | [¿Una carta de pangramas? ¡QUÉ!](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day20.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day20.test.js) | 36 | | 21 | [La ruta con los regalos](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day21.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day21.test.js) | 37 | | 22 | [¿Cuantos adornos necesita el árbol?](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day22.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day22.test.js) | 38 | | 23 | [¿Puedes reconfigurar las fábricas para no parar de crear regalos?](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day23.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day23.test.js) | 39 | | 24 | [Comparando árboles de Navidad](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day24.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day24.test.js) | 40 | | 25 | [El último juego y hasta el año que viene 👋](https://github.com/arialdev/adventjs/blob/main/src/2021/exercices/day24.js) | [✅](https://github.com/arialdev/adventjs/blob/main/src/2021/tests/day25.test.js) | 41 | -------------------------------------------------------------------------------- /src/2021/exercices/day01.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Considera una lista/array de ovejas. Cada oveja tiene un nombre y un color. Haz una función que devuelva una lista con 4 | todas las ovejas que sean de color rojo y que además su nombre contenga tanto las letras n Y a, sin importar el orden, 5 | las mayúsculas o espacios. 6 | 7 | Por ejemplo, si tenemos las ovejas: 8 | 9 | const ovejas = [ 10 | { name: 'Noa', color: 'azul' }, 11 | { name: 'Euge', color: 'rojo' }, 12 | { name: 'Navidad', color: 'rojo' }, 13 | { name: 'Ki Na Ma', color: 'rojo'}, 14 | { name: 'AAAAAaaaaa', color: 'rojo' }, 15 | { name: 'Nnnnnnnn', color: 'rojo'} 16 | ] 17 | Al ejecutar el método debería devolver lo siguiente: 18 | 19 | const ovejasFiltradas = contarOvejas(ovejas) 20 | 21 | console.log(ovejasFiltradas) 22 | 23 | // [{ name: 'Navidad', color: 'rojo' }, 24 | // { name: 'Ki Na Ma', color: 'rojo' }] 25 | Recuerda. Debe contener las dos letras 'a' y 'n' en el nombre. No cuentes ovejas que sólo tenga una de las letras, debe 26 | tener ambas. 27 | */ 28 | 29 | /** 30 | * Title: Filtering sheeps using functional programming (filter) and regular expressions. 31 | * Complexity: Θ(N) 32 | * Comment: We iterate along the whole list and we just keep the sheeps with the given conditions. 33 | * For letters validation we use a regular expression '[nN][aA]|[aA][nN]': 34 | * ¤ The '|' character specifies that we are looking for strings that match the left part ([nN][aA]) or the right part 35 | * ([aA][nN]) 36 | * ¤ [nN] means that we are looking for an 'n' or an 'N'. It's the same with [aA]. 37 | * Putting both following each other means that we are looking an 'n' or an 'N' followed by an 'a' or an 'A'. 38 | * ¤ The right part means the same but in the oposite order. 39 | */ 40 | export default function contarOvejas(ovejas) { 41 | return ovejas.filter(oveja => oveja.color === 'rojo' && oveja.name.match(/[nN][^aA]*[aA]|[aA][^nN]*[nN]/)) 42 | } -------------------------------------------------------------------------------- /src/2021/exercices/day02.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Te ha llegado una carta ✉️ con todos los regalos que debes preparar. El tema es que es una cadena de texto y es muy 4 | difícil de leer 😱. ¡Menos mal que han puesto cada regalo separado por espacio! (aunque ten cuidado, porque al ser 5 | niños, igual han colado más espacios de la cuenta) 6 | 7 | Encima nos hemos dado cuenta que algunas palabras vienen con un _ delante de la palabra, por ejemplo _playstation, que 8 | significa que está tachado y no se tiene que contar. 9 | 10 | Transforma el texto a un objeto que contenga el nombre de cada regalo y las veces que aparece. Por ejemplo, si tenemos 11 | el texto: 12 | 13 | const carta = 'bici coche balón _playstation bici coche peluche' 14 | Al ejecutar el método debería devolver lo siguiente: 15 | 16 | const regalos = listGifts(carta) 17 | 18 | console.log(regalos) 19 | 20 | { 21 | bici: 2, 22 | coche: 2, 23 | balón: 1, 24 | peluche: 1 25 | } 26 | 27 | Ten en cuenta que los tests pueden ser más exhaustivos... 😝 ¡Cuidado con contar espacios vacíos! 28 | */ 29 | 30 | /** 31 | * Title: Filtering and grouping items via functional programming (filter + reduce). 32 | * Complexity: Θ(N) 33 | * Comment: 34 | * 1. We use trim function to remove undesired blank spaces, as the problem description tells us that this may happen. 35 | * 2. We split the text into a list of words using the inner spaces as separators (we removed the outter spaces in step 36 | * 1. For this we use the regex '/s' that means any whitespace, next line, etc. 37 | * 3. We filter from the list those words starting with underscore _, as the problem descriptions requires. 38 | * 4. We use reduce function to count the occurrences of the listed items: 39 | * 4.1. We set as the accumulator an empty object. This will be the object that at the end we will return. 40 | * 4.2. We will iterate the list of words, and for every word we will check if there is an existing key in the 41 | * accumulator with that same word. If it does, we just increment it's value 1 unit. If not, we do create an entry 42 | * in the accumulator with the word as key and value 1 as it's the first occurrence. 43 | */ 44 | 45 | export default function listGifts(letter) { 46 | return letter.trim().split(/\s+/).filter(w => !w.startsWith('_')).reduce((acc, word) => { 47 | acc[word] = (acc[word] || 0) + 1; 48 | return acc; 49 | }, {}); 50 | } -------------------------------------------------------------------------------- /src/2021/exercices/day03.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | El Grinch está abriendo las cartas que iban a Santa Claus y las está dejando hechas un lío. 😱 4 | 5 | Las cartas son una cadena de texto que incluyen regalos y paréntesis (). 6 | 7 | Para saber si una carta es válida ✅, debes comprobar que los paréntesis cierran correctamente y que, además, no vayan 8 | vacíos. 9 | 10 | ¡Pero ojo! Porque el Grinch ha dejado llaves { y corchetes [ dentro de los paréntesis que hacen que no sean válidas. 11 | Por suerte sólo los ha dejado en medio de los paréntesis... 12 | 13 | Ejemplos: 14 | 15 | "bici coche (balón) bici coche peluche" // -> ✅ 16 | "(muñeca) consola bici" // ✅ 17 | 18 | "bici coche (balón bici coche" // -> ❌ 19 | "peluche (bici [coche) bici coche balón" // -> ❌ 20 | "(peluche {) bici" // -> ❌ 21 | "() bici" // ❌ 22 | 23 | Crea una función que pasándole el texto de la carta, devuelva true si es válida y false si no lo es. ¡Y acaba con la 24 | travesura del Grinch! 25 | */ 26 | 27 | /** 28 | * Title: Checking valid strings via regular expressions 29 | * Comment: 30 | * 1. To scape the parenthesis () and brackets [] we use the backwards slash \. 31 | * 2. The | character is a logical OR 32 | * 3. The first part of the logical operation, '\(\)', looks for empty parenthesis () 33 | * 4. The second part of the logical operation, '[\[\]{}]', looks for brackets, '[' or ']', and braces, '{' or '}'. 34 | * The first and the last characters of the expressions are also brackets, but their not escpaed, which means that 35 | * every character inside them are what we are looking for: '[' or ']' or '{' or '}'. 36 | * 5. The third part of the expression, '\([^\)]*$', looks for unclosed parenthesis. It only works for 1-depth 37 | * parenthesis, enough for this game. 38 | */ 39 | 40 | export default function isValid(letter) { 41 | return letter.match(/\(\)|[\[\]{}]|\([^\)]*$/) == null 42 | } -------------------------------------------------------------------------------- /src/2021/exercices/day04.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | ¡Es hora de poner el árbol de navidad en casa! 🎄 4 | 5 | Para ello vamos a crear una función que recibe la altura del árbol, que será un entero positivo del 1 a, como máximo, 6 | 100. 7 | 8 | Si le pasamos el argumento 5, se pintaría esto: 9 | 10 | ____*____ 11 | ___***___ 12 | __*****__ 13 | _*******_ 14 | ********* 15 | ____#____ 16 | ____#____ 17 | Creamos un triángulo de asteriscos * con la altura proporcionada y, a los lados, usamos el guión bajo _ para los 18 | espacios. Es muy importante que nuestro árbol siempre tenga la misma longitud por cada lado. 19 | Todos los árboles, por pequeños o grandes que sean, tienen un tronco de dos líneas de #. 20 | 21 | Otro ejemplo con un árbol de altura 3: 22 | 23 | __*__ 24 | _***_ 25 | ***** 26 | __#__ 27 | __#__ 28 | Ten en cuenta que el árbol es un string y necesitas los saltos de línea \n para cada línea para que se forme bien el 29 | árbol. 30 | */ 31 | 32 | /** 33 | * Title: Building the tree iteratively concatenating text. 34 | * Comment: 35 | * 1. Formulas are not cleaned as in the second implementation so people can understand where do they come from. 36 | * 2. This is a readable solution, but a very boring one. 37 | */ 38 | // export default function createXmasTree(height) { 39 | // function fillRow(start, end, width, character = '*') { 40 | // let text = ''; 41 | // for (let i = 0; i < start; i++) text += '_'; 42 | // for (let i = start; i < end; i++) text += character; 43 | // for (let i = end; i < width; i++) text += '_'; 44 | // return text; 45 | // } 46 | // 47 | // let tree = ''; 48 | // const width = height * 2 - 1; 49 | // for (let h = 1; h < height + 1; h++) { 50 | // let rowWidth = h * 2 - 1; 51 | // let rowSpaces = (width - rowWidth) / 2; 52 | // tree += fillRow(rowSpaces, width - rowSpaces, width); 53 | // tree += '\n'; 54 | // } 55 | // let rowWidth = 1; 56 | // let rowSpaces = (width - rowWidth) / 2; 57 | // return `${tree}${fillRow(rowSpaces, width - rowSpaces, width, '#')}\n${fillRow(rowSpaces, width - rowSpaces, width, '#')}`; 58 | // } 59 | 60 | /** 61 | * Title: Building the tree in just two lines using ternary operator and recursion. 62 | * Comment: 63 | * 1. The first line declares a very unreadable recursive function, and the second line calls it and returns its value. 64 | * 2. The inner function works as a reduction function, as it has an accumulator (string) and the value of the iteration 65 | * h. 66 | * 3. For every line created we add a newLine '\n'. As when we would be finished there would be one extra undesired 67 | * newLine, we need to trim it off using the trimRight function. We call this function in the base case just before 68 | * returning the whole finished tree. 69 | * 3. First we evaluate with a ternary operator if we are in the base case, which is being over the required height (we 70 | * go from 1 to N instead of 0 to N-1 because of mathematical reasons). 71 | * 4. If we are in the base case it means we have already created the "leaves", so only the trunk is missing. For that 72 | * we just print height-1 underscores using the String.prototype.repeat function, an # and, again, height-1 underscores. 73 | * We need to repeat this new line in order to get the second line of the base, and we do use again the repeat function. 74 | * 5. If the ternary operator evaluates that we are not in the base case it means we need to add to the accumulator, at 75 | * least, an extra line for the leaves. It's a similar procedure than in the base case, but with simplified formulas: 76 | * 5.1. We evaluate the width of the whole tree by multiplying its height by 2 and subtracting it 1 unit: 77 | * width = height * 2 - 1. 78 | * 5.2. Now we can evaluate the width of the leaves at an specific height by multiplying its height by 2 and 79 | * subtracting it 1 unit: heightWidth = h * 2 - 1. We see both formulas are quite similar. 80 | * 5.3. However, we don't know where the tree starts in the horizontal axis at this height. We need to check it out 81 | * using the tree width. We also want to know how many free spaces will be at every side to add the 82 | * underscores: underscoreSpaces = (treeWidth - heightWidth) / 2. 83 | * If we simplify this expression we get: underscoreSpaces = height - h 84 | * 5.4. Now we already have the number of underscores for each side and the number of leaves, so we add it in the 85 | * same way we did in the base case. 86 | * 5.5. As it is a recursive function we shall return the same function with it's height increased in one unit and 87 | * the new created line concatenated to the received accumulator. 88 | */ 89 | export default function createXmasTree(height) { 90 | const createTree = ((acc, h) => h > height ? acc + `${'_'.repeat(height - 1)}#${'_'.repeat(height - 1)}\n`.repeat(2).trimRight() : createTree(acc + `${'_'.repeat(height - h)}${'*'.repeat(2 * h - 1)}${'_'.repeat(height - h)}\n`, h + 1)); 91 | return createTree("", 1); 92 | } -------------------------------------------------------------------------------- /src/2021/exercices/day05.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Con la emoción, ya estamos empezando a contar los días del calendario hasta el 25 de diciembre 📆. 4 | 5 | Para ayudar a esto, vamos a crear una función que pasándole una instancia de Date nos diga el número de días que faltan. 6 | 7 | Veamos unos ejemplos: 8 | 9 | const date1 = new Date('Dec 1, 2021') 10 | daysToXmas(date1) // 24 11 | const date2 = new Date('Dec 24, 2021 00:00:01') 12 | daysToXmas(date2) // 1 13 | const date3 = new Date('Dec 24, 2021 23:59:59') 14 | daysToXmas(date3) // 1 15 | const date4 = new Date("December 20, 2021 03:24:00") 16 | daysToXmas(date4) // 5 17 | El resultado tiene que ser un número entero y, como ves, aunque falte un segundo hasta el siguiente día, se entiende 18 | que todavía falta un día. 19 | 20 | ¡Pero ojo! También hay que indicar si la fecha es del mismo día (devolveríamos 0) o si es una fecha futura 21 | (devolveríamos el número de días en negativo -): 22 | 23 | const date = new Date('Dec 25, 2021') 24 | daysToXmas(date) // 0 25 | const date1 = new Date('Dec 26, 2021') 26 | daysToXmas(date1) // -1 27 | const date2 = new Date('Dec 31, 2021') 28 | daysToXmas(date2) // -6 29 | const date3 = new Date('Jan 1, 2022 00:00:01') 30 | daysToXmas(date3) // -7 31 | const date4 = new Date('Jan 1, 2022 23:59:59') 32 | daysToXmas(date4) // -7 33 | Por cierto, la fecha de referencia para saber si es 25 de diciembre es Dec 25, 2021. 34 | */ 35 | 36 | /** 37 | * Title: Going through the calendar adding and subtracting days. 38 | * Comment: 39 | * This is the solution developed before realising about the second one, which I think it's a better one. 40 | * 1. First we add or subtract 365, or 366 if it is leap, to out day's counter for every iterated year until we get our 41 | * desired year 2021. 42 | * 2. Now we do the same with the months, adding or subtracting 28,30 or 31 depending on the iterated month until we get 43 | * to December. 44 | * 3. Finally we only have to add to the counter the subtraction between 25 (Christmas day) and the date's day. 45 | */ 46 | // export default function daysToXmas(date) { 47 | // 48 | // function isLeap(year) { 49 | // return year % 4 == 0 || (year % 100 == 0 && year % 400 == 0); 50 | // } 51 | // 52 | // function getMonthDays(month, leap = false) { 53 | // if (month === 1) return leap ? 29 : 28; 54 | // if ( 55 | // month === 0 || 56 | // month === 2 || 57 | // month === 4 || 58 | // month === 6 || 59 | // month === 7 || 60 | // month === 9 || 61 | // month === 11 62 | // ) 63 | // return 31; 64 | // return 30; 65 | // } 66 | // 67 | // let count = 0; 68 | // const [month, day, year] = [ 69 | // date.getMonth(), 70 | // date.getDate(), 71 | // date.getFullYear(), 72 | // ]; 73 | // 74 | // if (year > 2021) { 75 | // for (let i = 2021; i < year; i++) count -= isLeap(i) ? 366 : 365; 76 | // } else if (year < 2021) { 77 | // for (let i = year; i < 2021; i++) count += isLeap(i) ? 366 : 365; 78 | // } 79 | // 80 | // for (let i = month; i < 11; i++) count += getMonthDays(i); 81 | // 82 | // return count + (25 - day); 83 | // } 84 | 85 | /** 86 | * Title: Subtracting both dates and get the elapsed days via mathematical operations and ceiling. 87 | * Comment: 88 | * 1. I didn't like this solution because I thought it would be incorrect if there would be leap years, but later I 89 | * realised that the date subtraction would consider that. 90 | * 2. There was another problem, I thought, as in the case of passing the 24 Dec 2021 23h, only an hour before Christmas, 91 | * I thought the program would return 0 days, but Math.ceil help us with this. 92 | */ 93 | export default function daysToXmas(date) { 94 | return Math.ceil((new Date('Dec 25, 2021') - date) / (1000 * 3600 * 24)); 95 | } -------------------------------------------------------------------------------- /src/2021/exercices/day06.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Antes de poder disfrutar de la navidad... nos toca terminar de rematar los exámenes finales. ¡Y toca un poco de matemáticas! 😱 4 | 5 | A una función se le pasan dos parámetros: un Array con números y el resultado que se espera. 6 | 7 | La función debe devolver los dos valores del Array que sumen el resultado esperado. Como a veces pueden haber más de 8 | dos valores que sumen, se devolverá el primero empezando por la izquierda que encuentre otro par, sin importar lo lejos 9 | que esté a la derecha. 10 | 11 | Si no se encuentra, se devuelve null. 12 | 13 | Veamos unos ejemplos: 14 | 15 | sumPairs([3, 5, 7, 2], 10) // [3, 7] 16 | sumPairs([-3, -2, 7, -5], 10) // null 17 | sumPairs([2, 2, 3, 1], 4) // [2, 2] 18 | sumPairs([6, 7, 1, 2], 8) // [6, 2] 19 | sumPairs([0, 2, 2, 3, -1, 1, 5], 6) // [1, 5] 20 | El resultado tiene que ser un array con dos números. 21 | 22 | Una vez que tengas el resultado... ¿cómo podrías hacer que fuese lo más óptimo posible para no tener que recorrer las 23 | mismas situaciones dos veces 🤔? 24 | */ 25 | 26 | /** 27 | * Title: Brute force that stops when finds solution. 28 | * Complexity: O(N^2) 29 | * Comment: This is not an elegant solution, but it works and in this case I think, as I'm not an expert on dynamic 30 | * programming, it would work better than the fixed second solution (not implemented because I think it was not worth 31 | * it). 32 | */ 33 | export default function sumPairs(numbers, result) { 34 | for (let i = 0; i < numbers.length - 1; i++) { 35 | for (let j = i + 1; j < numbers.length; j++) { 36 | if (numbers[i] + numbers[j] === result) return [numbers[i], numbers[j]]; 37 | } 38 | } 39 | return null; 40 | } 41 | 42 | /** 43 | * Title: Optimal using dynamic programming. 44 | * Complexity: O(N) 45 | * Comment: Correct algorithm but does not work for tests because of solution order. 46 | * There is people who tries to reorder the 'posible solutions' array, but trying to reordering has the same or even 47 | * more time complexity than the first solution 48 | */ 49 | // export default function sumPairs(numbers, result) { 50 | // let map = new Map(); 51 | // for (const number of numbers) { 52 | // if (map.has(number)) return [map.get(number), number]; 53 | // map.set(result - number, number); 54 | // } 55 | // return null; 56 | // } 57 | 58 | /* 59 | Antes de poder disfrutar de la navidad... nos toca terminar de rematar los exámenes finales. ¡Y toca un poco de matemáticas! 😱 60 | 61 | A una función se le pasan dos parámetros: un Array con números y el resultado que se espera. 62 | 63 | La función debe devolver los dos valores del Array que sumen el resultado esperado. Como a veces pueden haber más de dos valores que sumen, se devolverá el primero empezando por la izquierda que encuentre otro par, sin importar lo lejos que esté a la derecha. 64 | 65 | Si no se encuentra, se devuelve null. 66 | 67 | Veamos unos ejemplos: 68 | 69 | sumPairs([3, 5, 7, 2], 10) // [3, 7] 70 | sumPairs([-3, -2, 7, -5], 10) // null 71 | sumPairs([2, 2, 3, 1], 4) // [2, 2] 72 | sumPairs([6, 7, 1, 2], 8) // [6, 2] 73 | sumPairs([0, 2, 2, 3, -1, 1, 5], 6) // [1, 5] 74 | El resultado tiene que ser un array con dos números. 75 | 76 | Una vez que tengas el resultado... ¿cómo podrías hacer que fuese lo más óptimo posible para no tener que recorrer las mismas situaciones dos veces 🤔? 77 | */ -------------------------------------------------------------------------------- /src/2021/exercices/day07.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Mi amigo Dani está trabajando en una tienda y con la llegada de las navidades tiene el almacén hecho un desastre y no 4 | encuentra nada. 5 | 6 | Vamos a crear una función contains que recibe dos parámetros: un objeto que define el almacén y el producto que 7 | buscamos. 8 | 9 | La función debe devolver un booleano que indique si se encuentra el string como valor en algún nivel del objeto. Veamos 10 | unos ejemplos: 11 | 12 | const almacen = { 13 | 'estanteria1': { 14 | 'cajon1': { 15 | 'producto1': 'coca-cola', 16 | 'producto2': 'fanta', 17 | 'producto3': 'sprite' 18 | } 19 | }, 20 | 'estanteria2': { 21 | 'cajon1': 'vacio', 22 | 'cajon2': { 23 | 'producto1': 'pantalones', 24 | 'producto2': 'camiseta' // <- ¡Está aquí! 25 | } 26 | } 27 | } 28 | 29 | contains(almacen, 'camiseta') // true 30 | 31 | const otroAlmacen = { 32 | 'baul': { 33 | 'fondo': { 34 | 'objeto': 'cd-rom', 35 | 'otro-objeto': 'disquette', 36 | 'otra-cosa': 'mando' 37 | } 38 | } 39 | } 40 | 41 | contains(otroAlmacen, 'gameboy') // false 42 | Ten en cuenta que la tienda es enorme. Tiene diferentes almacenes y, como has visto en los ejemplos, cada uno puede 43 | tener diferentes organizaciones.Lo importante es buscar que el producto está en los almacenes. 44 | */ 45 | 46 | /** 47 | * Title: Recursive solution 48 | * Comment: 49 | * 1. There are two base case: 50 | * 1.1. Case A: the store param is actually the requested product, we return true; 51 | * 1.2. Case B: The store param is falsy, not an object (could be another item in the store but not the requested 52 | * one), it is an empty object or it is an Array (in JS arrays are objects too so the second condition would be 53 | * TRUE). In any of those situations we return FALSE. 54 | * 55 | * 2. Otherwise, we have an non-empty object. We extract its values into an array and iterate through it applying 56 | * recursion to every item in order to find the requested product. If in any of these iterations we find it, we return 57 | * TRUE. Otherwise, we return FALSE. 58 | */ 59 | export default function contains(store, product) { 60 | if (store === product) return true; 61 | if (!(store && typeof store == 'object' && Object.values(store).length) || Array.isArray(store)) return false; 62 | 63 | const values = Object.values(store); 64 | for (let i = 0; i < values.length; i++) 65 | if (contains(values[i], product)) return true; 66 | return false; 67 | } -------------------------------------------------------------------------------- /src/2021/exercices/day08.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Invertir en criptomonedas es casi un deporte de riesgo. El otro día hackearon Bitmart y ha hecho que el valor de 4 | Bitcoin, y otras monedas, bajase un 25%. 5 | 6 | Vamos a escribir una función que reciba la lista de precios de una criptomoneda en un día y debemos devolver la 7 | ganancia máxima que podríamos sacar si compramos y vendemos la inversión el mismo día. 8 | 9 | La lista de precios es un array de números y representa el tiempo de izquierda a derecha. Por lo que ten en cuenta que 10 | no puedes comprar a un precio que esté a la derecha de la venta y no puedes vender a un precio que esté a la izquierda 11 | de la compra. 12 | 13 | Por ejemplo: 14 | 15 | const pricesBtc = [39, 18, 29, 25, 34, 32, 5] 16 | maxProfit(pricesBtc) // -> 16 (compra a 18, vende a 34) 17 | 18 | const pricesEth = [10, 20, 30, 40, 50, 60, 70] 19 | maxProfit(pricesEth) // -> 60 (compra a 10, vende a 70) 20 | 21 | Si ese día no se puede sacar ningún beneficio, tenemos que devolver -1 para evitar que hagamos una locura: 22 | 23 | const pricesDoge = [18, 15, 12, 11, 9, 7] 24 | maxProfit(pricesDoge) = // -> -1 (no hay ganancia posible) 25 | 26 | const pricesAda = [3, 3, 3, 3, 3] 27 | maxProfit(pricesAda) = // -> -1 (no hay ganancia posible) 28 | */ 29 | 30 | /** 31 | * Title: Standard solution. 32 | * Complexity: O(N log N) 33 | * Comment: 34 | * 1. The adopted idea is the iteration of the array and on every iteration we do another search throw the right part of 35 | * the array looking for the minimum highest value for selling. 36 | * 2. If we find a higher value for selling than the value we are studying for buying, we compare it's difference to the 37 | * best previous value. If it is higher, which means we got a better profit, we keep it and resume the search. 38 | */ 39 | export default function maxProfit(prices) { 40 | let record = -1; 41 | for (let buy = 0; buy < prices.length - 1; buy++) { 42 | for (let sell = buy + 1; sell < prices.length; sell++) { 43 | let tmpProfit = prices[sell] - prices[buy]; 44 | if (tmpProfit > record && tmpProfit > 0) record = tmpProfit; 45 | } 46 | } 47 | return record 48 | } -------------------------------------------------------------------------------- /src/2021/exercices/day09.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | En la fábrica de Papa Noél 🎅 se acerca el día especial... y todavía tenemos un montón de cosas por contar. 😅 4 | 5 | Por suerte a Mark Zucktheelf 🧝 se le ha ocurrido crear una función que permita agrupar un array, que puede ser de 6 | valores u objetos, a través de una función o de una propiedad. 7 | 8 | Nos trae un montón de ejemplos: 9 | 10 | groupBy([6.1, 4.2, 6.3], Math.floor) // { 6: [6.1, 6.3], 4: [4.2] } 11 | groupBy(['one', 'two', 'three'], 'length') // { 3: ['one', 'two'], 5: ['three'] } 12 | groupBy([{age: 23}, {age: 24}], 'age') // { 23: [{age: 23}], 24: [{age: 24}] } 13 | 14 | groupBy( 15 | [1397639141184, 1363223700000], 16 | timestamp => new Date(timestamp).getFullYear() 17 | ) 18 | // { 2013: [1363223700000], 2014: [1397639141184] } 19 | 20 | groupBy([ 21 | { title: 'JavaScript: The Good Parts', rating: 8 }, 22 | { title: 'Aprendiendo Git', rating: 10 }, 23 | { title: 'Clean Code', rating: 9 }, 24 | ], 'rating') 25 | // { 8: [{ title: 'JavaScript: The Good Parts', rating: 8 }], 26 | // 9: [{ title: 'Clean Code', rating: 9 }], 27 | // 10: [{ title: 'Aprendiendo Git', rating: 10 }] } 28 | Como ves, la función groupBy recibe una colección (array) y una función o una propiedad, y devuelve un objeto con 29 | claves que son los valores de la función ejecutada pasando como argumento cada elemento o de la propiedad por cada 30 | elemento. Luego los valores son un array de los valores que tengan la misma llave. 31 | 32 | La dificultad del reto está más en comprender la función que en la implementación. ¡Suerte!. 33 | */ 34 | 35 | /** 36 | * Title: Functional solution via reduction. 37 | * Complexity: Θ(N) 38 | * Comment: 39 | * 1. The solution is simple: applying the grouping condition to every value on the collection and storing its results 40 | * on an object. For that we use reduction. 41 | * 2. However, the implementation of the reduction function depends on if the grouping condition is a callable function 42 | * or a property from the items in the collection. Because of this we require two blocks of code, quite similar among them. 43 | * 3. There would be an alternative to only have one block of code: checking the grouping condition inside the reduction 44 | * function. However, I dont quite like this idea, because you would be checking it on every iteration, N times. Some 45 | * people defend this is not a terrible idea, as the checking cost would not be very significant, and the complexity of 46 | * the algorithm resides the grouping condition itself. There is no bad or good idea, you may chose whatever you prefer. 47 | */ 48 | export default function groupBy(collection, it) { 49 | if (typeof (it) === 'function') 50 | return collection.reduce((result, item) => { 51 | const key = it(item); 52 | if (result[key]) result[key].push(item); 53 | else result[key] = [item]; 54 | return result; 55 | }, {}); 56 | 57 | return collection.reduce((result, item) => { 58 | const key = item[it]; 59 | if (result[key]) result[key].push(item); 60 | else result[key] = [item]; 61 | return result; 62 | }, {}); 63 | } -------------------------------------------------------------------------------- /src/2021/exercices/day10.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Para mejorar la productividad de la tienda en la que trabajamos, vamos a crear una pequeña máquina que calcula el 4 | mínimo número de monedas que debemos usar para dar el cambio de una compra en metálico. 5 | 6 | Las monedas para cambio que puedes usar son estas: 7 | 8 | coins[0] = 1 céntimo 9 | coins[1] = 2 céntimos 10 | coins[2] = 5 céntimos 11 | coins[3] = 10 céntimos 12 | coins[4] = 20 céntimos 13 | coins[5] = 50 céntimos 14 | Tenemos que crear una función que recibe el número de céntimos que hay que devolver al cliente y la función nos da un 15 | array con la combinación de monedas mínimas que debemos usar para conseguirlo. 16 | 17 | getCoins(51) // [1, 0, 0, 0, 0, 1] -> una moneda de 1 céntimo y otra de 50 céntimos 18 | getCoins(3) // [1, 1, 0, 0, 0, 0] -> una moneda de 1 céntimo y otra de 2 19 | getCoins(5) // [0, 0, 1, 0, 0, 0] -> una moneda de 5 céntimos 20 | getCoins(16) // [1, 0, 1, 1, 0, 0] -> una moneda de 1 céntimo, una de 5 y una de 10 21 | getCoins(100) // [0, 0, 0, 0, 0, 2] -> dos monedas de 50 céntimos 22 | La dificultad del reto está en saber utilizar correctamente una estructura que te permita conocer las monedas que 23 | tienes disponible para crear el array con la devolución, ya que debes usar siempre el menor número de monedas posible. 24 | ¡Suerte 👩‍💻👨‍💻!. 25 | */ 26 | 27 | /** 28 | * Title: Functional using bit-to-bit operators 29 | * Complexity: Θ(N) 30 | * Comment: 31 | * This was my first attempt. Later, with the help of @suanserio (Twitter) I got a more evil one 😈. 32 | * 1. The idea of the algorithm is to iterate the coinsValue array while selecting how many coins (units) of every value 33 | * would be necessary in order to the the lowest number of total coins. 34 | * 2. To get the lower quantity of coins what we need to do is to get the highest quantity of 'big' coins possible. 35 | * E.g. If we need 51 cents in coins, its better to have a 1x50 + 1x1 than 51x1 or 25x2 + 1x1, etc. 36 | * 3. Because of this, we need to iterate the array from the highest values to the lowest values: descending. However, 37 | * the requested array must be ascending sorted. We have three options: 38 | * 3.1. Iterate the array in a descending way and finally reverse it. That would be a simple but inefficient 39 | * solution, as we would be traversing the array again, getting an Θ(N^2) solution. 40 | * 3.2. Using mathematical operations to get the result array's index. This would be nice, although it would add 41 | * some complexity to the code (nobody likes formulas, even if they are this simple). 42 | * 3.3. Using the ReduceRight function instead of Reduce in order to iterate the array from right to left (no need 43 | * for resorting). This is the chosen one because of its simplicity. 44 | * 4. For the reduction inner function we need the accumulator, the iterated value of the coinsValues array and it's 45 | * index. The accumulator is just a counter of the coins values, starting all the values to 0. The index is necessary 46 | * for updating the accumulator at the same position as the iterated value. 47 | * 5. The inner function: 48 | * 5.1. First we divide the current change to the current coin value. The result would be the quantity of coins of 49 | * this value necessary, and we store it in the solutions array (accumulator). For doing so we need and integer 50 | * division. We could just trunk the solution via Math.trunk, but I preferred to do a bit-to-bit division because 51 | * it's funnier 😅. 52 | * 5.2. Then we get the mod. This would be the resulting change after retrieving the previously calculated coins. 53 | */ 54 | // export default function getCoins(change) { 55 | // const coinsValues = [1, 2, 5, 10, 20, 50]; 56 | // return coinsValues.reduceRight((coins, coinValue, index) => { 57 | // coins[index] = ~~(change / coinValue); 58 | // change = change % coinValue; 59 | // return coins; 60 | // }, Array(coinsValues.length).fill(0)); 61 | // } 62 | 63 | /** 64 | * Title: One-linear madness. 65 | * Complexity: Θ(N log N) 66 | * Comment: 67 | * - Twitter's user @suanserio looked my previous solutions and he helped me developing this pure evil one-linear program. 68 | * The procedure is very similar, but very compacted. 69 | * - It's important to say that this is a more complicated solution, less readable and more time complex, so I would not 70 | * recommend to write code like this. Do it only for fun on this kind of challenges. 71 | * 72 | * 1. Now we start from an empty array, and we will be concatenating the calculated values on it's head, so we won't 73 | * need the index anymore. 74 | * 2. So on every iteration we would be returning a new array. On it's head there will be the current calculated number 75 | * of the current coin, and after it there would be the items of the accumulator. That's why we need the spread 76 | * operator '...'. 77 | * 3. You may have notice about a third element on the returning array. This would mean nothing to the actual array, 78 | * its only purpose is to decrease the change value on every iteration: 79 | * 3.1. change %= coinValue would modify change's value, and returns it. We only want to decrease, and not to 80 | * receive its value, because that would add it into the array and we do not want that. For ignorig it we just do 81 | * it inside a void function. 82 | * 3.2. This will return 'undefined', but we do not want this either, so we use the logical operator 'or' || and 83 | * add a second value: and empty array. As the left part will always be undefined, the result will always be the 84 | * empty array. 85 | * 3.3. But we are not finished yet. This will return and empty array as the last item of the accumulator 86 | * (e.g. [1,[])). However, using the spread operator we can obtains all its values, in the same way we did to get 87 | * all the accumulator items for the resulting array. As it is an empty array it would return nothing: now we do 88 | * are done :). 89 | * 90 | */ 91 | export default function getCoins(change) { 92 | return [1, 2, 5, 10, 20, 50].reduceRight((acc, coinValue) => [~~(change / coinValue), ...acc, ...void (change %= coinValue) || []], []); 93 | } -------------------------------------------------------------------------------- /src/2021/exercices/day11.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Este mes de diciembre hay películas super interesantes en el cine... y tengo que optimizar cómo gasto el dinero. 4 | 5 | Mi cine favorito tiene dos posibilidades: 6 | 7 | • Entrada de un sólo uso: Cuesta 12$ por cada película. 8 | 9 | • Tarjeta de fidelidad: Cuesta 250$ pero que cada vez que vas pagas sólo el 75% del precio del ticket. ¡Lo mejor es que 10 | se acumula! Y cada vez que vas, se paga el 75% del precio del ticket que pagaste la última vez. 11 | Ejemplo de cada una al comprar 3 entradas y el precio que pagaría en total: 12 | 13 | // Entrada normal: 12$ * 3 = 36$ 14 | // Tarjeta fidelidad: 250$ + (12$ * 0,75) + (12$ * 0,75 * 0,75) + (12$ * 0,75 * 0,75 * 0,75) = 270,8125$ 15 | Necesito una función que, al pasarle las veces que voy a ir al cine, me diga si vale la pena comprar la tarjeta 16 | fidelidad o no. 17 | 18 | shouldBuyFidelity(1) // false -> Mejor comprar tickets de un sólo uso 19 | shouldBuyFidelity(100) // true -> Mejor comprar tarjeta fidelidad 20 | La dificultad del reto está en encontrar una fórmula sencilla que nos diga el precio con descuento acumulado para la 21 | tarjeta fidelidad. 😜 22 | */ 23 | 24 | /** 25 | * Title: Recursion + ternary operator + arrow function. 26 | * Complexity: Θ(N) 27 | * Comment: 28 | * -I spent some time trying to implement the shortest solution. This is the best I could get. 29 | * -The idea is to create a one-liner tail recursive function and then to call it with the initial params. 30 | * 1. On every iteration the ticket's counter (times) value will be decreased, while the returning value would be 31 | * increased (the ticket's cost) 32 | * 2. The idea of the algorithm is to get to the base case (tickets = 0) and then on every iteration add the current 33 | * ticket price to the accumulated price. This way we will be iterating twice (Θ(2N) ~= Θ(N)) 34 | */ 35 | export default function shouldBuyFidelity(times) { 36 | const calculateDiscount = t => t === 0 ? 0 : Math.pow(0.75, t) * 12 + calculateDiscount(t - 1); 37 | return 12 * times >= 250 + calculateDiscount(times); 38 | } -------------------------------------------------------------------------------- /src/2021/exercices/day12.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | En el taller de Santa 🎅 se están preparando los trineos de motor eléctrico para poder hacer la ruta perfecta para 4 | dejar los regalos. 5 | 6 | La ruta empieza en el punto 0 y de ahí va hacia la derecha en línea recta. 7 | 8 | El Keanu Relfes 🧝 nos ha preparado una lista de obstáculos a evitar. El problema es que nos ha dado la lista de 9 | posiciones de los obstáculos desordenada... 😅 aunque al menos nunca la posición 0 puede tener un obstáculo. 10 | 11 | Encima, el trineo sólo se puede configurar para saltar un número fijo de posiciones... 😱 12 | 13 | Necesitamos una función que nos diga la longitud mínima del salto del trineo para ir evitando todos los obstáculos en 14 | la ruta. 15 | 16 | const obstacles = [5, 3, 6, 7, 9] 17 | getMinJump(obstacles) // -> 4 18 | 19 | S es salto, X es obstáculo 20 | Así quedaría la representación: 21 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 22 | . . . X . X X X . X . 23 | S-----------S-----------S------- 24 | 25 | 26 | const obstacles = [2, 4, 6, 8, 10] 27 | getMinJump(obstacles) // -> 7 28 | 29 | // Así quedaría la representación: 30 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 31 | . . X . X . X . X . X 32 | S--------------------S--------- 33 | 34 | // Longitudes de salto: 35 | // 1 caería en el 2 36 | // 2 caería en el 2 37 | // 3 caería en el 6 38 | // 4 caería en el 4 39 | // 5 caería en el 10 40 | // 6 caería en el 6 41 | // 7 es el ideal!!! ✅ 42 | 43 | getMinJump([1, 2, 3, 5]) // -> 4 44 | getMinJump([3, 7, 5]) // -> 2 45 | getMinJump([9, 5, 1]) // -> 2 46 | 47 | La dificultad del reto está en pensar que sólo podemos configurar el salto del trineo una vez y que buscamos el salto 48 | mínimo que nos serviría para sortear todos los obstáculos. 49 | */ 50 | 51 | /** 52 | * Title: Functional + multiple condition 53 | * Complexity: O(N*T) -> T is the highest value of the list 54 | * Comment: 55 | * 1. First we get the highest value of the list. It's an O(N) operation but we will only do this once. 56 | * 2. The idea is to try every possible jump: from 1 to T, being T the highest value of the list. T is important because 57 | * we need to set a stopping point on the loop, and if we could not find any possible jump on the last iteration, a 58 | * jump which could pass all the obstacles at once would be the answer. 59 | * 3. To know if the current jump value would work we just have to ensure that there is no obstacle multiple of the 60 | * current value. Another option would be to create an inner loop for every jump to iterate the array and ensure 61 | * that with that value we won't hit any array's value. However, the proposed option is smarter, more simple and 62 | * shorter. 63 | */ 64 | export default function getMinJump(obstacles) { 65 | const maxValue = Math.max(...obstacles); 66 | for (let j = 1; j < maxValue; j++) if (obstacles.every(o => o % j !== 0)) return j; 67 | } -------------------------------------------------------------------------------- /src/2021/exercices/day13.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | ¡Hay demasiados regalos 🎁! Y envolverlos es una locura... 4 | 5 | Vamos a crear una función que pasándole un array de regalos, nos devuelva otro array pero donde todos los regalos han 6 | sido envueltos con asteriscos tanto por arriba como por los lados. 7 | 8 | Sólo tienes que tener en cuenta unas cosillas ✌️: 9 | 10 | - Si el array está vacío, devuelve un array vacío 11 | - Los regalos son emojis 🎁... por lo que tenlo en cuenta a la hora de contar su longitud... 12 | - Por suerte, cada posición del array siempre tiene la misma longitud... 13 | */ 14 | 15 | /** 16 | * Title: One-liner + functional + spread operator + string concatenation + ternary operator + template literals 17 | * Comment: 18 | * -The idea is simple: 19 | * a. All the items in the array have the same length. 20 | * b. We need to create a string, where every line would be one array's item headed and tailed by one asterisk. 21 | * c. We must wrap the string at the beginning and ending by asterisks with the same width than the array's items + 22 | * the two extra asterisks for the sides. 23 | * d. Emoji's length is two characters. We must take this in consideration. 24 | * 1. The ternary operator is for the case the gifts list is empty. Rules say that in this case we mast return and empty 25 | * list, so we return the very same list. 26 | * 2. Otherwise we create a new array with two elements*: 27 | * 2.1. The first element of the array is the result of a reduction: for every item in the array we wrap it with 28 | * two asterisks and add the resulting string into the accumulator. The initial accumulator is the first line of 29 | * the final string: the heading asterisks. 30 | * 2.2. As the accumulator is an array of strings, we need to use the spread operator to get its elements into the 31 | * final array. 32 | * 2.3. Now there is only one last thing missing: the last line of asterisks. Because of this we add it as the 33 | * second element of the final array, doing the same we did with the first layer (reduction function's initial 34 | * accumulator). 35 | */ 36 | export default function wrapGifts(gifts) { 37 | return gifts.length === 0 ? gifts : [...gifts.reduce((acc, gift) => [...acc, ...[`*${gift}*`]], ['*'.repeat(gifts[0].length + 2),]), '*'.repeat(gifts[0].length + 2)]; 38 | } -------------------------------------------------------------------------------- /src/2021/exercices/day14.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | ¡Hemos perdido a un reno y falta poco más de una semana para Navidad! 😱 4 | 5 | Lo peor es que son tantos que no sabemos cuál es el que nos falta... ¡Qué lío! A ver, Elfon Musk ha hecho inventario y 6 | nos pasa un array con los ids de cada reno. 7 | 8 | 👍 Lo bueno: los ids son números que pueden ir del 0 al 100, no están repetidos y sólo se ha perdido un reno. 9 | 👎 Lo malo: la lista está desordenada y podría faltar el último... 10 | 11 | Necesitamos una función que al pasarle la lista de ids de renos nos diga inmediatamente cuál es el que falta: 12 | 13 | missingReindeer([0, 2, 3]) // -> 1 14 | missingReindeer([5, 6, 1, 2, 3, 7, 0]) // -> 4 15 | missingReindeer([0, 1]) // -> 2 (¡es el último el que falta!) 16 | missingReindeer([3, 0, 1]) // -> 2 17 | missingReindeer([9, 2, 3, 5, 6, 4, 7, 0, 1]) // -> 8 18 | missingReindeer([0]) // -> 1 (¡es el último el que falta!) 19 | Parece fácil con una complejidad de O(n)... ¿crees que podrías hacerlo mejor? 20 | */ 21 | 22 | /** 23 | * Title: Iterating over an array looking for missing Id 24 | * Complexity: Θ(N) 25 | * Comment: 26 | * -The problem description suggests this could be resolved with a better performance than O(N). However, I could not 27 | * guess how. Feel free to comment a better solution. There is people who got it solved via firstly sorting the 28 | * list and then just trying to find out which element does no match with its index. However, a sorting algorithm 29 | * in JS has an O(N log N) time complexity, so it would actually be a worse solution than the one that I propose. 30 | * 1. The passed argument is a list from 0 to N-1 but with one missing item, so we suppose that the correct content of 31 | * the list should be from 0 to N-1. Because of this we create a set full of ids from 0 to N-1. This set will be our 32 | * initial accumulator. 33 | * 2. We iterate the ids list via reduction, and for every item on the ids list we remove it from the set. 34 | * 3. After removing all the items, only one last id will remain on the set: the missing id. If there is no id left on 35 | * the set, this means that the missing Id would be the Nth id, so we return ids.length. 36 | */ 37 | export default function missingReindeer(ids) { 38 | return [...ids.reduce((acc, id) => { 39 | acc.delete(id); 40 | return acc; 41 | }, new Set([...Array(ids.length).keys()])).values()][0] ?? ids.length; 42 | } -------------------------------------------------------------------------------- /src/2021/exercices/day15.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | ¡Estamos haciendo los últimos ajustes para el trineo de Santa Claus! 4 | 5 | Como ya sabes, el trineo es volador y estamos ajustando el motor para que haga parabolas lo más óptimas posibles. Para 6 | esto el salto debe ser siempre hacia arriba y, a partir del punto más alto, debe bajar siempre hacia abajo... 7 | 8 | Nuestro mecánico de confianza, Kiko Belfs, que tiene un Tesla genial, nos ha explicado que los saltos se pueden ver 9 | como arrays... y que sólo tenemos que asegurarnos que los números suben y bajan de forma correcta. También nos avisa 10 | que sólo pasaremos arrays de, como mínimo, tres posiciones. 11 | 12 | Nos ha pasado algunos ejemplos de cómo debería ser nuestra función y algunos resultados: 13 | 14 | checkSledJump([1, 2, 3, 2, 1]) // true: sube y baja de forma estricta 15 | checkSledJump([0, 1, 0]) // -> true: sube y baja de forma estricta 16 | checkSledJump([0, 3, 2, 1]) // -> true: sube y baja de forma estricta 17 | checkSledJump([0, 1000, 1]) // -> true: sube y baja de forma estricta 18 | 19 | checkSledJump([2, 4, 4, 6, 2]) // false: no sube de forma estricta 20 | checkSledJump([1, 2, 3]) // false: sólo sube 21 | checkSledJump([1, 2, 3, 2, 1, 2, 3]) // false: sube y baja y sube... ¡no vale! 22 | Lo importante: recorrer el array de izquierda a derecha para ver que la subida es siempre estricta, detectar el punto 23 | más alto y entonces ver que la bajada es estricta hacia abajo... 24 | */ 25 | 26 | /** 27 | * Title: Iterating over the array, checking out if the trend is OK. 28 | * Complexity: O(N) 29 | * Comment: 30 | * 1. Initial state: we set the up variable to true, indicating that we are going up as we should, and the lastValue to 31 | * the first item of the array. 32 | * 2. We start iterating the array from 1 to N-1, as the initial state is initialized with the first item of the list. 33 | * 3. During the iteration, if the received height is the same than the previous one it means that the ascending or the 34 | * descending is not strict. If we are going down and we receive a higher value than the last recorded one it means 35 | * we are going up again and this is not a parable. We return false in both situations. 36 | * 4. Otherwise, the value is OK. If the trend is going up but we now receive a lower value than the lastValue, it means 37 | * we are starting to descend. We update the lastValue and continue the loop. 38 | * 5. The loop only checks issues with the descending, but after exiting it we did not check if we ever descend. Because 39 | * of this we return the negated result of up, so if we never descended, would return false, and true otherwise. 40 | */ 41 | export default function checkSledJump(heights) { 42 | let up = true; 43 | let lastValue = heights[0] 44 | for (let i = 1; i < heights.length; i++) { 45 | if (heights[i] === lastValue || !up && heights[i] > lastValue) return false; 46 | if (up && heights[i] < lastValue) up = false; 47 | lastValue = heights[i]; 48 | } 49 | return !up; 50 | } -------------------------------------------------------------------------------- /src/2021/exercices/day16.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Lara Eloft ha encontrado unos restos élficos en una cueva, cerca del Círculo Polar Ártico, a 8 km al norte de Rovaniemi. 4 | 5 | Ahora se encuentra descifrando unas misteriosas cartas que contiene información sobre unos números que le puede hacer 6 | llegar al próximo objetivo. 7 | 8 | Lara tiene un documento que contiene una serie de números que pueden ser usados para descifrarlos: 9 | 10 | Símbolo Valor 11 | . 1 12 | , 5 13 | : 10 14 | ; 50 15 | ! 100 16 | Lara, además, ha notado una cosa. Los símbolos se restan si están inmediatamente a la izquierda de otro mayor. 😱 17 | 18 | Tenemos que crear una función que nos pasa una cadena de texto con símbolos y tenemos que transformarlo al número 19 | correcto. ¡Ojo! Si encuentras un símbolo que no entendemos, mejor que devolvamos un NaN: 20 | 21 | decodeNumbers('...') // 3 22 | decodeNumbers('.,') // 4 (5 - 1) 23 | decodeNumbers(',.') // 6 (5 + 1) 24 | decodeNumbers(',...') // 8 (5 + 3) 25 | decodeNumbers('.........!') // 107 (1 + 1 + 1 + 1 + 1 + 1 + 1 - 1 + 100) 26 | decodeNumbers('.;') // 49 (50 - 1) 27 | decodeNumbers('..,') // 5 (-1 + 1 + 5) 28 | decodeNumbers('..,!') // 95 (1 - 1 - 5 + 100) 29 | decodeNumbers('.;!') // 49 (-1 -50 + 100) 30 | decodeNumbers('!!!') // 300 31 | decodeNumbers(';!') // 50 32 | decodeNumbers(';.W') // NaN 33 | */ 34 | 35 | /** 36 | * Title: Two-liner program via functional programming. 37 | * Complexity: Θ(N) 38 | * Comment: 39 | * - The problem is a conversor from roman numbers to decimal numbers. 40 | * - Because of the reduction function's nature this solution will always traverse every item in the list, so it will 41 | * always has an O(N) time cost execution. The second solution is a bit more efficient, as we will see later. 42 | * 1. We need a data structure to work as a table for translation. We could use an object or a Map, both are fine. If we 43 | * wanted to write this program in a single line we could (should not) just declare this object every time we 44 | * wanted to access to it. It would be very evil, too much for me. I just wanted to let you know it is possible. 45 | * 2. The function's parameter 'symbols' is a string, but we cannot directly iterate every character on the string. We 46 | * could firstly use String.prototype.split to create an array of characters from the string, or we could use the 47 | * spread operator, as I did, to get the same result. I prefer the spread operator because it is more compact, 48 | * both methods are fine. 49 | * 3. I decided that the easiest way to read roman numbers is to do it from right to left. This way when you have to do 50 | * a subtraction it is easy because you just do it to the value that you already have. This is why I use the 51 | * reduceRight function. In it's inner function I need an accumulator (the decimal result), the current symbol and 52 | * the index. Index is important for subtracting, as I need to know which was the previous value (index + 1, as I'm 53 | * reading from right to left). 54 | * 4. If there is any unknown symbol, I must return 'NaN'. However, the sad part of 'reducing' is that I cannot return 55 | * any value until I have travers all the items on the list. That's why if I detect any unknown value I must continue 56 | * traversing, sweeping along the NaN value. Because of this, I first must check if the accumulator is NaN, and if 57 | * it does, return it to continue to the next iteration. In the second solution implemented I did not use 58 | * 'reduction' so ASAP I get a NaN the program ends. For doing this "checking" I use a ternary operator to 59 | * implement the return value in a single line (NOT recommended on serious scenarios). 60 | * 5. If the symbol is contained in the translation object I only have to add it's value to the accumulator and return 61 | * it. However, if it has a lower value than the next symbol (actually, the previously iterated one as I'm 62 | * traversing the string from right to left), I must subtract it from the accumulator, or what it is the same, add 63 | * it in a negative way. 64 | */ 65 | 66 | export default function decodeNumber(symbols) { 67 | let translation = {'.': 1, ',': 5, ':': 10, ';': 50, '!': 100}; 68 | return [...symbols].reduceRight((acc, symbol, index) => (isNaN(acc) || !translation[symbol]) ? NaN : acc + translation[symbol] * (symbols[index + 1] && translation[symbols[index + 1]] > translation[symbol] ? -1 : 1), 0); 69 | } 70 | 71 | 72 | /** 73 | * Title: Iterative program reading from left to right 74 | * Complexity: O(N) 75 | * Comment: 76 | * - There are some differences from the previous solution. The rest is the same. 77 | * ¤ This solution is iterative via a loop. There is no functional programming. 78 | * ¤ There is no need to iterate every item when he have already found an error (NaN), so time complexity remains 79 | * O(N) (worst scenario: all the symbols are known or the last symbol is unknown but to get there I had to 80 | * traverse the whole string) but not Θ(N) (this notation means that both O and Ω have the same complexity), as 81 | * it may be Ω(1) (best performing scenario, when the first symbol in the string is unknown so we stop at the 82 | * very fist beginning). 83 | * ¤ The string is read from left to right. In the previous solution's explanation I said that I found easier to 84 | * read it from right to left, but I said that as an human being. Computers don't give a **** about how 85 | * you, piece of flesh, do Maths. The way you decide to read the string has no impact on the solution or 86 | * its performance. I just wanted to show you both directions are OK. 87 | */ 88 | 89 | // export default function decodeNumber(symbols) { 90 | // let translation = {'.': 1, ',': 5, ':': 10, ';': 50, '!': 100}; 91 | // let result = 0; 92 | // for (let i = 0; i < symbols.length; i++) { 93 | // if (!translation[symbols[i]]) return NaN; 94 | // result += translation[symbols[i]] * (symbols[i + 1] && translation[symbols[i + 1]] > translation[symbols[i]] ? -1 : 1); 95 | // } 96 | // return result; 97 | // } -------------------------------------------------------------------------------- /src/2021/exercices/day17.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Las empresas de paquetería 📦 se preparan para la época de fiestas y la locura de envíos que les espera. 4 | 5 | La empresa funciona con flotas de furgonetas 🚛 y camiones 🚚. Las flotas tienen un organigrama, ya que existen rangos 6 | de nivel de experiencia. 7 | 8 | Necesitamos saber el número de paquetes que una persona va a poder gestionar en un día. Para ello se cuenta el número 9 | de paquetes que puede llevar esa persona y todos los transportistas que tiene en su equipo. Lo malo es que los datos 10 | están almacenados de una forma un poco rara en un array: 11 | 12 | El array contiene otros arrays que contienen los datos de cada transportista 13 | transportista[0] -> Nombre/ID del Transportista 14 | transportista[1] -> Paquetes que gestiona en un día 15 | transportista[2] -> Array con sus subordinados 16 | 17 | Para que lo veamos en código, tanto el array, como la función de dos parámetros para conseguir el número deseado: 18 | 19 | const carriers = [ 20 | ['dapelu', 5, ['midu', 'jelowing']], 21 | ['midu', 2, []], 22 | ['jelowing', 2, []] 23 | ] 24 | 25 | countPackages(carriers, 'dapelu') // 9 26 | // 5 de dapelu, 2 de midu y 2 de jelowing = 9 27 | 28 | const carriers2 = [ 29 | ['lolivier', 8, ['camila', 'jesuspoleo']], 30 | ['camila', 5, ['sergiomartinez', 'conchaasensio']], 31 | ['jesuspoleo', 4, []], 32 | ['sergiomartinez', 4, []], 33 | ['conchaasensio', 3, ['facundocapua', 'faviola']], 34 | ['facundocapua', 2, []], 35 | ['faviola', 1, []] 36 | ] 37 | 38 | countPackages(2, 'camila') // 15 39 | // 5 de camila, 4 de sergiomartinez, 3 de conchaasensio, 2 de facundocapua y 1 de faviola = 15 40 | ¡Ten cuidado! Como has visto en el segundo ejemplo, el organigrama puede tener diferentes niveles y además nos viene 41 | información que puede ser que no necesitemos. Debemos tener en cuenta el parámetro de carrierID para calcular bien el 42 | número y contar todo su equipo. 43 | */ 44 | 45 | /** 46 | * Title: Iterative solution 47 | * Complexity: O(N), Ω(1) 48 | * Comment: 49 | * - The best way to store this information would be map, but we got this messy array instead. 50 | * - The idea of my algorithm is to iterate over all the carriers until we find our required carrier and their subordinates. 51 | * If this were a tree, this would mean to find our desired element and travers its subtree. As we do not have a 52 | * tree we will have to travers through undesired items, although we already found our desired first carrier, 53 | * because we do not have a subtree to iterate so we have to continue iterating the main list. 54 | * 1. To know if I'm done searching I use a Set. I initialize this set with my required ID and after I find it I 55 | * continue iterating adding its subordinates and so on. If my set is ever empty this will mean that I'm done 56 | * searching, as there won't be any subordinates left. This way, I do not have to iterate every element unless the 57 | * last item was a subordinate. 58 | * 2. As in ES6 Set deleting and adding have a BigO time complexity of O(1), this is a very efficient algorithm. 59 | * 3. In the second IF i just check the result of deletion. I do it this way to save a line of code, as both 'delete' 60 | * and 'has' methods are O(1) and returns true if they found the item or false otherwise. As the returning result 61 | * is the same thing, I use deletion instead of using firs 'has' and then, later in the if's body, 'delete'. 62 | */ 63 | 64 | export default function countPackages(carriers, carrierID) { 65 | let wanted = new Set().add(carrierID); 66 | let result = 0; 67 | for (const carrier of carriers) { 68 | if (!wanted.size) return result; 69 | if (wanted.delete(carrier[0])) { 70 | carrier[2].forEach((subordinate) => wanted.add(subordinate)); 71 | result += carrier[1]; 72 | } 73 | } 74 | return result; 75 | } -------------------------------------------------------------------------------- /src/2021/exercices/day18.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Evelyn Belefzin 👩‍💻 está trabajando en un sistema operativo para ser usado en el taller de Santa Claus 🎅. 4 | 5 | Se ha dado cuenta que en el taller nadie le presta atención a los nombres de los ficheros y a veces intentan guardar el 6 | mismo fichero más de una vez... así que es importante que gestionemos bien los nombres duplicados. 7 | 8 | Tenemos que crear una función que al pasarnos un array de nombres de archivo devolvamos un array con el mismo número de 9 | elementos pero donde los nombres que se repetían se anexe al final (k) donde k sería el número de veces que se encontró 10 | repetido. 11 | 12 | Lo mejor es que veamos un ejemplo: 13 | 14 | const files = ['photo', 'postcard', 'photo', 'photo', 'video'] 15 | fixFiles(files) // ['photo', 'postcard', 'photo(1)', 'photo(2)', 'video'] 16 | 17 | const files2 = ['file', 'file', 'file', 'game', 'game'] 18 | fixFiles(files2) = ['file', 'file(1)', 'file(2)', 'game', 'game(1)'] 19 | 20 | // ojo que los elfos ya tenían archivos con (1)... ¡y pueden estar repetidos! 21 | const files3 = ['file', 'file(1)', 'icon', 'icon(1)', 'icon(1)'] 22 | fixFiles(files3) // ['file', 'file(1)', 'icon', 'icon(1)', 'icon(1)(1)'] 23 | Por cierto, nos han dicho que son Agile y usan Scrum. Por eso quieren saber cuánto tiempo vas a tardar para saber 24 | cuándo van a poder usarlo. Que hay prisa. 😝 Así que entra a Discord y cuéntanos. 25 | */ 26 | 27 | /** 28 | * Title: Using a map (or object) to store the values and their occurrences. 29 | * Complexity: Θ(N) 30 | * Comment: 31 | * - As the returning value will be the vary same list, but with a bit of modification on some of their values, I decided 32 | * to use the map function. 33 | * - The idea is to have a map to store the items as keys and their occurrences as values. The map (or object in this 34 | * case), starts empty, and during the iteration I'll be filling it. If I get to al element that doesn't appears on the 35 | * map, I added with an starting value of 1 (first occurrence). Otherwise I increments its existing value. 36 | * - The mapping works like this: 37 | * 1. I always return first the actual item. Then I check if the item exists on the map. If it does exist I just 38 | * have to return the value and increment it. Otherwise, I use the same trick I used on exercise 10. 39 | * 2. The trick consists on that I don't wanna to return nothing, as the first occurrences of items shall not have 40 | * the counter on their string. But I need to modify its value. By doing this[file] = 1 the returning value 41 | * would be '1'. I don't want this. Because of this I put this sentence in an inner void function so the 42 | * returning value would be 'undefined'. Why would I do that? I don't want 'undefined' either. Because by 43 | * getting and undefined I could use the nullish coalescing operator ?? (logical OR operator || would work 44 | * too) to return and empty string. By doing this I can modify its value and return nothing. 45 | */ 46 | export default function fixFiles(files) { 47 | return files.map(function (file) { 48 | return file + (this[file] ? `(${this[file]++})` : void (this[file] = 1) ?? '') 49 | }, {}); 50 | } 51 | 52 | /** 53 | * Title: Same implementation but using an arrow-function instead (now we cannot use 'this' inside the arrow-function so 54 | * we needed to declare a map outside of it). 55 | * Complexity: Θ(N) 56 | */ 57 | // export default function fixFiles(files) { 58 | // let map = {}; 59 | // return files.map(file => file + (map[file] ? `(${map[file]++})` : void (map[file] = 1) ?? '')); 60 | // } -------------------------------------------------------------------------------- /src/2021/exercices/day19.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Con motivo de las fechas más bonitas del año, en Platzi han lanzado una promoción muy especial porque la educación es 4 | un regalo 🎁 para siempre. 5 | 6 | En Platzi tienen más de 800 cursos 📚 pero, claro, nuestro tiempo es limitado. Así que vamos a optimizar nuestro tiempo 7 | disponible para completar dos cursos. 8 | 9 | Tenemos que crear una función que recibe dos parámetros. El primero es el número de horas que tenemos disponible ⏳ y el 10 | segundo es un array donde cada índice es un curso y el valor el tiempo que se tarda en completar. 11 | 12 | Tenemos claro que queremos hacer dos cursos así que la función debe devolver un array con el índice de los dos cursos 13 | que vamos a poder completar con el tiempo disponible proporcionado. Si no nos da tiempo, devolvemos null 14 | 15 | Vamos a ver unos ejemplos: 16 | 17 | learn(10, [2, 3, 8, 1, 4]) // [0, 2] -> con 10 horas disponibles lo mejor es que completemos los cursos en el índice 0 18 | y 2. 19 | 20 | learn(15, [2, 10, 4, 1]) // [1, 2] -> Los cursos en [1, 2] son 14 horas, es la mejor opción. 21 | 22 | learn(25, [10, 15, 20, 5]) // [0, 1] -> los cursos [0, 1] y [2, 3] completan exactamente con 25 horas pero siempre 23 | devolvemos el primero que encontremos 24 | 25 | learn(8, [8, 2, 1]) // [1, 2] -> para hacer dos cursos, no podemos hacer el de 8 horas, así que devolvemos el de 1 y 2. 26 | 27 | learn(4, [10, 14, 20]) // null -> no nos da tiempo a hacer dos cursos 28 | learn(5, [5, 5, 5]) // null -> no nos da tiempo a hacer dos cursos 29 | */ 30 | 31 | /** 32 | * Title: Iterating every element and its successors. 33 | * Complexity: O(N log N); Ω(1) 34 | * Comment: 35 | * - In this solution I use reduction to sum the bestResult items. This list will contain, at most, 2 items, so a 36 | * classical sum will work. I do it this way because I didn't want to deal with adding undefined values. 37 | * 1. We iterate every element and its successors. By doing this we get a time complexity of O(N log N) instead of 38 | * O(N^2), because when we find the exact solution (two items who sums the exact required time) we just stop 39 | * iterating and finish the execution. 40 | * 2. I declare a variable named 'bestResult', whose initial result is an empty array. For every iteration I check if 41 | * the new pair summed is lower than the time parameter and that its a better (not equal) solution than the 42 | * previous one. If so, I update bestResult, otherwise I keep its value. 43 | * 3. As the initial result value is an empty list, and as if none of the courses suited the time bound I shall return 44 | * null, I use a ternary operator to do it. 45 | * 46 | */ 47 | export default function learn(time, courses) { 48 | let bestResult = []; 49 | for (let i = 0; i < courses.length - 1; i++) 50 | for (let j = i + 1; j < courses.length; j++) { 51 | bestResult = (courses[i] + courses[j] > time || bestResult.reduce((acc, r) => acc + courses[r], 0) > courses[i] + courses[j]) ? bestResult : [i, j]; 52 | if (bestResult.reduce((acc, r) => acc + courses[r], 0) === time) return bestResult; 53 | } 54 | return bestResult.length ? bestResult : null; 55 | } -------------------------------------------------------------------------------- /src/2021/exercices/day20.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | En la clase de español del pueblo de Laponia han creado un reto a la hora de escribir la carta a Papa Noél 🎅: la carta 4 | ✉️ tiene que contener todas las letras del alfabeto. 5 | 6 | Desde el taller de Santa 🎅 se han enterado y quieren escribir una función que les diga si realmente la cadena de texto 7 | que les llega tiene, efectivamente, todas las letras del abecedario español 🔎. 8 | 9 | Hay que tener en cuenta las letras en mayúscula y que las letras con acento y diéresis se consideran iguales. Por 10 | ejemplo la á y la ä cuenta como una a. 11 | 12 | Vamos a ver unos ejemplos de frases: 13 | 14 | pangram('Extraño pan de col y kiwi se quemó bajo fugaz vaho') // true 15 | pangram('Jovencillo emponzoñado y con walkman: ¡qué figurota exhibes!') // true 16 | 17 | pangram('Esto es una frase larga pero no tiene todas las letras del abecedario') // false 18 | pangram('De la a a la z, nos faltan letras') // false 19 | Y ya que estás... ¿Cuál es tu pangrama favorito? ¡Compártelo en nuestra comunidad de Discord! 20 | */ 21 | 22 | /** 23 | * Title: Normalizing every character and removing it from the alphabet. 24 | * Complexity: O(N); Ω(27) ~= Ω(1) 25 | * Comment: 26 | * 1. First we transform the string into a list of characters. 27 | * 2. Then we create a set including all the characters in the Spanish alphabet. The idea is to remove all the 28 | * characters of from the set and then check if the set is empty, which means the letter contained all the 29 | * characters in the spanish alphabet. 30 | * 3. On every iteration we must normalize every character, so an 'à' counts as an 'a'. WARNING: we must take caution 31 | * because with the wrong regex we would be transforming 'ñ' into 'n' and we don't want that, as we are using 32 | * Spanish alphabet and 'ñ' is an important character for Spanish speakers. We'd love to get on well with them... 33 | * 34 | * - Another option would be to normalize the letter directly, but that would mean obtaining an exact time complexity 35 | * of O(N), in other words, always iterate through all the characters in the letter during the normalization. This 36 | * way, we can return a solution as soon as we receive the 27 Spanish's alphabet characters. 37 | * - It's important to clarify that the author is not an expert on this area, so he is not sure about the performance 38 | * of normalizing a single character over the whole text. He just applied common sense. He doesn't know why he is 39 | * speaking in third person... 40 | * 4. If we get out from the loop that means the set is still not empty, so we return false. 41 | */ 42 | export default function pangram(letter) { 43 | const characters = [...letter]; 44 | const charactersSet = new Set(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'ñ', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']); 45 | for (const character of characters) { 46 | charactersSet.delete(character.toLowerCase().normalize("NFD").replace(/([aeio])\u0301|(u)[\u0301\u0308]/gi, "$1$2").normalize()); 47 | if (!charactersSet.size) return true; 48 | } 49 | return false; 50 | } -------------------------------------------------------------------------------- /src/2021/exercices/day21.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Se están preparando las rutas para el trineo de Santa 🎅. Tenemos almacenes por todo el mundo para que Santa pueda 4 | recoger los regalos y entregarlos en el destino final. 🎁 5 | 6 | Necesitamos saber si las rutas que estamos creando tienen sentido o si Santa va a tener que dejar tirados regalos por 7 | el camino. 🥺 8 | 9 | Para eso vamos a crear una función que recibe dos parámetros: 10 | 11 | Un número con la capacidad máxima de regalos en el trineo. 12 | El viaje que es un array de arrays. Cada subarray contiene tres números que representan: 13 | trip[0] = número de regalos a transportar 14 | trip[1] = punto de recogida de los regalos 15 | trip[2] = punto de entrega de los regalos 16 | La ruta siempre va de izquierda a derecha (nunca volverá Santa hacia atrás) pero... ¡ten en cuenta que en mitad de la 17 | ruta puede tener que recoger regalos cuando ya tiene alguno encima! 18 | 19 | Lo mejor es que veamos un ejemplo: 20 | 21 | canCarry(4, [[2, 5, 8], [3, 6, 10]]) // false 22 | // En el punto 5 recoge 2 regalos... 23 | // En el punto 6 recoge 3 regalos... 24 | // Del punto 6 al 8 tendría 5 regalos en total 25 | // Y su capacidad es 4... así que ¡no podría! 26 | 27 | canCarry(3, [[1, 1, 5], [2, 2, 10]]) // true 28 | // En el punto 1 recoge 1 regalo... 29 | // En el punto 2 recoge 2 regalos... 30 | // En el punto 5 entrega 1 regalo... 31 | // En el punto 10 entrega 2 regalos... 32 | // ¡Sí puede! Nunca superó la carga máxima de 3 regalos 33 | 34 | canCarry(3, [[2, 1, 5],[3, 5, 7]]) // true -> nunca supera el máximo de capacidad 35 | canCarry(4, [[2, 3, 8],[2, 5, 7]]) // true -> del punto 5 al 7 lleva 4 regalos y no supera el máximo 36 | 37 | canCarry(1, [[2, 3, 8]]) // false -> no podría ni con el primer viaje 38 | canCarry(2, [[1, 2, 4], [2, 3, 8]]) // false -> del punto 3 al 4 supera la capacidad máxima porque llevaría 3 regalos 39 | Lo difícil, e importante, es que entiendas que Santa Claus va entregando y recogiendo regalos y que a veces eso puede 40 | hacer que supere la carga máxima. 41 | */ 42 | 43 | /** 44 | * Title: Using a map to store the destinies and their expected items 45 | * Complexity: O(N); Ω(1) 46 | * Comment: 47 | * 1. We set a counter starting to 0 to count the load. 48 | * 2. We declare a map to store the destinies and their expected items 49 | * 3. We iterate the stops of the trip. First we check if there is any package for this stop, so we can lose weight. 50 | * Then we got the items that must travel from this stop to a new destiny stop. In this moment we check if the 51 | * carrying objects are OK for Santa's sleigh. If not, we return false, otherwise we add the new destiny into our 52 | * map of destinies. 53 | * 4. If we get out from the loop this means we could complete the whole trip without luggage issues. 54 | */ 55 | export default function canCarry(capacity, trip) { 56 | let carrying = 0; 57 | let destinies = new Map(); 58 | for (let stop of trip) { 59 | let [items, from, to] = stop; 60 | if (destinies.has(from)) carrying -= destinies.get(from); 61 | carrying += items; 62 | if (carrying > capacity) return false; 63 | destinies.set(to, items); 64 | } 65 | return true; 66 | } -------------------------------------------------------------------------------- /src/2021/exercices/day22.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | ¡Ay! Que llega la Navidad y no hemos decorado todavía el árbol. 🎄😱 4 | 5 | Necesitamos una función que pasándole un árbol binario nos diga el número de decoraciones que necesitamos. Para ello 6 | tenemos un objeto que sería la representación del árbol y que nos indica en cada nivel el número de ramas a decorar. 7 | 8 | Lo mejor es que veamos un ejemplo: 9 | 10 | // tenemos el árbol en forma de objeto 11 | const tree = { 12 | value: 1, // el nodo raíz siempre es uno, porque es la estrella ⭐ 13 | left: { 14 | value: 2, // el nodo izquierdo necesita dos decoraciones 15 | left: null, // no tiene más ramas 16 | right: null // no tiene más ramas 17 | }, 18 | right: { 19 | value: 3, // el nodo de la derecha necesita tres decoraciones 20 | left: null, // no tiene más ramas 21 | right: null // no tiene más ramas 22 | } 23 | } 24 | 25 | Gráficamente sería así: 26 | 1 27 | / \ 28 | 2 3 29 | 30 | 1 + 2 + 3 = 5 31 | 32 | countDecorations(tree) // 5 33 | 34 | 35 | const bigTree = { 36 | value: 1, 37 | left: { 38 | value: 5, 39 | left: { 40 | value: 7, 41 | left: { 42 | value: 3, 43 | left: null, 44 | right: null 45 | }, 46 | right: null 47 | }, 48 | right: null 49 | }, 50 | right: { 51 | value: 6, 52 | left: { 53 | value: 5, 54 | left: null, 55 | right: null 56 | }, 57 | right: { 58 | value: 1, 59 | left: null, 60 | right: null 61 | } 62 | } 63 | } 64 | 65 | 66 | 1 67 | / \ 68 | 5 6 69 | / / \ 70 | 7 5 1 71 | / 72 | 3 73 | 74 | countDecorations(bigTree) // 28 75 | 76 | Por cierto, Bellf Gates me ha contado que este tipo de ejercicio es muy típico en las entrevistas de trabajo para 77 | programadores. ¿Lo sabías? 78 | */ 79 | 80 | /** 81 | * Title: Sum all tree nodes via recursion and ternary operators 82 | * Complexity: Θ(N) 83 | * Comment: 84 | * 1. We use recursion to iterate the tree, using every subtree (left and right children) as the bigTree parameter on 85 | * every recursive call. 86 | * 2. If we don't have a bigTree on a call, or what its the same, the bigTree is null, we just return 0. 87 | */ 88 | export default function countDecorations(bigTree) { 89 | return bigTree ? bigTree.value + countDecorations(bigTree.left) + countDecorations(bigTree.right) : 0; 90 | } -------------------------------------------------------------------------------- /src/2021/exercices/day23.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Estamos en la fábrica de Santa Claus 🎅 creando regalos como si no hubiera un mañana 4 | 5 | Pensábamos que no íbamos a llegar pero Jelf Bezos ha tenido una idea genial para aprovechar las máquinas y optimizar al 6 | máximo la creación de regalos. 🎁 7 | 8 | La configuración de las máquinas es un string. Podemos reconfigurarla para que haga otro regalo y, para ello, podemos 9 | cambiar cada carácter por otro. 10 | 11 | Pero tiene limitaciones 🥲: al reemplazar el carácter se debe mantener el orden, no se puede asignar al mismo carácter 12 | a dos letras distintas (pero sí a si mismo) y, claro, la longitud del string debe ser el mismo. 13 | 14 | Necesitamos una función que nos diga si podemos reconfigurar una máquina para que de un regalo pueda pasar a fabricar 15 | otro según las reglas mencionadas. Lo mejor es que veamos un ejemplo: 16 | 17 | const from = 'BAL' 18 | const to = 'LIB' 19 | const canReconfigure(from, to) // true 20 | 21 | la transformación sería así: 22 | B -> L 23 | A -> I 24 | L -> B 25 | 26 | 27 | const from = 'CON' 28 | const to = 'JUU' 29 | const canReconfigure(from, to) // false 30 | 31 | no se puede hacer la transformación: 32 | C -> J 33 | O -> U 34 | N -> FALLO 35 | 36 | 37 | const from = 'MMM' 38 | const to = 'MID' 39 | cons canReconfigure(from, to) // false 40 | no se puede hacer la transformación: 41 | M -> M (BIEN, asigna el mismo carácter a si mismo) 42 | M -> I (FALLO, asigna el mismo carácter a dos letras distintas) 43 | M -> D (FALLO, asigna el mismo carácter a dos letras distintas) 44 | 45 | 46 | const from = 'AA' 47 | const to = 'MID' 48 | cons canReconfigure(from, to) // false -> no tiene la misma longitud 49 | */ 50 | 51 | /** 52 | * Title: Use a map as a translation table and check if there are the same existing records for different values. 53 | * Complexity: O(N); Ω(1) 54 | * Comment: 55 | * 1. First we check if we can save all the computational cost of the algorithm by checking the parameters length. If 56 | * they have different length we return false, as the instructions require. This is important for the efficiency of 57 | * our algorithm, as we can ensure more O(1) scenarios. 58 | * 2. We declare a map to store our values. The keys would be the returning character and the value the original 59 | * character. 60 | * 3. We start the iteration. As we have already check that both parameters have the same length, they will share the 61 | * same index in the itaration.We must first check if we already have any of the following conditions: 62 | * 3.1. We already have the returning character recorded and its recorded translated value is different than the 63 | * one we are checking up. 64 | * 3.2. We already have the original character recorded and its recorded returning value is different than the one 65 | * we are checking right now. 66 | * If any of these conditions is TRUE, we return false. Otherwise, we store the new recording into the map and 67 | * continue iterating. 68 | * 4. If we ever escape from the loop this will mean that we didn't found any error in our translation, so we return TRUE. 69 | */ 70 | export default function canReconfigure(from, to) { 71 | if (from.length !== to.length) return false 72 | let map = new Map(); 73 | for (let i in from) { 74 | if (map.has(to[i]) && map.get(to[i]) !== from[i] || map.has(from[i]) && map.get(from[i]) !== to[i]) 75 | return false; 76 | map.set(to[i], from[i]); 77 | map.set(from[i], to[i]); 78 | } 79 | return true; 80 | } -------------------------------------------------------------------------------- /src/2021/exercices/day24.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | El abuelo 👴 dice que ve todos los árboles de navidad iguales... La abuela 👵, en cambio, piensa que no. Que todos los 4 | árboles de navidad son distintos... 5 | 6 | Vamos a hacer una función que nos diga si dos árboles de navidad son iguales. Para ello, vamos a comparar los árboles 7 | que ya creamos en el reto 22. 8 | 9 | Tenemos que ver si ambos árboles tienen la misma estructura y los mismos valores en todas las ramas. Aquí tienes unos 10 | ejemplos: 11 | 12 | const tree = { 13 | value: 1, 14 | left: { value: 2, left: null, right: null }, 15 | right: { value: 3, left: null, right: null } 16 | } 17 | 18 | checkIsSameTree(tree, tree) // true 19 | 20 | const tree2 = { 21 | value: 1, 22 | left: { value: 3, left: { value: 2, left: null, right: null }, right: null }, 23 | right: { value: 5, left: null, right: { value: 4, left: null, right: null } } 24 | } 25 | 26 | checkIsSameTree(tree, tree2) // false 27 | checkIsSameTree(tree2, tree2) // true 28 | El cuñado 🦹‍♂️, que se las sabe todas, me ha dicho que tenga cuidado porque el truco del JSON.stringify puede no 29 | funcionar... ya que los árboles pueden ser el mismo pero el orden de representación de las ramas izquierda y derecha 30 | puede ser inversa... 31 | */ 32 | 33 | /** 34 | * Title: Traversing element by element, using recursion, to check if both trees are equuals. 35 | * Complexity: O(N); Ω(1) 36 | * Comment: 37 | * - We cannot use treeA===treeB because this comparison doesn't work for nested objects. 38 | * - We cannot use Stringify because this only gave us information about the nodes values, and not the tree's structure. 39 | * 1. First we check if both of the params are null. This means we got leaves in both trees. This condition will return True. 40 | * 2. If we aren't checking leaves, we must check if both of the values are the same, and we use recursive calls for 41 | * their left and right subtrees. This must return true. If any of these conditions fails, the trees won't be 42 | * equal. So we return the result of this logical AND's concatenation. 43 | */ 44 | export default function checkIsSameTree(treeA, treeB) { 45 | return (treeA === null && treeB === null) || (treeA?.value === treeB?.value && checkIsSameTree(treeA.left, treeB.left) && checkIsSameTree(treeA.right, treeB.right)); 46 | } 47 | 48 | /* 49 | This is a more readable code for the same solution: 50 | 51 | export default function checkIsSameTree(treeA, treeB) { 52 | if (treeA === null && treeB === null) return true; 53 | if (treeA?.value !== treeB?.value) return false; 54 | return ( 55 | checkIsSameTree(treeA.left, treeB.left) && 56 | checkIsSameTree(treeA.right, treeB.right) 57 | ); 58 | } 59 | */ 60 | 61 | -------------------------------------------------------------------------------- /src/2021/exercices/day25.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Ayer, en noche buena, una família cenó por todo lo alto... Con tanta copa 🍾 encima todavía no han retirado los platos 4 | y la comida de ayer... 5 | 6 | Un ratoncillo llamado midurat 🐭, que vió ayer el festín escondido, está relamiéndose los bigotes al ver todos los 7 | manjares que hay en el comedor. 8 | 9 | Eso sí, hay que tener cuidado 😶 y sólo hacer los movimientos correctos para comer algo. Por eso, el ratón, que se ha 10 | visto los vídeos de midudev, va a crear una función para saber si su próximo movimiento es correcto o no ✅. 11 | 12 | El ratoncillo se puede mover en 4 direcciones: up, down, left, right y el comedor es una matriz (un array de arrays) 13 | donde cada posición puede ser: 14 | 15 | Un espacio vacío es que no hay nada 16 | Una m es el ratón 17 | Un * es la comida 18 | Vamos a ver unos ejemplos: 19 | 20 | const room = [ 21 | [' ', ' ', ' '], 22 | [' ', ' ', 'm'], 23 | [' ', ' ', '*'] 24 | ] 25 | 26 | canMouseEat('up', room) // false 27 | canMouseEat('down', room) // true 28 | canMouseEat('right', room) // false 29 | canMouseEat('left', room) // false 30 | 31 | const room2 = [ 32 | ['*', ' ', ' ', ' '], 33 | [' ', 'm', '*', ' '], 34 | [' ', ' ', ' ', ' '], 35 | [' ', ' ', ' ', '*'] 36 | ] 37 | 38 | canMouseEat('up', room2) // false 39 | canMouseEat('down', room2) // false 40 | canMouseEat('right', room2) // true 41 | canMouseEat('left', room2) // false 42 | ¡Ten en cuenta que el ratón quiere buscar comida en diferentes habitaciones y que cada una puede tener dimensiones diferentes! 43 | */ 44 | 45 | /** 46 | * Title: Iterating over the matrix until find the mouse, then check the requested position. 47 | * Complexity: O(N); Ω(1) 48 | * Comment: 49 | * - Caution: accessing to a non-existing position in the matrix will make the program crash. Because of that, in the 50 | * first two scenarios, up and down, we must ensure that the existing row exists. If it does not, it may return 51 | * undefined. Tests expect a TRUE or FALSE returning, no TRUTHY or FALSE. Because of this, returning undefined 52 | * won't work, so we use the logical NULLISH operator ?? to return FALSE in these situations. 53 | */ 54 | export default function canMouseEat(direction, game) { 55 | for (let r = 0; r < game.length; r++) 56 | for (let c = 0; c < game[r].length; c++) 57 | if (game[r][c] === 'm') { 58 | if (direction === 'up') return (game[r - 1] && game[r - 1][c] === '*') ?? false; 59 | else if (direction === 'down') return (game[r + 1] && game[r + 1][c] === '*') ?? false; 60 | else if (direction === 'left') return game[r][c - 1] === '*' ?? false; 61 | else if (direction === 'right') return game[r][c + 1] === '*' ?? false; 62 | } 63 | return false; 64 | } 65 | -------------------------------------------------------------------------------- /src/2021/tests/day01.test.js: -------------------------------------------------------------------------------- 1 | import contarOvejas from '../exercices/day01' 2 | 3 | describe('Day 1 challenge', () => { 4 | test('filters sheeps by their color & name', () => { 5 | const sheeps = [ 6 | { name: 'Noa', color: 'azul' }, 7 | { name: 'Euge', color: 'rojo' }, 8 | { name: 'Navidad', color: 'rojo' }, 9 | { name: 'Ki Na Ma', color: 'rojo' }, 10 | { name: 'AAAAAaaaaa', color: 'rojo' }, 11 | { name: 'Nnnnnnnn', color: 'rojo' } 12 | ] 13 | 14 | expect(contarOvejas(sheeps)).toEqual([ 15 | { name: 'Navidad', color: 'rojo' }, 16 | { name: 'Ki Na Ma', color: 'rojo' }, 17 | ]) 18 | }) 19 | }) -------------------------------------------------------------------------------- /src/2021/tests/day02.test.js: -------------------------------------------------------------------------------- 1 | import listGifts from '../exercices/day02' 2 | 3 | describe('Day 2 challenge', () => { 4 | test('Group and filter gifts letter', () => { 5 | const letter = 'bici coche balón _playstation bici coche peluche'; 6 | 7 | expect(listGifts(letter)).toEqual({ 8 | bici: 2, 9 | coche: 2, 10 | balón: 1, 11 | peluche: 1 12 | }) 13 | }) 14 | }) -------------------------------------------------------------------------------- /src/2021/tests/day03.test.js: -------------------------------------------------------------------------------- 1 | import isValid from '../exercices/day03' 2 | 3 | describe('Day 3 challenge', () => { 4 | test('is valid letter without parenthesis', () => { 5 | expect(isValid('bici coche peluche')).toBeTruthy() 6 | }) 7 | 8 | test('is valid letter with one parenthesis', () => { 9 | expect(isValid('bici coche (balón) peluche')).toBeTruthy() 10 | }) 11 | 12 | test('is valid letter with two parenthesis', () => { 13 | expect(isValid('bici coche (balón) tren (moto) peluche')).toBeTruthy() 14 | }) 15 | 16 | test('is valid letter starting with parenthesis', () => { 17 | expect(isValid('(bici) coche peluche')).toBeTruthy() 18 | }) 19 | 20 | test('is valid letter finishing with parenthesis', () => { 21 | expect(isValid('bici coche (peluche)')).toBeTruthy() 22 | }) 23 | 24 | test('is invalid letter with empty parenthesis', () => { 25 | expect(isValid('bici () peluche')).toBeFalsy() 26 | }) 27 | 28 | test('is invalid letter with unclosed parenthesis', () => { 29 | expect(isValid('bici (coche peluche')).toBeFalsy() 30 | expect(isValid('bici (coche) (peluche')).toBeFalsy() 31 | }) 32 | 33 | test('is invalid letter with invalid chracters', () => { 34 | expect(isValid('bici (tren [coche) peluche')).toBeFalsy() 35 | expect(isValid('bici (tren ]coche) peluche')).toBeFalsy() 36 | expect(isValid('bici (tren {coche) peluche')).toBeFalsy() 37 | expect(isValid('bici (tren }coche) peluche')).toBeFalsy() 38 | }) 39 | 40 | // test('is invalid letter with unpair parenthesis', () => { 41 | // expect(isValid('bici coche (tren (moto) peluche')).toBeFalsy() 42 | // expect(isValid('bici (coche) (tren (moto) peluche')).toBeFalsy() 43 | // }) 44 | }) -------------------------------------------------------------------------------- /src/2021/tests/day04.test.js: -------------------------------------------------------------------------------- 1 | import createXmasTree from '../exercices/day04' 2 | 3 | describe('Day 4 challenge', () => { 4 | test('Create tree height 5', () => { 5 | expect(createXmasTree(5)).toEqual('____*____\n' + 6 | '___***___\n' + 7 | '__*****__\n' + 8 | '_*******_\n' + 9 | '*********\n' + 10 | '____#____\n' + 11 | '____#____') 12 | }); 13 | test('Create tree height 3', () => { 14 | expect(createXmasTree(3)).toEqual('__*__\n' + 15 | '_***_\n' + 16 | '*****\n' + 17 | '__#__\n' + 18 | '__#__') 19 | }); 20 | }) -------------------------------------------------------------------------------- /src/2021/tests/day05.test.js: -------------------------------------------------------------------------------- 1 | import daysToXmas from '../exercices/day05' 2 | 3 | describe('Day 5 challenge\'', () => { 4 | test('First of same month same year', () => { 5 | expect(daysToXmas(new Date('Dec 1, 2021'))).toEqual(24) 6 | }); 7 | test('Day before', () => { 8 | expect(daysToXmas(new Date('Dec 24, 2021 00:00:01'))).toEqual(1) 9 | }); 10 | test('Second before', () => { 11 | expect(daysToXmas(new Date('Dec 24, 2021 23:59:59'))).toEqual(1) 12 | }); 13 | test('Same month, same year previous day', () => { 14 | expect(daysToXmas(new Date("December 20, 2021 03:24:00"))).toEqual(5) 15 | }); 16 | test('The same day', () => { 17 | expect(daysToXmas(new Date('Dec 25, 2021'))).toEqual(0) 18 | }); 19 | test('Next day', () => { 20 | expect(daysToXmas(new Date('Dec 26, 2021'))).toEqual(-1) 21 | }); 22 | test('Las day of the same year', () => { 23 | expect(daysToXmas(new Date('Dec 31, 2021'))).toEqual(-6) 24 | }); 25 | test('First second of the next year', () => { 26 | expect(daysToXmas(new Date('Jan 1, 2022 00:00:01'))).toEqual(-7) 27 | }); 28 | test('Last second of the first day of the next year', () => { 29 | expect(daysToXmas(new Date('Jan 1, 2022 23:59:59'))).toEqual(-7) 30 | }); 31 | }); -------------------------------------------------------------------------------- /src/2021/tests/day06.test.js: -------------------------------------------------------------------------------- 1 | import sumPairs from '../exercices/day06' 2 | 3 | describe('Day 6 challenge', () => { 4 | test('First and last', () => { 5 | expect(sumPairs([3, 5, 7, 2], 10)).toEqual([3, 7]) 6 | }); 7 | test('None', () => { 8 | expect(sumPairs([-3, -2, 7, -5], 10)).toBeNull() 9 | }); 10 | test('First two', () => { 11 | expect(sumPairs([2, 2, 3, 1], 4)).toEqual([2, 2]) 12 | }); 13 | test('First and last Pt. 2', () => { 14 | expect(sumPairs([6, 7, 1, 2], 8)).toEqual([6, 2]) 15 | }); 16 | test('Last two Pt. 2', () => { 17 | expect(sumPairs([0, 2, 2, 3, -1, 1, 5], 6)).toEqual([1, 5]) 18 | }); 19 | }) -------------------------------------------------------------------------------- /src/2021/tests/day07.test.js: -------------------------------------------------------------------------------- 1 | import contains from '../exercices/day07' 2 | 3 | describe('Day 7 challenge', () => { 4 | test('Existing result in 3 level depth', () => { 5 | const almacen = { 6 | 'estanteria1': { 7 | 'cajon1': { 8 | 'producto1': 'coca-cola', 9 | 'producto2': 'fanta', 10 | 'producto3': 'sprite' 11 | } 12 | }, 13 | 'estanteria2': { 14 | 'cajon1': 'vacio', 15 | 'cajon2': { 16 | 'producto1': 'pantalones', 17 | 'producto2': 'camiseta' // <- ¡Está aquí! 18 | } 19 | } 20 | } 21 | expect(contains(almacen, 'camiseta')).toBeTruthy(); 22 | }); 23 | test('No existing item', () => { 24 | const otroAlmacen = { 25 | 'baul': { 26 | 'fondo': { 27 | 'objeto': 'cd-rom', 28 | 'otro-objeto': 'disquette', 29 | 'otra-cosa': 'mando' 30 | } 31 | } 32 | } 33 | expect(contains(otroAlmacen, 'gameboy')).toBeFalsy(); 34 | }); 35 | }) -------------------------------------------------------------------------------- /src/2021/tests/day08.test.js: -------------------------------------------------------------------------------- 1 | import maxProfit from '../exercices/day08' 2 | 3 | describe('Day 8 challenge', () => { 4 | test('The price varies along the day', () => { 5 | expect(maxProfit([39, 18, 29, 25, 34, 32, 5])).toEqual(16); 6 | }); 7 | test('Every hour the price is higher', () => { 8 | expect(maxProfit([10, 20, 30, 40, 50, 60, 70])).toEqual(60); 9 | }); 10 | test('Every hour the price is lower: Impossible profit', () => { 11 | expect(maxProfit([18, 15, 12, 11, 9, 7])).toEqual(-1); 12 | }); 13 | test('The price is inmutable: Impossible profit', () => { 14 | expect(maxProfit([3, 3, 3, 3, 3])).toEqual(-1); 15 | }); 16 | }) -------------------------------------------------------------------------------- /src/2021/tests/day09.test.js: -------------------------------------------------------------------------------- 1 | import groupBy from '../exercices/day09' 2 | 3 | describe('Day 9 challenge', () => { 4 | test('Applying function', () => { 5 | expect(groupBy([6.1, 4.2, 6.3], Math.floor)).toEqual({ 6: [6.1, 6.3], 4: [4.2] }); 6 | }); 7 | test("Applying prototype's function", () => { 8 | expect(groupBy(['one', 'two', 'three'], 'length')).toEqual({ 3: ['one', 'two'], 5: ['three'] }); 9 | }); 10 | test("Grouping by object's property to only-one-value objects", () => { 11 | expect(groupBy([{ age: 23 }, { age: 24 }], 'age')).toEqual({ 23: [{ age: 23 }], 24: [{ age: 24 }] }); 12 | }); 13 | test("Applying arrow function", () => { 14 | expect(groupBy( 15 | [1397639141184, 1363223700000], 16 | timestamp => new Date(timestamp).getFullYear() 17 | )).toEqual({ 2013: [1363223700000], 2014: [1397639141184] }); 18 | }); 19 | test("Grouping by object's property to objects with multiple properties", () => { 20 | expect(groupBy([ 21 | { title: 'JavaScript: The Good Parts', rating: 8 }, 22 | { title: 'Aprendiendo Git', rating: 10 }, 23 | { title: 'Clean Code', rating: 9 }, 24 | ], 'rating')).toEqual({ 25 | 8: [{ title: 'JavaScript: The Good Parts', rating: 8 }], 26 | 9: [{ title: 'Clean Code', rating: 9 }], 27 | 10: [{ title: 'Aprendiendo Git', rating: 10 }] 28 | }); 29 | }); 30 | }) -------------------------------------------------------------------------------- /src/2021/tests/day10.test.js: -------------------------------------------------------------------------------- 1 | import getCoins from '../exercices/day10' 2 | 3 | describe('Day 10 challenge', () => { 4 | test('51 cents', () => { 5 | expect(getCoins(51)).toEqual([1, 0, 0, 0, 0, 1]); 6 | }); 7 | test('3 cents', () => { 8 | expect(getCoins(3)).toEqual([1, 1, 0, 0, 0, 0]); 9 | }); 10 | test('5 cents', () => { 11 | expect(getCoins(5)).toEqual([0, 0, 1, 0, 0, 0]); 12 | }); 13 | test('16 cents', () => { 14 | expect(getCoins(16)).toEqual([1, 0, 1, 1, 0, 0]); 15 | }); 16 | test('1 euro (100 cents)', () => { 17 | expect(getCoins(100)).toEqual([0, 0, 0, 0, 0, 2]); 18 | }); 19 | }) -------------------------------------------------------------------------------- /src/2021/tests/day11.test.js: -------------------------------------------------------------------------------- 1 | import shouldBuyFidelity from '../exercices/day11' 2 | 3 | describe('Day 11 challenge', () => { 4 | test('1 ticket', () => { 5 | expect(shouldBuyFidelity(1)).toBeFalsy(); 6 | }); 7 | test('100 tickets', () => { 8 | expect(shouldBuyFidelity(100)).toBeTruthy(); 9 | }); 10 | }) -------------------------------------------------------------------------------- /src/2021/tests/day12.test.js: -------------------------------------------------------------------------------- 1 | import getMinJump from '../exercices/day12' 2 | 3 | describe('Day 12 challenge', () => { 4 | test('Unsorted obstacles: medium jump', () => { 5 | expect(getMinJump([5, 3, 6, 7, 9])).toEqual(4); 6 | }); 7 | test('Unsorted obstacles: big jump', () => { 8 | expect(getMinJump([2, 4, 6, 8, 10])).toEqual(7); 9 | }); 10 | test('Ascending sorted obstacles', () => { 11 | expect(getMinJump([1, 2, 3, 5])).toEqual(4); 12 | }); 13 | test('Unsorted obstacles: small jump', () => { 14 | expect(getMinJump([3, 7, 5])).toEqual(2); 15 | }); 16 | test('Descending sorted obstacles', () => { 17 | expect(getMinJump([9, 5, 1])).toEqual(2); 18 | }); 19 | }) -------------------------------------------------------------------------------- /src/2021/tests/day13.test.js: -------------------------------------------------------------------------------- 1 | import wrapGifts from '../exercices/day13' 2 | 3 | describe('Day 13 challenge', () => { 4 | test('Two single gifts', () => { 5 | expect(wrapGifts(["📷", "⚽️"])).toEqual(['****', 6 | '*📷*', 7 | '*⚽️*', 8 | '****' 9 | ]); 10 | }); 11 | test('Two double gifts', () => { 12 | expect(wrapGifts(["🏈🎸", "🎮🧸"])).toEqual(['******', 13 | '*🏈🎸*', 14 | '*🎮🧸*', 15 | '******' 16 | ]); 17 | }); 18 | test('One single gift', () => { 19 | expect(wrapGifts(["📷"])).toEqual(['****', 20 | '*📷*', 21 | '****' 22 | ]); 23 | }); 24 | }) -------------------------------------------------------------------------------- /src/2021/tests/day14.test.js: -------------------------------------------------------------------------------- 1 | import missingReindeer from '../exercices/day14' 2 | 3 | describe('Day 14 challenge', () => { 4 | test('Missing half with a few sorted items', () => { 5 | expect(missingReindeer([0, 2, 3])).toEqual(1); 6 | }); 7 | test('Missing half with lots of unsorted items', () => { 8 | expect(missingReindeer([5, 6, 1, 2, 3, 7, 0])).toEqual(4); 9 | }); 10 | test('Missing last with a few sorted items Pt.2', () => { 11 | expect(missingReindeer([0, 1])).toEqual(2); 12 | }); 13 | test('Missing half with a few unsorted items', () => { 14 | expect(missingReindeer([3, 0, 1])).toEqual(2); 15 | }); 16 | test('Missing previous to last with a lot of unsorted items', () => { 17 | expect(missingReindeer([9, 2, 3, 5, 6, 4, 7, 0, 1])).toEqual(8); 18 | }); 19 | test('Missing last with only one item', () => { 20 | expect(missingReindeer([0])).toEqual(1); 21 | }); 22 | }) -------------------------------------------------------------------------------- /src/2021/tests/day15.test.js: -------------------------------------------------------------------------------- 1 | import checkSledJump from '../exercices/day15' 2 | 3 | describe('Day 15 challenge', () => { 4 | test('Missing half with a few sorted items', () => { 5 | expect(checkSledJump([1, 2, 3, 2, 1])).toBeTruthy(); 6 | }); 7 | test('Missing half with lots of unsorted items', () => { 8 | expect(checkSledJump([0, 1, 0])).toBeTruthy(); 9 | }); 10 | test('Missing last with a few sorted items Pt.2', () => { 11 | expect(checkSledJump([0, 3, 2, 1])).toBeTruthy(); 12 | }); 13 | test('Missing half with a few unsorted items', () => { 14 | expect(checkSledJump([0, 1000, 1])).toBeTruthy(); 15 | }); 16 | test('Missing previous to last with a lot of unsorted items', () => { 17 | expect(checkSledJump([2, 4, 4, 6, 2])).toBeFalsy(); 18 | }); 19 | test('Missing last with only one item', () => { 20 | expect(checkSledJump([1, 2, 3])).toBeFalsy(); 21 | }); 22 | test('Missing last with only one item', () => { 23 | expect(checkSledJump([1, 2, 3, 2, 1, 2, 3])).toBeFalsy(); 24 | }); 25 | }) -------------------------------------------------------------------------------- /src/2021/tests/day16.test.js: -------------------------------------------------------------------------------- 1 | import decodeNumber from '../exercices/day16' 2 | 3 | describe('Day 16 challenge', () => { 4 | test('Same symbol 3 times', () => { 5 | expect(decodeNumber('...')).toEqual(3); 6 | }); 7 | test('Two symbols => subtraction', () => { 8 | expect(decodeNumber('.,')).toEqual(4); 9 | }); 10 | test('Two symbols => NO subtraction', () => { 11 | expect(decodeNumber(',.')).toEqual(6); 12 | }); 13 | test('4 Symbols', () => { 14 | expect(decodeNumber(',...')).toEqual(8); 15 | }); 16 | test('A lot of the same symbol and a higher symbol at the end', () => { 17 | expect(decodeNumber('.........!')).toEqual(107); 18 | }); 19 | test('Two symbol: lower and much higher => subtraction', () => { 20 | expect(decodeNumber('.;')).toEqual(49); 21 | }); 22 | test('Thee symbols', () => { 23 | expect(decodeNumber('..,')).toEqual(5); 24 | }); 25 | test('A bit of everything but repeating', () => { 26 | expect(decodeNumber('..,!')).toEqual(95); 27 | }); 28 | test('A bit of everything without repeating', () => { 29 | expect(decodeNumber('.;!')).toEqual(49); 30 | }); 31 | test('Same symbol 3 times with high value', () => { 32 | expect(decodeNumber('!!!')).toEqual(300); 33 | }); 34 | test('Different small symbols form a higher value with a the resulting value of a higher symbol', () => { 35 | expect(decodeNumber(';!')).toEqual(50); 36 | }); 37 | test('Unknown symbol at the end of the string', () => { 38 | expect(decodeNumber(';.W')).toEqual(NaN); 39 | }); 40 | }) -------------------------------------------------------------------------------- /src/2021/tests/day17.test.js: -------------------------------------------------------------------------------- 1 | import countPackages from '../exercices/day17' 2 | 3 | describe('Day 17 challenge', () => { 4 | test('Same symbol 3 times', () => { 5 | const carriers = [ 6 | ['dapelu', 5, ['midu', 'jelowing']], 7 | ['midu', 2, []], 8 | ['jelowing', 2, []] 9 | ] 10 | expect(countPackages(carriers, 'dapelu')).toEqual(9); 11 | }); 12 | test('Two symbols => subtraction', () => { 13 | const carriers = [ 14 | ['lolivier', 8, ['camila', 'jesuspoleo']], 15 | ['camila', 5, ['sergiomartinez', 'conchaasensio']], 16 | ['jesuspoleo', 4, []], 17 | ['sergiomartinez', 4, []], 18 | ['conchaasensio', 3, ['facundocapua', 'faviola']], 19 | ['facundocapua', 2, []], 20 | ['faviola', 1, []] 21 | ] 22 | expect(countPackages(carriers, 'camila')).toEqual(15); 23 | }); 24 | }) -------------------------------------------------------------------------------- /src/2021/tests/day18.test.js: -------------------------------------------------------------------------------- 1 | import fixFiles from '../exercices/day18' 2 | 3 | describe('Day 18 challenge', () => { 4 | test('Items without parenthesis but with repetitions', () => { 5 | const files = ['photo', 'postcard', 'photo', 'photo', 'video'] 6 | expect(fixFiles(files)).toEqual(['photo', 'postcard', 'photo(1)', 'photo(2)', 'video']); 7 | }); 8 | test('Items without parenthesis but with multiple repetitions', () => { 9 | const files2 = ['file', 'file', 'file', 'game', 'game'] 10 | expect(fixFiles(files2)).toEqual(['file', 'file(1)', 'file(2)', 'game', 'game(1)']); 11 | }); 12 | test('Items with parenthesis but with multiple repetitions', () => { 13 | const files3 = ['file', 'file(1)', 'icon', 'icon(1)', 'icon(1)'] 14 | expect(fixFiles(files3)).toEqual(['file', 'file(1)', 'icon', 'icon(1)', 'icon(1)(1)']); 15 | }); 16 | }) -------------------------------------------------------------------------------- /src/2021/tests/day19.test.js: -------------------------------------------------------------------------------- 1 | import learn from '../exercices/day19' 2 | 3 | describe('Day 19 challenge', () => { 4 | test('The first and the third courses are the solution', () => { 5 | expect(learn(10, [2, 3, 8, 1, 4])).toEqual([0, 2]); 6 | }); 7 | test('The middle courses are the solution', () => { 8 | expect(learn(15, [2, 10, 4, 1])).toEqual([1, 2]); 9 | }); 10 | test('The first two courses are the solution', () => { 11 | expect(learn(25, [10, 15, 20, 5])).toEqual([0, 1]); 12 | }); 13 | test('The first course duration is the same as the requested one, so wo need other combination', () => { 14 | expect(learn(8, [8, 2, 1])).toEqual([1, 2]); 15 | }); 16 | test('Last two courses', () => { 17 | expect(learn(8, [8, 2, 1, 4, 3])).toEqual([3, 4]); 18 | }); 19 | test('All courses has a longer duration than the requested one', () => { 20 | expect(learn(4, [10, 14, 20])).toBeNull(); 21 | }); 22 | test('All courses has the same duration, impossible to match with other courses', () => { 23 | expect(learn(5, [5, 5, 5])).toBeNull(); 24 | }); 25 | }) -------------------------------------------------------------------------------- /src/2021/tests/day20.test.js: -------------------------------------------------------------------------------- 1 | import pangram from '../exercices/day20' 2 | 3 | describe('Day 20 challenge', () => { 4 | test("Sentence with the whole alphabet, with accents and 'ñ'", () => { 5 | expect(pangram('Extraño pan de col y kiwi se quemó bajo fugaz vaho')).toBeTruthy(); 6 | }); 7 | test('Sentence with the whole alphabet and other characters', () => { 8 | expect(pangram('Jovencillo emponzoñado y con walkman: ¡qué figurota exhibes!')).toBeTruthy(); 9 | }); 10 | test("Sentence without some letters, such as 'ñ'", () => { 11 | expect(pangram('Esto es una frase larga pero no tiene todas las letras del abecedario')).toBeFalsy(); 12 | }); 13 | test("Sentence with the lack of multiple letters", () => { 14 | expect(pangram('De la a a la z, nos faltan letras')).toBeFalsy(); 15 | }); 16 | }) -------------------------------------------------------------------------------- /src/2021/tests/day21.test.js: -------------------------------------------------------------------------------- 1 | import canCarry from '../exercices/day21' 2 | 3 | describe('Day 21 challenge', () => { 4 | test("Cannot go to the first destiny because on the second stop the weight is to high", () => { 5 | expect(canCarry(4, [[2, 5, 8], [3, 6, 10]])).toBeFalsy(); 6 | }); 7 | test('Equal but not higher than capacity', () => { 8 | expect(canCarry(3, [[1, 1, 5], [2, 2, 10]])).toBeTruthy(); 9 | }); 10 | test("We just deliver enough packages in the stop where the capacity after picking up its would be too much.", () => { 11 | expect(canCarry(3, [[2, 1, 5], [3, 5, 7]])).toBeTruthy(); 12 | }); 13 | test("It never passes the limits", () => { 14 | expect(canCarry(4, [[2, 3, 8], [2, 5, 7]])).toBeTruthy(); 15 | }); 16 | test("We cannot pass form the first stop", () => { 17 | expect(canCarry(1, [[2, 3, 8]])).toBeFalsy(); 18 | }); 19 | test("We cannot finish our trip on the last stop", () => { 20 | expect(canCarry(2, [[1, 2, 4], [2, 3, 8]])).toBeFalsy(); 21 | }); 22 | }) -------------------------------------------------------------------------------- /src/2021/tests/day22.test.js: -------------------------------------------------------------------------------- 1 | import countDecorations from '../exercices/day22' 2 | 3 | describe('Day 22 challenge', () => { 4 | test('Small tree', () => { 5 | const tree = { 6 | value: 1, // el nodo raíz siempre es uno, porque es la estrella ⭐ 7 | left: { 8 | value: 2, // el nodo izquierdo necesita dos decoraciones 9 | left: null, // no tiene más ramas 10 | right: null // no tiene más ramas 11 | }, 12 | right: { 13 | value: 3, // el nodo de la derecha necesita tres decoraciones 14 | left: null, // no tiene más ramas 15 | right: null // no tiene más ramas 16 | } 17 | } 18 | expect(countDecorations(tree)).toEqual(6); 19 | }); 20 | test('Bigger tree', () => { 21 | const bigTree = { 22 | value: 1, 23 | left: { 24 | value: 5, 25 | left: { 26 | value: 7, 27 | left: { 28 | value: 3, 29 | left: null, 30 | right: null 31 | }, 32 | right: null 33 | }, 34 | right: null 35 | }, 36 | right: { 37 | value: 6, 38 | left: { 39 | value: 5, 40 | left: null, 41 | right: null 42 | }, 43 | right: { 44 | value: 1, 45 | left: null, 46 | right: null 47 | } 48 | } 49 | } 50 | expect(countDecorations(bigTree)).toEqual(28) 51 | }); 52 | }) -------------------------------------------------------------------------------- /src/2021/tests/day23.test.js: -------------------------------------------------------------------------------- 1 | import canReconfigure from '../exercices/day23' 2 | 3 | describe('Day 23 challenge', () => { 4 | test('Correct translation even having most of the characters on both parameters ', () => { 5 | expect(canReconfigure('BAL', 'LIB')).toBeTruthy(); 6 | }); 7 | test("Repeated destiny character for different origin's characters", () => { 8 | expect(canReconfigure('CON', 'JUU')).toBeFalsy(); 9 | }); 10 | test("Origin presents the same character for 3 different destiny's characters", () => { 11 | expect(canReconfigure('MMM', 'MID')).toBeFalsy(); 12 | }); 13 | test("Different lengths although there is no controversial character", () => { 14 | expect(canReconfigure('AA', 'MID')).toBeFalsy(); 15 | }); 16 | }) -------------------------------------------------------------------------------- /src/2021/tests/day24.test.js: -------------------------------------------------------------------------------- 1 | import checkIsSameTree from '../exercices/day24' 2 | 3 | describe('Day 24 challenge', () => { 4 | const tree = { 5 | value: 1, 6 | left: { value: 2, left: null, right: null }, 7 | right: { value: 3, left: null, right: null } 8 | } 9 | test('Same object in parameters', () => { 10 | expect(checkIsSameTree(tree, tree)).toBeTruthy(); 11 | }); 12 | 13 | const tree2 = { 14 | value: 1, 15 | left: { value: 3, left: { value: 2, left: null, right: null }, right: null }, 16 | right: { value: 5, left: null, right: { value: 4, left: null, right: null } } 17 | } 18 | test("Different trees (same root but different children)", () => { 19 | expect(checkIsSameTree(tree, tree2)).toBeFalsy(); 20 | }); 21 | test("Same object in parameters", () => { 22 | expect(checkIsSameTree(tree2, tree2)).toBeTruthy(); 23 | }); 24 | }) -------------------------------------------------------------------------------- /src/2021/tests/day25.test.js: -------------------------------------------------------------------------------- 1 | import canMouseEat from '../exercices/day25' 2 | 3 | describe('Day 25 challenge', () => { 4 | const room = [ 5 | [' ', ' ', ' '], 6 | [' ', ' ', 'm'], 7 | [' ', ' ', '*'] 8 | ]; 9 | 10 | test('UP - Blank space', () => { 11 | expect(canMouseEat('up', room)).toEqual(false); 12 | }); 13 | test("DOWN - Success", () => { 14 | expect(canMouseEat('down', room)).toEqual(true); 15 | }); 16 | test("Right - Outbounds", () => { 17 | expect(canMouseEat('right', room)).toEqual(false); 18 | }); 19 | test("LEFT- Blank space", () => { 20 | expect(canMouseEat('left', room)).toEqual(false); 21 | }); 22 | 23 | const room2 = [ 24 | ['*', ' ', ' ', ' '], 25 | [' ', 'm', '*', ' '], 26 | [' ', ' ', ' ', ' '], 27 | [' ', ' ', ' ', '*'] 28 | ]; 29 | test('UP - Blank space', () => { 30 | expect(canMouseEat('up', room2)).toEqual(false); 31 | }); 32 | test("DOWN - Blank space", () => { 33 | expect(canMouseEat('down', room2)).toEqual(false); 34 | }); 35 | test("Right - Success", () => { 36 | expect(canMouseEat('right', room2)).toEqual(true); 37 | }); 38 | test("LEFT- Blank space", () => { 39 | expect(canMouseEat('left', room2)).toEqual(false); 40 | }); 41 | }) -------------------------------------------------------------------------------- /src/2022/exercices/day01.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Este año los elfos han comprado una máquina que envuelve regalos. Pero… ¡no viene programada! Necesitamos crear 4 | un algoritmo que le ayude en la tarea. 5 | 6 | A la máquina se le pasa un array con los regalos. Cada regalo es un string. Necesitamos que la máquina envuelva 7 | cada regalo en papel de regalo y lo coloque en un array de regalos envueltos. 8 | 9 | El papel de regalo es el símbolo * y para envolver un regalo se coloca el símbolo * de forma que rodee totalmente 10 | al string por todos los lados. Por ejemplo: 11 | 12 | const gifts = ['cat', 'game', 'socks'] 13 | const wrapped = wrapping(gifts) 14 | 15 | console.log(wrapped) 16 | [ 17 | "*****\\n*cat*\\n*****", 18 | "******\\n*game*\\n******", 19 | "*******\\n*socks*\\n*******" 20 | ] 21 | 22 | Como ves, el papel de regalo envuelve el string. Por arriba y por abajo, para no dejar ningún hueco, las esquinas 23 | también están cubiertas por el papel de regalo. 24 | 25 | Nota: El carácter \n representa un salto de línea. 26 | 27 | ¡Ojo! Asegúrate que pones el número correcto de * para envolver completamente el string. Pero no demasiados. Sólo 28 | los necesarios para cubrir el string. 29 | 30 | Ah, y no modifiques (mutes) el array original. 31 | */ 32 | 33 | /** 34 | * 35 | * Explicación: 36 | * - Cada regalo es un string, y tenemos que 'envolver' ese string en asteriscos. 37 | * - Para ello necesitamos saber cuánto ocupa ese string en horizontal. 38 | * - La palabra 'sopa' queraría así: 39 | * ****** 40 | * *sopa* 41 | * ****** 42 | * - Es decir, si la longitud de la palabra es n, necesitamos una capa superior de n+2 asteriscos, una capa inferior de 43 | * n+2 asteriscos y luego poner un par de asteriscos a cada lado de la palabra. 44 | * - Podemos lograr esto dinámicamente y muy fácilmente mediante String.prototype.repeat junto con los Template Literals 45 | * (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals). 46 | */ 47 | export default function wrapping(gifts) { 48 | return gifts.map(gift => `${'*'.repeat(gift.length + 2)}\n*${gift}*\n${'*'.repeat(gift.length + 2)}`); 49 | } 50 | -------------------------------------------------------------------------------- /src/2022/exercices/day02.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Un millonario ha comprado una red social y no trae buenas noticias. Ha anunciado que cada vez que una jornada 4 | de trabajo se pierde por un día festivo, habrá que compensarlo con dos horas extra otro día de ese mismo año. 5 | 6 | Obviamente la gente que trabaja en la empresa no le ha hecho ni pizca de gracia y están preparando un programa 7 | que les diga el número de horas extras que harían en el año si se aplicara la nueva norma. 8 | 9 | Al ser trabajo de oficina, su horario laboral es de lunes a viernes. Así que sólo tienes que preocuparte de 10 | los días festivos que caen en esos días. 11 | 12 | Dado un año y un array con las fechas de los días festivos, devuelve el número de horas extra que se harían 13 | ese año: 14 | 15 | const year = 2022 16 | const holidays = ['01/06', '04/01', '12/25'] // formato MM/DD 17 | 18 | // 01/06 es el 6 de enero, jueves. Cuenta. 19 | // 04/01 es el 1 de abril, un viernes. Cuenta. 20 | // 12/25 es el 25 de diciembre, un domingo. No cuenta. 21 | 22 | countHours(year, holidays) // 2 días -> 4 horas extra en el año 23 | Cosas a tener en cuenta y consejos: 24 | 25 | El año puede ser bisiesto. Haz las comprobaciones que necesitas para ello, si fuese necesario. 26 | Aunque el 31 de diciembre sea festivo, las horas extra se harán el mismo año y no el siguiente. 27 | El método Date.getDay() te devuelve el día de la semana de una fecha. El 0 es domingo, el 1 es lunes, etc. 28 | */ 29 | 30 | /** 31 | * 32 | * Explicación: 33 | * - El constructor de Date permite crear un objeto de tipo Date recibiendo un string en formato mes/día/año. 34 | * - Como recibimos las vacaciones en formato mes/día, y también recibimos el año como parámetro, podemos construir 35 | * las fechas en formato Date muy fácilmente. Para ello usamomos los String Templates.\ 36 | * - Una vez tenemos nuestro array de fechas, debemos quitar aquellas que caen en fin de semana. 37 | * Para hacer tal cosa utilizamos el método Date.prototype.getDate, que devuelve el índice del día de la semana, 38 | * pero en formato americano, es decir, 0 es domingo, 1 es lunes,... 6 es sábado. 39 | * Para simplificarnos la vida nos viene mejor trabajar en el formato internacional, es decir, estableciendo el 40 | * lunes como primer día de la semana y al domingo como el último. Para ello sumamos 6 para "avanzar" 6 días y 41 | * darle la vuelta a la semana, para finalmente hacer el módulo con los 7 días existentes en la semana. 42 | * Por ejemplo, si estamos en domingo, día 0, nos saldría 6, y 6 módulo 7 es 6, que corresponde al último día de 43 | * la semana (recuerda que trabajamos de 0 a 6). Si estuviésemos en jueves, día 4, al sumar 6 nos daría 10, pero al 44 | * hacer el módulo nos daría 3 (cuarto día de la semana empezando desde 0). 45 | * - Una vez aplicada esta operación, nos quedamos con aquellos días menores que 5, es decir, sábado, sexto día de la semana. 46 | * - Por último, aplicamos un reduce donde simplemente sumamos 2 por cada elemento restante en el array. 47 | */ 48 | export default function countHours(year, holidays) { 49 | return holidays 50 | .map((holiday) => new Date(`${holiday}/${year}`)) 51 | .filter((holiday) => (holiday.getDay() + 6) % 7 < 5) 52 | .reduce((acc) => acc + 2, 0); 53 | } -------------------------------------------------------------------------------- /src/2022/exercices/day03.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | You receive a Christmas gifts pack that Santa Claus wants to deliver to the children. 4 | Each gift inside the pack is represented by a string and it has a weight equal to the 5 | number of letters in its name. Santa Claus's sleigh can only carry a weight limit. 6 | 7 | Santa Claus also has a list of reindeer able to help him to deliver the gifts. Each 8 | reindeer has a maximum weight limit that it can carry. The maximum weight limit of 9 | each reindeer is equal to twice the number of letters in its name. 10 | 11 | Your task is to implement a function distributeGifts(packOfGifts, reindeers) that 12 | receives a gifts pack and a list of reindeer and returns the maximum number of gifts 13 | packs that Santa Claus can deliver. You can't split gifts packs. 14 | 15 | const packOfGifts = ["book", "doll", "ball"] 16 | const reindeers = ["dasher", "dancer"] 17 | 18 | // pack weights 4 + 4 + 4 = 12 19 | // reindeers can carry (2 * 6) + (2 * 6) = 24 20 | distributeGifts(packOfGifts, reindeers) // 2 21 | Things to keep in mind: 22 | 23 | The gifts pack can't be splitted. 24 | Gifts and reindeers' names length will always be greater than 0. 25 | */ 26 | 27 | /** 28 | * 29 | * Explicación: 30 | * - Necesitamos calcular cuanto pesa cada pack de regalos, para ello contamos 31 | * las letras de cada palabra y sumamos la lista resultante. 32 | * - Lo mismo para la fuerza de trineo que tenemos, pero multiplicamos la fuerza 33 | * de cada reno por dos, tal y como indican las instrucciones. 34 | * - Nótese que es lo mismo multiplicar todos los elementos por dos, y luego 35 | * sumarlos, que sumarlos y la suma final multiplicarla por dos (propiedad distributiva). 36 | * - Nótese también que ambas funciones que usamos para tanto calcular el peso del pack de 37 | * regalos como para obtener la fuerza del trineo son práctiacamente idénticas y que se 38 | * podría extraer esa lógica para no repetir código. En el contexto de este ejercicio no 39 | * lo vi necesario. 40 | * - Finalmente dividimos la fuerza total que tenemos entre lo que pesa cada pack de regalos 41 | * y nos quedamos con el cociente (ignoramos el resto, pues los packs de regalo no se 42 | * pueden dividir). Una forma muy sencilla de hacer eso es redondear por abajo con 43 | * Math.prototype.floor. 44 | 45 | */ 46 | export default function distributeGifts(packOfGifts, reindeers) { 47 | const giftPackWeight = packOfGifts 48 | .map((gift) => gift.length) 49 | .reduce((acc, giftWight) => acc + giftWight); 50 | const availableStrength = reindeers 51 | .map((reinder) => reinder.length) 52 | .reduce((acc, reinderPower) => acc + reinderPower) * 2; 53 | 54 | return Math.floor(availableStrength / giftPackWeight); 55 | } -------------------------------------------------------------------------------- /src/2022/exercices/day04.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Santa Claus needs to review his gift boxes to make sure he can pack them all in his sleigh. 4 | He has a series of boxes of different sizes, characterized by their length, width, and height. 5 | 6 | Your task is to write a function that, given a list of boxes with their sizes, determines whether 7 | it is possible to pack all the boxes in one so that each box contains another (which in turn 8 | contains another, and so on). 9 | 10 | Each box represents its measures with an object. For example: {l: 2, w: 3, h: 2}. This means 11 | that the box has a length of 2, a width of 3 and a height of 2. 12 | 13 | A box fits into another box if all the sides of the first are smaller than the sides of the 14 | second. The elves have told us that the boxes cannot be rotated, so you cannot put a box of 2x3x2 15 | in a box of 3x2x2. Let's see some examples: 16 | 17 | fitsInOneBox([ 18 | { l: 1, w: 1, h: 1 }, 19 | { l: 2, w: 2, h: 2 } 20 | ]) // true 21 | In the previous example, the smallest box fits into the largest box. Therefore, it is possible 22 | to pack all the boxes in one. Now let's see a case that does not: 23 | 24 | const boxes = [ 25 | { l: 1, w: 1, h: 1 }, 26 | { l: 2, w: 2, h: 2 }, 27 | { l: 3, w: 1, h: 3 } 28 | ] 29 | 30 | fitsInOneBox(boxes) // false 31 | In the previous example, the smallest box fits into the middle box, but the middle box does 32 | not fit into the largest box. Therefore, it is not possible to pack all the boxes in one. 33 | 34 | Note that the boxes may not come in order: 35 | 36 | const boxes = [ 37 | { l: 1, w: 1, h: 1 }, 38 | { l: 3, w: 3, h: 3 }, 39 | { l: 2, w: 2, h: 2 } 40 | ] 41 | 42 | fitsInOneBox(boxes) // true 43 | In the previous example, the first box fits into the third, and the third into the second. 44 | Therefore, it is possible to pack all the boxes in one. 45 | 46 | Things to keep in mind: 47 | 48 | The boxes cannot be rotated because the elves have told us that the machine is not ready. 49 | The boxes may come in any order. 50 | The boxes are not always squares, they could be rectangles. 51 | */ 52 | 53 | /** 54 | * 55 | * Explicación: 56 | * - Primero ordenamos la lista de cajas en orden ascendente, teniendo en cuenta 57 | * las tres dimensiones de las mismas. 58 | * - Después iteramos la lista de cajas ordenadas, comprobando que cada caja entra 59 | * en la caja sucesora. Es decir, que cada una de sus tres dimensiones es inferior 60 | * a las dimensiones respectivas de la caja siguiente. En caso contrario, devolvemos 61 | * FALSE. 62 | * - Si terminamos de recorrer la lista sin devolver false significa que no encontramos 63 | * ningún problema, y devolvemos TRUE. 64 | * - Nótese el truco de utilizar una caja con dimensiones de valor INFINITO para que la 65 | * última caja de la lista no haga fallar el bucle al no encontrar una caja sucesora. 66 | 67 | */ 68 | export default function fitsInOneBox(boxes) { 69 | const sortedBoxes = [...boxes].sort((a, b) => { 70 | if (b.l > a.l || b.w > a.w || b.h > a.h) return -1; 71 | return 1; 72 | }); 73 | let result = true; 74 | let i = 0; 75 | while (result && i < sortedBoxes.length) { 76 | const a = sortedBoxes[i]; 77 | const b = sortedBoxes[++i] || { l: Infinity, w: Infinity, h: Infinity }; 78 | result = a.l < b.l && a.w < b.w && a.h < b.h; 79 | } 80 | return result; 81 | } -------------------------------------------------------------------------------- /src/2022/exercices/day05.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | To not tire the reindeer, Papa Noel wants to leave the maximum number of gifts by making the least 4 | number of trips possible. 5 | 6 | He has an array of cities where each element is the number of gifts he can leave there. For example, 7 | [12, 3, 11, 5, 7]. He also has a limit on the number of gifts that can fit in his bag, and finally, 8 | the maximum number of cities that his reindeer can visit. 9 | 10 | As he doesn't want to leave a city half-way, if he can't leave all the gifts that are from that city, 11 | he doesn't leave any there. 12 | 13 | Create a program that tells him the highest sum of gifts that he could distribute, taking into account 14 | the maximum number of gifts and the maximum number of cities he can visit. For example: 15 | 16 | const giftsCities = [12, 3, 11, 5, 7] 17 | const maxGifts = 20 18 | const maxCities = 3 19 | 20 | // the highest sum of gifts to distribute 21 | // visiting a maximum of 3 cities 22 | // is 20: [12, 3, 5] 23 | 24 | // the highest sum would be [12, 7, 11] 25 | // but it exceeds the limit of 20 gifts and he 26 | // would have to leave a city half-way. 27 | 28 | getMaxGifts(giftsCities, maxGifts, maxCities) // 20 (12 + 3 + 5) 29 | If it is not possible to make any trips that satisfies everything, the result should be 0. More examples: 30 | 31 | getMaxGifts([12, 3, 11, 5, 7], 20, 3) // 20 32 | getMaxGifts([50], 15, 1) // 0 33 | getMaxGifts([50], 100, 1) // 50 34 | getMaxGifts([50, 70], 100, 1) // 70 35 | getMaxGifts([50, 70, 30], 100, 2) // 100 36 | getMaxGifts([50, 70, 30], 100, 3) // 100 37 | getMaxGifts([50, 70, 30], 100, 4) // 100 38 | To consider: 39 | 40 | maxGifts >= 1 41 | giftsCities.length >= 1 42 | maxCities >= 1 43 | The number of maxCities can be greater than giftsCities.length 44 | */ 45 | 46 | /** 47 | * 48 | * Explicación: 49 | * - Esta solución no es muy eficiente, pero sí es muy sencilla de entender. 50 | * - Primero, a partir de la lista de ciudades posibles, generamos una nueva lista con todas las combinaciones 51 | * posibles que cumplan la condición del número máximo de ciudades. Es decir, que si maxCities tuviera un 52 | * valor n, generaríamos todas las combinaciones de tamaño 0 < X <= maxCities. 53 | * - Después calculamos el valor máximo de regalos que podríamos repartir en cada combinación. 54 | * - Nos interesa quedarnos con el mayor valor entre todas las combinaciones, por lo que ordenamos descendentemente 55 | * la lista y nos quedamos con el primer valor resultante. 56 | */ 57 | 58 | export default function getMaxGifts(giftsCities, maxGifts, maxCities) { 59 | return getCombinations(giftsCities, maxCities) 60 | .map((combination) => combination.reduce((deliveredGifts, city) => 61 | (deliveredGifts + city <= maxGifts) ? deliveredGifts + city : 0, 0)) 62 | .sort((a, b) => b - a)[0]; 63 | } 64 | 65 | function getCombinations(list, n) { 66 | const combinations = []; 67 | for (let size = 0; size <= n; size++) { 68 | const sizeCombinations = getSizeCombinations(list, size); 69 | combinations.push(...sizeCombinations); 70 | } 71 | return combinations; 72 | } 73 | 74 | function getSizeCombinations(list, size) { 75 | if (size === 0) return [[]]; 76 | 77 | if (size === 1) return list.map((element) => [element]); 78 | 79 | const combinations = []; 80 | for (let i = 0; i < list.length; i++) { 81 | const subCombinations = getSizeCombinations(list.slice(i + 1), size - 1); 82 | subCombinations.forEach((combination) => { 83 | combinations.push([list[i], ...combination]); 84 | }); 85 | } 86 | return combinations; 87 | } -------------------------------------------------------------------------------- /src/2022/exercices/day06.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | A couple of Christmas enthusiasts have created a Christmas decoration company. The first decoration they want 4 | to manufacture is a cube that is placed on the trees. 5 | 6 | The problem is that they have to program the machine and they don't know how to do it. They have asked us for 7 | help to achieve it. 8 | 9 | To create the cubes, a number with the desired size is passed to the program and it returns a string with the 10 | design of that size. For example, if we pass a 3, the program must return a cube of 3x3x3: 11 | 12 | const cube = createCube(3) 13 | 14 | // output: 15 | /\_\_\_\ 16 | /\/\_\_\_\ 17 | /\/\/\_\_\_\ 18 | \/\/\/_/_/_/ 19 | \/\/_/_/_/ 20 | \/_/_/_/ 21 | As you can see, the cube has three faces visually. The symbols used to build the cube faces are: /, \, _. In order 22 | to make the cube, some spaces are needed. Also, each line is separated by a new line character \n. 23 | 24 | Other examples of cubes: 25 | 26 | const cubeOfOne = createCube(1) 27 | 28 | // output: 29 | /\_\ 30 | \/_/ 31 | const cubeOfTwo = createCube(2) 32 | 33 | // output: 34 | /\_\_\ 35 | /\/\_\_\ 36 | \/\/_/_/ 37 | \/_/_/ 38 | Take into account: 39 | 40 | Pay attention to the spaces in the cube. 41 | The cube has to be symmetrical. 42 | Make sure you use the correct symbols. 43 | Each line must be separated by a new line character \n except for the last one. 44 | */ 45 | 46 | /** 47 | * 48 | * Explicación: 49 | * - Primero pintaremos la parte superior con bucles, y luego la parte inferior de la misma 50 | * forma pero invirtiendo los bucles para conseguir la simetría. 51 | * - Nótese el trimEnd() del final para eliminar el salto de línea sobrante. 52 | */ 53 | 54 | export default function createCube(size) { 55 | let cube = ''; 56 | for (let row = 1; row <= size; row++) { 57 | cube += ' '.repeat(size - row) + '/\\'.repeat(row); 58 | for (let col = 1; col <= size; col++) { 59 | cube += '_\\'; 60 | } 61 | cube += '\n'; 62 | } 63 | for (let row = size; row > 0; row--) { 64 | cube += ' '.repeat(size - row) + '\\/'.repeat(row); 65 | for (let col = size; col > 0; col--) { 66 | cube += '_/'; 67 | } 68 | cube += '\n'; 69 | } 70 | return cube.trimEnd(); 71 | } -------------------------------------------------------------------------------- /src/2022/exercices/day07.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | In the Santa Claus stores they are doing inventory. There are three stores (which is represented 4 | as an Array each). In each store there are gifts. 5 | 6 | We have been asked to write a program that tells us what gifts we have to buy to replenish our stores 7 | now that Christmas is approaching. A gift must be replenished when there is only stock in one of the 8 | three stores. 9 | 10 | For example, if we have the following stores: 11 | 12 | const a1 = ['bike', 'car', 'bike', 'bike'] 13 | const a2 = ['car', 'bike', 'doll', 'car'] 14 | const a3 = ['bike', 'pc', 'pc'] 15 | 16 | The store a1 has "bike" and "car". 17 | The store a2 has "car", "bike" and "doll". 18 | The store a3 has "bike" and "pc". 19 | 20 | The gift "doll" and "pc" are only in the stores a2 and a3 respectively. 21 | 22 | 23 | const gifts = getGiftsToRefill(a1, a2, a3) // ['doll', 'pc'] 24 | As you can see, the stores can have the same gift repeated several times. But, no matter how many 25 | existences there are in a store, if we do not have it in the other two, we must replenish it to have 26 | better distribution. 27 | 28 | 📝 To sum up 29 | 1. Create a function getGiftsToRefill that receives three Array as parameters. 30 | 1. The function must return an Array with the gifts to be replenished. 31 | 3. A gift must be replenished when there is only stock in one of the three stores. 32 | 4. If there is no gift to replenish, the function must return an empty Array. 33 | 5. If there is more than one gift to replenish, the function must return an Array with all the gifts that 34 | need to be replenished. 35 | */ 36 | 37 | /** 38 | * 39 | * Explicación: 40 | * - Creamos una lista que contenga las listas que recibimos como parametros. Así podremos trabajar de forma 41 | * más funcional. 42 | * - No nos interesa que una tienda tenga más de una unidad de stock de un item, por lo que eliminamos las 43 | * repeticiones dentro de cada tienda. Para ello transformamos cada lista de items en un conjunto y para así 44 | * volver a transformar el conjunto en una lista. Ya no tenemos repeticiones en cada tienda. 45 | * - A continuación juntamos los items de cada tienda en una única lista común. Ahora si hubiese repeticiones estas 46 | * pertenecerían a tiendas distintas. 47 | * - Por tanto, filtramos la lista común con los elementos que estén presentes en dicha lista una única vez. Esta 48 | * operación es O(N2) y podría implementarse de forma más eficiente mediante el algoritmo de la sliding window, 49 | * pero quedaba feo en el código implementar tal cosa 🤪. 50 | */ 51 | 52 | export default function getGiftsToRefill(a1, a2, a3) { 53 | return [a1, a2, a3] 54 | .map(store => [...new Set(store)]) 55 | .reduce((acc, store) => [...acc, ...store], []) 56 | .filter((currentItem, _, itemList) => 57 | itemList.filter(item => item === currentItem).length === 1); 58 | } -------------------------------------------------------------------------------- /src/2022/exercices/day08.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Se han estropeado algunos trineos eléctricos y los elfos están buscando piezas de repuesto para arreglarlos, 4 | pero no tienen claro si las piezas que tienen sirven. 5 | 6 | Las piezas de repuesto son cadenas de texto y el mecánico Elfon Masc ha dicho que una pieza de repuesto es 7 | válida si la pieza puede ser un palíndromo después de eliminar, como máximo, un carácter. 8 | 9 | Un palíndromo es una palabra o frase que se lee igual de izquierda a derecha que de derecha a izquierda. 10 | 11 | Nuestra función debe devolver un booleano que indique si la pieza de repuesto es válida o no con esa regla: 12 | 13 | checkPart("uwu") // true 14 | // "uwu" es un palíndromo sin eliminar ningún carácter 15 | 16 | checkPart("miidim") // true 17 | // "miidim" puede ser un palíndromo después de eliminar la primera "i" 18 | // ya que "midim" es un palíndromo 19 | 20 | checkPart("midu") // false 21 | // "midu" no puede ser un palíndromo después de eliminar un carácter 22 | */ 23 | 24 | /** 25 | * 26 | * Explicación: 27 | * - La forma más sencilla de comprobar si una palabra es un palíndromo es utilizar dos punteros, uno que 28 | * comienze por el principio y otro que lo haga por el final, ir incrementándolos y decrementándolos 29 | * respectivamente hasta que se crucen. En cada paso, se habrá que comprobar si los caracteres a los que 30 | * apuntan son iguales. En caso afirmativo, se continúa con la iteración. En caso contrario, se termina 31 | * inmediatamente la función retornando FALSE. 32 | * - Aquí tenemos la dificultad añadida de que se nos permite errar en el palíndromo por un caracter, por lo que 33 | * es un caso especial que habrá que tener en cuenta. Yo lo he hecho con la variable charactersRemovedCount, que 34 | * jamás deberá superar el 1 como valor. 35 | */ 36 | 37 | export default function checkPart(part) { 38 | let charactersRemovedCount = 0; 39 | let isPalindrome = true; 40 | 41 | let start = -1; 42 | let end = part.length; 43 | while (isPalindrome && charactersRemovedCount < 1 && start < end) { 44 | start++; 45 | end--; 46 | if (part[start] === part[end]) continue; 47 | if (part[start + 1] === part[end]) start++; 48 | else if (part[start] === part[end - 1]) end--; 49 | else return false; 50 | charactersRemovedCount++; 51 | } 52 | return isPalindrome; 53 | } -------------------------------------------------------------------------------- /src/2022/exercices/day09.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Una empresa que fabrica luces de Navidad nos ha pedido que le echemos una mano. 4 | 5 | Tienen unas tiras led que son como un Array. Cada posición es un led y puede ser estar encendido (1) o apagado (0). 6 | 7 | Cada 7 segundos, los leds cambian de estado de esta forma: 8 | 9 | Si el led está apagado, se enciende si el led de su izquierda (index - 1) estaba encendido antes. 10 | Si el led está encendido, se mantiene siempre encendido. 11 | Nos han pedido un programa que nos diga cuantos segundos deben pasar hasta que todos los leds están encendidos. Los 12 | segundos se expresan en un número entero. Por ejemplo: 13 | 14 | const leds = [0, 1, 1, 0, 1] 15 | countTime(leds) // 7 16 | // 7 segundos ya que en el primer cambio 17 | // todas las luces se encendieron 18 | // 0s: [0, 1, 1, 0, 1] 19 | // 7s: [1, 1, 1, 1, 1] 20 | 21 | countTime([0, 0, 0, 1]) // 21 22 | // 21 segundos ya que necesita tres cambios: 23 | // 0s: [0, 0, 0, 1] 24 | // 7s: [1, 0, 0, 1] 25 | // 14s: [1, 1, 0, 1] 26 | // 21s: [1, 1, 1, 1] 27 | 28 | countTime([0, 0, 1, 0, 0]) // 28 29 | // 28 segundos ya que necesita cuatro cambios: 30 | // 0s: [0, 0, 1, 0, 0] 31 | // 7s: [0, 0, 1, 1, 0] 32 | // 14s: [0, 0, 1, 1, 1] 33 | // 21s: [1, 0, 1, 1, 1] 34 | // 28s: [1, 1, 1, 1, 1] 35 | A tener en cuenta 36 | El array siempre tendrá al menos un led encendido. 37 | El array puede tener cualquier longitud. 38 | Si todos los leds están encendidos, el tiempo es 0. 39 | */ 40 | 41 | /** 42 | * Explicación: 43 | * - El ejercicio nos promete que la entrada siempre tendrá un led encendido, por lo que nuestro programa nunca se topará 44 | * con una entrada que el algoritmo no pueda resolver. Por esto mismo, nos tomamos la libertad de hacer un while(true), 45 | * ya que nuestro programa, por contrato, jamás entrará en estado de bucle infinito. 46 | * - El algoritmo es el siguiente. Iteramos mediante un while true hasta que todos nuestros leds estén encendidos. Al contar 47 | * siempre con al menos un led encendido de entrada, en cada iteración del bucle while habrá un cambio de estado obligatoriamente, 48 | * es decir, habrá siempre al menos un led apagado que encendamos. 49 | * - La lista de leds es circular. Es decir, el elemento de la izquierda del primer elemento es el último elemento. 50 | * - A partir de los tests entendemos que no hay reacción en cadena en una misma 'ronda', es decir, si en cierta iteración tengo [0,0,1], 51 | * podemos encender el primer led, puesto que el de su 'izquierda', es decir, el último led en este caso, está encendido. Esto quedaría así 52 | * [1,0,1]. Si ahora, en la misma 'ronda' comprobamamos el segundo led, veremos que 'podríamos' encenderlo, puesto que el led de su izquierda 53 | * está encendido (lo acabamos de encender en la misma ronda), por lo que la lista de leds quedaría así [1,1,1]. ERROR, no se permite tal cosa. 54 | * Para evitar bugs de este estilo, en cada ronda utilizamos una lista auxiliar sobre la que vamos editando y al final de cada ronda se aplica esta 55 | * lista auxiliar como la lista a utilizar en la siguiente ronda. 56 | */ 57 | 58 | export default function countTime(leds) { 59 | let counter = 0; 60 | while (true) { 61 | let modified = false; 62 | const modifiedLeds = [...leds]; 63 | for (let i = leds.length - 1; i >= 0; i--) { 64 | const leftPosition = leds[(i - 1 + leds.length) % leds.length]; 65 | if (!leds[i] && leftPosition) { 66 | modifiedLeds[i] = 1; 67 | modified = true; 68 | } 69 | } 70 | if (!modified) return counter; 71 | leds = modifiedLeds; 72 | counter += 7; 73 | } 74 | } -------------------------------------------------------------------------------- /src/2022/exercices/day10.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Crea un programa que compruebe que el trineo de Santa Claus hace una parabola al saltar entre ciudades. Recibes un 4 | array de números que representan la altura en la que se encuentra el trineo en cada momento. 5 | 6 | Para que la parabola sea correcta, el viaje del trineo debe ser ascendente al principio, llegar al punto más alto y 7 | descender hasta el final. No puede volver a subir una vez que ha bajado, ni puede iniciar el viaje bajando. Veamos unos ejemplos: 8 | 9 | const heights = [1, 3, 8, 5, 2] 10 | checkJump(heights) // true 11 | 12 | /* 13 | Es `true`. 14 | El salto va de abajo a arriba y luego de arriba a abajo: 15 | 16 | 8 (punto más alto) 17 | ↗ ↘ 18 | 3 5 19 | ↗ ↘ 20 | 1 2 21 | 22 | 23 | const heights = [1, 7, 3, 5] 24 | checkJump(heights) // false 25 | 26 | /* 27 | Es `false`. 28 | Va de abajo a arriba, de arriba a abajo y luego sube otra vez. 29 | 30 | 7 5 31 | ↗ ↘ ↗ 32 | 1 3 33 | Necesitamos que el programa devuelva un boolean que indique si el trineo hace una parabola o no. 34 | 35 | A tener en cuenta 36 | Para que el salto sea válido tiene que subir una vez y bajar una vez. Si durante el salto se queda en la misma altura 37 | entre dos posiciones, la parabola continua. 38 | No hace falta que el punto de inicio y final sean el mismo (las ciudades pueden estar a diferentes alturas). 39 | */ 40 | 41 | /** 42 | * Explicación: 43 | * - La idea es ir recorriendo la lista estando pendiente de dos flags: hasIncreased y hasDecreased. 44 | * - Al terminar de recorrer la lista, se habrá formado una parábola si ambos flags están a TRUE. 45 | * - Sin embargo, sólo se puede subir y bajar una vez, por lo que en cada iteración debemos estar pendientes del 46 | * sentido del movimiento. 47 | * - Para ello calculamos la posición anterior. Para no tener problemas, empezamos a iterar desde al segunda posición de 48 | * la lista, y así nunca tendremos un valor undefined. 49 | * - Si el sentido del movimiento es ascendente, pero ya habíamos descendido antes, es decir, estaríamos ante una segunda subida, 50 | * salimos de la función devolviendo FALSE. 51 | */ 52 | 53 | 54 | export default function checkJump(heights) { 55 | let i = 1; 56 | let hasIncreased = false; 57 | let hasDecreased = false; 58 | while (i < heights.length) { 59 | const previousPosition = heights[i - 1]; 60 | if (previousPosition < heights[i]) { 61 | if (hasDecreased) return false; 62 | hasIncreased = true; 63 | } else if (previousPosition > heights[i]) { 64 | hasDecreased = true; 65 | } 66 | i++; 67 | } 68 | return hasIncreased && hasDecreased; 69 | } -------------------------------------------------------------------------------- /src/2022/exercices/day11.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Problem description: 3 | Papa Noél está un poco preocupado porque los preparativos están llevando mucho tiempo. Hace poco se ha sacado una 4 | certificación de Scrum y ha decidido usar la metodología para organizar el trabajo de sus elfos. 5 | 6 | Le dicen la duración esperada de las tareas con un string con el formato hh:mm:ss y en el mismo formato cuánto tiempo 7 | llevan trabajando en ella. 8 | 9 | Pero Papa Noél no se entera rápidamente si falta o mucho para que termine, así que nos ha pedido que hagamos un programa 10 | que nos indique la porción de la tarea que ya se ha completado. 11 | 12 | Por ejemplo, si la tarea dura 03:00:00 y llevan trabajando 01:00:00 entonces ya han completado 1/3 de la tarea. En código: 13 | 14 | getCompleted('01:00:00', '03:00:00') // '1/3' 15 | getCompleted('02:00:00', '04:00:00') // '1/2' 16 | getCompleted('01:00:00', '01:00:00') // '1/1' 17 | getCompleted('00:10:00', '01:00:00') // '1/6' 18 | getCompleted('01:10:10', '03:30:30') // '1/3' 19 | getCompleted('03:30:30', '05:50:50') // '3/5 20 | 21 | Ten en cuenta: 22 | -El formato de la hora es hh:mm:ss. 23 | -El formato de la salida es un string a/b donde a es la porción de la tarea que ya se ha completado y b es la porción de la 24 | tarea que falta por completar. 25 | -La porción siempre se muestra con la menor fracción posible. (por ejemplo, nunca se mostraría 2/4 porque se puede representar 26 | como 1/2). 27 | -Si ya se ha completado la tarea, la fracción sería 1/1. 28 | -Ningun elfo ha sido maltradado durante la ejecución de este reto ni han tenido que usar Scrum de verdad. 29 | */ 30 | 31 | /** 32 | * Explicación: 33 | * - La entrada nos viene en formato string, pero para poder trabajar con la diferencia de tiempo nos conviene pasarlo a formato 34 | * numérico. Escogemos los segundos como unidad de tiempo para ser precisos. 35 | * - Para hacer la conversión de tiempo utilizamos una función que parsea el string, primero extrayendo las tres unidades 36 | * disponibles: horas, minutos y segundos, en ese orden. Después parseamos a número esos valores. Debido al Type Coercion este 37 | * último paso no es necesario, pues un '3' se trataría como un 3, pero haciendo este paso explícitamente nos curamos en salud. 38 | * Por último trabajamos cada unidad multiplicandolo por lo que toque y sumando los resultados para obtener los segundos totales. 39 | * - Ahora tenemos unos maravillosos numerador y denominador, tiempo transcurrido y tiempo estimado respectivamente, en segundos. 40 | * Necesitamos simplificar esa fracción, y para ello utilizamos el Máximo Común Divisor. 41 | */ 42 | 43 | export default function getCompleted(part, total) { 44 | const getGCD = (a, b) => b ? getGCD(b, a % b) : a; 45 | const getSecondsFromString = (str => str 46 | .split(':') 47 | .map((t) => parseInt(t)) 48 | .reduce((acc, t, i) => acc + t * Math.pow(60, 2 - i), 0)); 49 | 50 | const elapsedSeconds = getSecondsFromString(part); 51 | const estimatedSeconds = getSecondsFromString(total); 52 | 53 | const gcd = getGCD(elapsedSeconds, estimatedSeconds); 54 | const numerator = elapsedSeconds / gcd; 55 | const denominator = estimatedSeconds / gcd; 56 | 57 | return numerator + '/' + denominator; 58 | } -------------------------------------------------------------------------------- /src/2022/exercices/day12.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Explicación: 3 | * - No nos interesa el consumo por unidad de distancia, nos interesa el consumo por lo que ha recorrido, 4 | * por tanto a cada objeto de la lista le añadimos este calculo bajo la clave 'watts'. 5 | * - Filtramos aquellos elementos que consuman más de lo que nos permite la batería. 6 | * - Nos quedamos con la última opción disponible. Como el array estaba ordenado ascendentemente por consumo, 7 | * esa será la mejor opción. 8 | * - En el caso de que no haya ningún reno que nos permita consumir menos de lo que nos permita la batería, o que 9 | * directamente no haya renos, devolvemos NULL. 10 | */ 11 | 12 | export default function selectSleigh(distance, sleighs) { 13 | const BATTERY_LIMIT = 20; 14 | const result = sleighs 15 | .map(sleigh => { 16 | sleigh.watts = sleigh.consumption * distance; 17 | return sleigh; 18 | }) 19 | .filter(sleigh => sleigh.watts <= BATTERY_LIMIT) 20 | .at(-1) 21 | return result ? result.name : null; 22 | } 23 | 24 | /** 25 | * SOLUCIÓN ALTERNATIVA!! 26 | * Esta solución es idéntica a la anterior, pero utilizando una sintaxis de JS más moderna. Lamentablemente, 27 | * adventJS sólo permite utilizar sintaxis hasta ES2017. 28 | * 29 | * Explicación: 30 | * - Gracias a la desestructuración, en el map podemos crear por cada objeto un nuevo objeto incluyendo las entradas 31 | * ya existentes en el objeto original + la nueva entrada de weight. Todo en una sóla línea. 32 | * - Si no fuese por mí manía de no usar magic numbers, todo podría hacerse en una sóla línea, como puedes ver. Esto 33 | * es debido a que con los Optional Fields + Nullish coalescing podemos trabajar los valores nullish y undefined. 34 | * En este caso, .at(-1) nos devuelve la última posición del array, pero esto podría devolver UNDEFINED en caso de 35 | * en el array no haya elementos. Si accediésemos en ese caso al campo NAME, podría saltar una excepción, pues .name 36 | * no existe en UNDEFINED. Para ello accedemos utilizando el operador '?.', que hace que en ese caso concreto, lo que 37 | * haría sería propagar el UNDEFINED por la llamada. Es decir: 38 | * * undefined.name => ERROR 39 | * * undefined?.name => undefined 40 | * Por último, con el operador NULLISH COALESCING, '??', estamos comparando lógicamente si el valor de la izquierda es 41 | * NULLISH, es decir, NULL o UNDEFINED, y en caso afirmativo, devolver lo que tengamos a la derecha del operador, en este caso, NULL. 42 | * 43 | */ 44 | 45 | export default function selectSleigh(distance, sleighs) { 46 | const BATTERY_LIMIT = 20; 47 | return sleighs 48 | .map(sleigh => ({ ...sleigh, watts: sleigh.consumption * distance })) 49 | .filter(sleigh => sleigh.watts <= BATTERY_LIMIT) 50 | .at(-1) 51 | ?.name ?? null 52 | } -------------------------------------------------------------------------------- /src/2022/exercices/day13.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Explicación: 3 | * - Primero filtramos aquellos ficheros cuya última modificación sea posterior a la fecha del último backup. 4 | * - A partir de aquí ya hemos terminado con los timestamps, sólo nos interesan los ids, así que nos quedamos 5 | * sólo con ellos. 6 | * - Luego queremos eliminar los ids repetidos, pertenecientes a las distintas modificaciones de un archivo. 7 | * - Por último, ya cuando tenemos los M ejercicios solución, los ordenamos ascendentemente. Es importante hacer 8 | * esta ordenación como último paso para asegurarnos no ordenar elementos de la lista que luego van a ser descargatos 9 | * (estaríamos haciendo operaciones de más, penalizando el rendimiento). 10 | */ 11 | 12 | export default function getFilesToBackup(lastBackup, changes) { 13 | return changes 14 | .filter(file => file[1] > lastBackup) 15 | .map(([id,]) => id) 16 | .reduce((uniqueIds, id) => 17 | uniqueIds.includes(id) ? uniqueIds : [...uniqueIds, id], []) 18 | .sort((a, b) => a - b); 19 | } -------------------------------------------------------------------------------- /src/2022/exercices/day14.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Explicación: 3 | * - El algoritmo es sencillo. Vamos a utilizar una función recursiva auxiliar que vaya explorando el arbol. 4 | * - Caso base: Si estoy en un nodo hoja, devulevo el valor del nodo hoja. 5 | * - Caso recursivo: En caso contrario, llamo a la función recursiva con las coordenadas (fila, columna) de 6 | * mi hijo izquierdo y obtengo su solución. Hago lo mismo con mi hijo derecho. 7 | * - La función recursiva devuelve el valor mínimo entre sus dos hijos, es decir, el camino más corto que hay 8 | * desde su posición hasta el final, más el valor el propio nodo. 9 | * 10 | * Observaciones SUPER IMPORTANTES: 11 | * - Debido al analizador estático que analiza la complejidad cognitica del código, necesito guardar la solución 12 | * en la variable 'result' en vez de hacer directametne un return con la primera llamada a la función recursiva. 13 | * No soy muy fan de esto, pero no quería quedarme con un 10 de nota :P. 14 | * - Aquí está lo que menos me ha gustado del analizador sintáctico: me ha penalizado por usar una caché. Una caché 15 | * es un mapa que va guardando soluciones ya exploradas. Si nos vamos al ejemplo ([[0], [7, 3], [2, 4, 6]], y dibujamos 16 | * el arbol de forma gráfica: 17 | * 18 | * 0 19 | / \ 20 | 7 3 21 | / \ / \ 22 | 2 4 6 23 | Nuestra función recorrería el '4' que hay en la última fila dos veces: una cuando 7 analizase su hijo derecho, y otra 24 | cuando 3 analizase su hijo izquierdo. 25 | 26 | Aquí no es ningún drama, pues son árboles muy pequeños, pero si tuviésemos muchas filas, si no úsasemos una caché nuestro 27 | programa tardaría una eternidad (quizás nunca acabase). Con el uso de una caché, ese valor 4 sólo tendría que calcularse 28 | una única vez, ya que cuando se analizase en la ejecución del hijo derecho de 7 se guardaría la solución en una caché, y 29 | cuando 3 analizase su hijo izquierdo, ya tendríamos esa solución accesible de forma O(1) a través de la caché, en vez de 30 | O(N log(N)). 31 | */ 32 | 33 | export default function getOptimalPath(path) { 34 | const getOptimalPathAux = (path, row, column) => { 35 | if (row === path.length - 1) return path[row][column]; 36 | const leftChildResult = getOptimalPathAux(path, row + 1, column); 37 | const rightChildResult = getOptimalPathAux(path, row + 1, column + 1) 38 | return path[row][column] + Math.min(leftChildResult, rightChildResult); 39 | } 40 | const result = getOptimalPathAux(path, 0, 0); 41 | return result; 42 | } -------------------------------------------------------------------------------- /src/2022/exercices/day15.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Explicación: 3 | * - La entrada no es una lista de caracteres, sino un string que separa los caracteres con espacio. La salida 4 | * esperada no es una lista de listas de caracteres, sino una lista de caracteres separados por espacios. Esto 5 | * es importante, puesto que nos resulta molesto ya que la forma más fácil de abordar este ejercicio es tratando 6 | * las filas como arrays de caracteres y no como strings con espacios. Mi estrategia ha sido trabajar con arrays 7 | * de caracteres y finalmente mapear a la salida deseada. 8 | * - Para ello cogí la base, la transformé en un array de caracteres, creé una lista de listas de tamaño N, siendo N 9 | * el número de elementos de la base (ya que esta también es la altura del árbol). Por último añado la base en forma 10 | * de lista de caracteres al final de mi arbol: ya tengo mi lista de listas, sólo queda rellenarla (salvola base, 11 | * que ya la tenemos). 12 | * - Como voy desde la base hasta arriba, el bucle FOR que recorre el arbol verticalmente está invertido. Sin embargo, 13 | * el bucle FOR que recorre la anchura de cada fila del arbol no hace falta que lo esté. 14 | * - Para determinar qué adorno colocar podemos crear una función que en función de los objetos base devuelva: 15 | * - El objeto de su hijo izquierdo si su hijo izquierdo y derechos son iguales. 16 | * - El elemento faltante de una lista [a,b,c] si sus hijos están en esa lista y no son iguales. Esto podría hacerse así: 17 | * const getParentItem = (left, right) => left === right ? left : ['B', 'R', 'P'].filter(item = > item !== left && item !== right)[0]; 18 | * Sin embargo, yo preferí meter todas las posibilidades en un objeto y utilizarlo como mapa. Aquí para gustos: colores. 19 | */ 20 | 21 | export default function decorateTree(base) { 22 | const posibilities = { 23 | 'B_B': 'B', 24 | 'R_R': 'R', 25 | 'P_P': 'P', 26 | 'B_P': 'R', 27 | 'B_R': 'P', 28 | 'P_B': 'R', 29 | 'P_R': 'B', 30 | 'R_B': 'P', 31 | 'R_P': 'B', 32 | }; 33 | const baseArray = base.split(' '); 34 | const tree = new Array(baseArray.length); 35 | tree[baseArray.length - 1] = baseArray; 36 | for (let row = baseArray.length - 1; row > 0; row--) { 37 | const newRow = []; 38 | for (let itemIndex = 0; itemIndex < tree[row].length - 1; itemIndex++) { 39 | const key = `${tree[row][itemIndex]}_${tree[row][itemIndex + 1]}`; 40 | newRow.push(posibilities[key]); 41 | } 42 | tree[row - 1] = newRow; 43 | } 44 | return tree.map(row => row.join(' ')); 45 | } -------------------------------------------------------------------------------- /src/2022/exercices/day16.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Explicación: 3 | * Este problema consiste en ir procesando la entrada, como si de una cadena de montaje se tratase, modificando 4 | * el string hasta obtener la solución. Por favor, JavaScript, implementa el operador Pipe de una santa vez. 5 | * 6 | * En este caso, no veo necesario explicar cada paso uno a uno, pues creo que el nombre de las funciones es 7 | * suficientemente descriptivo. En este problema utilicé expresiones regulares. ¿Por qué? Porque me molan jeje, 8 | * pero no es necesario (y ni siquiera son buenas para el rendimiento en algunos casos). 9 | * 10 | * Nota: como no podemos disponer de la función String.prototype.replaceAll, debido a que AdventJS utiliza ES2017, 11 | * hemos tenido que utilizar el flag para regex 'g'. Intenté implementarme mi propio pollyfill para esta función, 12 | * pero AdventJS restringe la modificación de 'this'. 13 | * 14 | */ 15 | 16 | export default function fixLetter(letter) { 17 | const removeMultipleWhiteSpaces = text => text.replace(/\s+/g, ' '); 18 | const cleanCommasSpaces = text => text.replace(/\s*,\s*/g, ', '); 19 | const cleanPeriodsSpaces = text => text.replace(/\s*\.\s*/g, '. '); 20 | const removeMultipleQuestionMarks = text => text.replace(/\s*\?+/g, '?'); 21 | const uppercaseSantaClaus = text => text 22 | .replace(/santa claus/gi, 'Santa Claus'); 23 | const upperCaseWordsAfterPunctuation = text => { 24 | text = text[0].toLocaleUpperCase() + text.substring(1); 25 | const regex = /[.!?]\s[a-z]/gi; 26 | return text.replace(regex, 27 | match => match[0] + ' ' + match[2].toUpperCase() 28 | ); 29 | }; 30 | const addFinalPeriod = text => text + 31 | (text.charAt(text.length - 1).match(/[\.\?\!]$/) ? '' : '.'); 32 | 33 | let formattedText = letter; 34 | formattedText = removeMultipleWhiteSpaces(formattedText); 35 | formattedText = cleanCommasSpaces(formattedText); 36 | formattedText = cleanPeriodsSpaces(formattedText); 37 | formattedText = removeMultipleQuestionMarks(formattedText); 38 | formattedText = uppercaseSantaClaus(formattedText); 39 | formattedText = formattedText.trim(); 40 | formattedText = upperCaseWordsAfterPunctuation(formattedText); 41 | formattedText = addFinalPeriod(formattedText); 42 | return formattedText; 43 | } -------------------------------------------------------------------------------- /src/2022/exercices/day17.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Explicación: 3 | * Resumen: tenemos que agrupar un array de string en orden y por tamaño de los elementos. 4 | * - Nuestra variable solución será giftPacks, y en ella iremos añadiendo los distintos 5 | * paquetitos según los vayamos identificando. 6 | * - bagWeight será nuestra 'báscula'. En ella iremos pesando los distintos elementos que 7 | * componen un paquetito para no pasarnos. 8 | * - currentPack será el paquetito que estamos completando en ese momento. Cuando se 9 | * 'cierre', vaciaremos el array y continuaremos haciendo paquetitos con el resto de regalitos. 10 | * - El proceso es muy iterativo. Recorremos la lista de regalos. Si el peso de ese regalo, más 11 | * el peso total de los regalos que hemos metido en el paquetito actual, supera el peso máximo 12 | * válido, significa que tenemos que cerrar el paquetito con los regalos previos y crear un nuevo 13 | * paquetito cuyo primer elemento sea este regalito que estábamos estudiando. 14 | * - Sin embargo, se nos pide un array de paquetitos en forma de string, es decir, un array de 15 | * strings, y nosotros tenemos una array de array de strings. Para eso, por cada paquetito, en 16 | * este momento un array de strings, ejecutamos el método 'join' para agrupar los elementos en 17 | * texto. Así obtenemos la solución deseada. 18 | * 19 | */ 20 | 21 | export default function carryGifts(gifts, maxWeight) { 22 | const giftsPacks = []; 23 | let bagWeight = 0; 24 | let currentPack = []; 25 | for (let giftIndex = 0; giftIndex < gifts.length; giftIndex++) { 26 | if (bagWeight + gifts[giftIndex].length <= maxWeight) { 27 | bagWeight += gifts[giftIndex].length; 28 | currentPack.push(gifts[giftIndex]); 29 | } 30 | else { 31 | if (currentPack.length) { 32 | giftsPacks.push(currentPack.join(' ')); 33 | } 34 | bagWeight = 0; 35 | currentPack = []; 36 | if (gifts[giftIndex].length <= maxWeight) { 37 | bagWeight += gifts[giftIndex].length; 38 | currentPack.push(gifts[giftIndex]); 39 | } 40 | } 41 | } 42 | if (currentPack.length) { 43 | giftsPacks.push(currentPack.join(' ')); 44 | } 45 | return giftsPacks; 46 | } -------------------------------------------------------------------------------- /src/2022/exercices/day18.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Explicación: 3 | * Pensando en números quizás esto se vea confuso, así que pensemos que son strings. 4 | * Aquí se nos estaría pidiendo que, dado un caracter y una lista de strings, devolvamos 5 | * aquellos elementos de la lista de strings que contienen dicho caracter al menos una vez. 6 | * - Cierto, no nos dan una lista de caracteres, sino que nos dicen el límite superior de 7 | * un rango de números. Este rango, por convención, empieza por 1, así que si al segundo 8 | * parámetro lo llamásemos N, estaríamos trabajando con un rango de [1,N]. Este rango no 9 | * lo tenemos, lo tenemos que crear, y para eso es la primera operación: vamos a crear un 10 | * array vacío de tamaño N, obtener una colección iterable de sus índices y por último 11 | * transformar esa colección iterable en una lista gracias al operador 'spread'. 12 | * - Sin embargo, recordemos que los arrays en JavaScript se enumeran así: [0,N-1]. Por lo 13 | * tanto, para tener nuestro querido array de números de [1,N] necesitamos incrementar 14 | * todos los items una posición. 15 | * - Ya tenemos nuestra entrada deseada, ahora sólo falta fitrar todos aquellos números que, 16 | * convertidos a string (por comodidad), tienen un caracter que sea el dígito pedido en 17 | * cuestión (debido al tipado dinámico de JavaScript, al estar comparando string con number, 18 | * estará comparando realmente string con string. Ver coersion). 19 | * 20 | */ 21 | 22 | export default function dryNumber(dry, numbers) { 23 | return [...Array(numbers).keys()] 24 | .map(bar => bar + 1) 25 | .filter(bar => bar.toString().includes(dry)); 26 | } -------------------------------------------------------------------------------- /src/2022/exercices/day19.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Explicación: 3 | * Aquí presento la solución que mejor puntuación me ha dado. 4 | * Básicamente es, aprovechando que en JS las listas son dinámicas, crear un array vacío y por cada elemento 5 | * de la lista de positions añadir el juquete que comparta índice con ese item en la posición del nuevo array 6 | * que indique el mismo elemento. Es decir, que si tuviéramos: 7 | * sortToys(['ball', 'doll', 'car'], [2, 1, 0]), y una lista vacía para el resultado: [] 8 | * Iteraríamos el array de positions. El primer elemento es 2, con índice 0. Es decir, que en la posición 2 del 9 | * nuevo array meteríamos el juguete que comparta índice con esa posición: 10 | * resultado: [undefined, undefined, 'ball']. 11 | * Continuaríamos con el segundo elemento del array de posiciones, que es el 1 y tiene índice 1. Hacemos lo mismo: 12 | * resultado: [undefined, 'doll', 'ball'] 13 | * Por último, el elemento 0 con índice 2: 14 | * resultado: ['car', 'doll', 'ball']. 15 | * 16 | * Pero cuidado, las posiciones no tienen por qué ir de [0,N-1]. El enunciado nos advierte que pueden tener un 17 | * 'desfase'. Es decir, si tuviésemos un desfase (o desplazamiento, como prefieras), de 2 unidades, nuestro ejemplo 18 | * anterior sería [4, 3, 2]. El truco de usar los valores del array de positions como índices del nuevo array no funciona. 19 | * Por ello, la opción más eficiente es encontrar el desfase y restarlo a cada valor cuando vayamos a usarlo como índice. 20 | * 21 | */ 22 | 23 | export default function sortToys(toys, positions) { 24 | const gap = positions 25 | .reduce((acc, position) => Math.min(acc, position), Infinity); 26 | return positions 27 | .reduce((acc, position, i) => { 28 | acc[position - gap] = toys[i]; 29 | return acc; 30 | }, []); 31 | } -------------------------------------------------------------------------------- /src/2022/exercices/day20.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Explicación: 3 | * Este problema es una variante del famoso problema de la máquina expendedora, donde dado un array de monedas con 4 | * el nombre de la moneda y su valor, y dado un precio, debemos calcular qué y cuántas monedas a utilizar para llegar 5 | * a ese precio minimizando el número de monedas. Aquí se nos introduce un nuevo problema: siempre tener más monedas 6 | * pequeñas que grandes. 7 | * - Mi estrategia ha sido similar a la que uso para resolver el problema base: iterar desde el reno de mayor capacidad 8 | * hasta el de menor capacidad descontando dicha capadidad del peso que debo soportar. 9 | * Es decir, si tengo que llevar un peso 35 y tengo estos renos: 10 | * [ 11 | * {nombre:Juan,capacidad:20}, 12 | * {nombre:Carmen,capacidad:10}, 13 | * {nombre:Itziar,capacidad:5}, 14 | * {nombre:Jon,capacidad:1} 15 | * ] 16 | * Empezaría con el primer reno: Juan, y descontaría a mi peso su capacidad: 35-20 = 15. Vuelvo a intentarlo con el 17 | * mismo reno, pero veo que ya no puedo descontarle 20 a 15, por lo que paso al siguiente reno. 18 | * Con Carmen sí puedo descontarle 10 a 15, lo hago, consiguiendo un peso restante de 5, y veo que tampoco puedo seguir 19 | * con Carmen. Bajo a Itziar, que sí puede con esos 5 de peso, lo descuento. Ahora mismo mi peso restante es de 0. En el 20 | * problema básico, ya habríamos terminado. Sin embargo, luego toca Jon. Vemos que Jon no puede seguir restando. 21 | * Evaluamos el resultado hasta ahora: 22 | * [ 23 | * {nombre:Juan,capacidad:20, carga: 1}, 24 | * {nombre:Carmen,capacidad:10, carga: 1}, 25 | * {nombre:Itziar,capacidad:5, carga: 1}, 26 | * {nombre:Jon,capacidad:1, carga: 0} 27 | * ] 28 | * No se está cumpliendo que cada reno deba llevar más carga que su reno superior en capacidad: Itziar está cargando 29 | * más que Jon. Por tanto, debemos descontar a Itziar para que Jon pueda cargar: 30 | * [ 31 | * {nombre:Juan,capacidad:20, carga: 1}, 32 | * {nombre:Carmen,capacidad:10, carga: 1}, 33 | * {nombre:Itziar,capacidad:5, carga: 0}, 34 | * {nombre:Jon,capacidad:1, carga: 5} 35 | * ] 36 | * Pero vemos que el problema ha escalado hacia arriba. Ahora es Itziar quien tiene menos carga que su superior, Carmen. 37 | * Por tanto decrementamos la carga de carmen. Sin embargo, al hacerlo, la carga que tanto Jon como Itziar pueden llevar ha 38 | * variado, así que tendríamos que volver a calcular todo desde Itziar. Es decir, hemos decrementado nuestro índice y vuelto 39 | * a empezar pero modificando la precondición. Tenemos que repetir este proceso hasta tenerlo todo correctamente ajustado y 40 | * satisfaciendo las condiciones requeridas. 41 | */ 42 | 43 | export default function howManyReindeers(reindeerTypes, gifts) { 44 | const prepareReindeers = (reindeerTypes, { country, weight }) => { 45 | /* 46 | Nuestra variable acumuladora para calcular qué peso están soportando los renos actualmente seleccionados. Crecerá y decrecerá 47 | en función de los ajustes que se hagan 48 | */ 49 | let weightCapacityAcc = 0; 50 | /* 51 | Inicializamos el array solución con todo los tipos de reno y un contador de carga para cada uno de ellos a 0. Al final 52 | de la función filtraremos por sólo aquellos renos que lleven carga. 53 | */ 54 | const reindeerCount = reindeerTypes.map(({ type }) => ({ type, num: 0 })); 55 | /* 56 | Nuestro índice que irá iterando por el array de renos. Recuerda, no sólo crece, ya que también puede decrementarse para 57 | ajustar la solución. Este índice es compartido tanto por el array solución como por el array de tipos de reno, ya que son 58 | equivalentes. 59 | */ 60 | let i = 0; 61 | while (i < reindeerTypes.length) { 62 | if (reindeerTypes[i].weightCapacity + weightCapacityAcc <= weight) { 63 | // Si el reno que actualmente estoy analizando podría cargar más peso, pero sin pasarme y que me sobre capacidad: 64 | weightCapacityAcc += reindeerTypes[i].weightCapacity; //Incremento peso acumulado 65 | reindeerCount[i].num++; //Incremento contador de dicho tipo de reno 66 | } 67 | else { 68 | /* 69 | Si no, debo analizar si el reno actual está cargando menos peso que el siguiente reno de mayor capacidad. Si es así, 70 | tengo que reajustar mi solución, lo que implica que hasta quizás tenga que retroceder mi índice. 71 | */ 72 | /* 73 | j es un nuevo índice decremental para reajustar la solución. Irá subiendo por la jerarquía de renos 74 | para comprobar que todo está OK. 75 | */ 76 | let j = i - 1; 77 | let modified = false; //Variable para comprobar si tras el análisis se ha producido un reajuste o no. 78 | while (j >= 0) { 79 | if (reindeerCount[j].num > reindeerCount[j + 1].num) { 80 | reindeerCount[j].num--; 81 | weightCapacityAcc -= reindeerTypes[j].weightCapacity; 82 | modified = true; 83 | if (j < i - 1) i--; 84 | /* 85 | No estamos reajustando el reno inmediatamente superior al que apunta mi índice i, sino uno aún más superior, por lo 86 | que los renos intermedios deben ser recalculados. 87 | */ 88 | } 89 | j--; 90 | } 91 | //Si no ha habido ningún ajuste, podemos continuar con el siguiente reno. 92 | if (!modified) i++; 93 | } 94 | } 95 | // Eliminamos de la solución los tipos de reno que no vayamos a utilizar. 96 | const filteredReindeers = reindeerCount.filter(({ num }) => num > 0) 97 | return { country, reindeers: filteredReindeers }; 98 | }; 99 | //El listado de tipos de renos debemos ordenarlo en función de su capacidad, para ir de mayor a menor. 100 | const sortedReindeerTypes = [...reindeerTypes] 101 | .sort((a, b) => b.weightCapacity - a.weightCapacity); 102 | return gifts 103 | .map(city => prepareReindeers(sortedReindeerTypes, city)); 104 | } --------------------------------------------------------------------------------