Live Coding

Comprobar si un Objeto Está Vacío

¿Cómo comprobar si un objeto de JavaScript está vacío?

Explicación: Un objeto está vacío si no tiene propiedades enumerables propias. Podemos usar Object.keys(obj) que devuelve un array con los nombres de las propiedades enumerables propias de un objeto dado. Si la longitud de este array es 0, el objeto está vacío.

function isEmpty(obj) { return Object.keys(obj).length === 0; } console.log(isEmpty({})); // true console.log(isEmpty({ a: 1 })); // false

Invertir una Cadena de Texto

Escribe una función para invertir una cadena de texto dada.

Explicación: La forma más sencilla es convertir la cadena en un array de caracteres, usar el método reverse() incorporado para arrays, y luego unir los caracteres de nuevo en una cadena.

function reverseString(str) { return str.split('').reverse().join(''); } console.log(reverseString('hello')); // 'olleh'

Comprobación de Palíndromo

Escribe una función que compruebe si una cadena dada es un palíndromo.

Explicación: Un palíndromo es una palabra o frase que se lee igual hacia adelante que hacia atrás. Podemos comprobar esto invirtiendo la cadena (ignorando mayúsculas/minúsculas y caracteres no alfanuméricos para una comprobación más robusta, pero aquí hacemos una comprobación simple) y comparándola con la original.

function isPalindrome(str) { const reversed = str.split('').reverse().join(''); return str === reversed; } console.log(isPalindrome('racecar')); // true console.log(isPalindrome('hello')); // false

Encontrar el Número Máximo en un Array

Escribe una función para encontrar el número más grande en un array de números.

Explicación: Puedes iterar a través del array, manteniendo un registro del número más grande encontrado hasta ahora. Alternativamente, puedes usar la función Math.max() junto con la sintaxis de propagación (...) para pasar los elementos del array como argumentos.

function findMaxNumber(arr) { if (arr.length === 0) return undefined; // O lanzar un error return Math.max(...arr); } console.log(findMaxNumber([1, 5, 2, 9, 3])); // 9

FizzBuzz

Escribe un programa que imprima números del 1 al n. Pero para los múltiplos de tres, imprime 'Fizz' en lugar del número, y para los múltiplos de cinco, imprime 'Buzz'. Para los números que son múltiplos de tres y de cinco, imprime 'FizzBuzz'.

Explicación: Este problema clásico evalúa la lógica básica de bucles y condicionales. Necesitas iterar del 1 al n y usar el operador de módulo (%) para comprobar la divisibilidad por 3 y 5.

function fizzBuzz(n) { for (let i = 1; i <= n; i++) { if (i % 3 === 0 && i % 5 === 0) { console.log('FizzBuzz'); } else if (i % 3 === 0) { console.log('Fizz'); } else if (i % 5 === 0) { console.log('Buzz'); } else { console.log(i); } } } fizzBuzz(15);

Eliminar Duplicados de un Array

Escribe una función que elimine los elementos duplicados de un array.

Explicación: Una forma moderna y concisa de lograr esto es usando un Set. Los Sets solo almacenan valores únicos. Puedes convertir el array a un Set y luego de nuevo a un array.

function removeDuplicates(arr) { return [...new Set(arr)]; } console.log(removeDuplicates([1, 2, 2, 3, 4, 4, 5])); // [1, 2, 3, 4, 5]

Comprobación de Anagramas

Escribe una función para comprobar si dos cadenas son anagramas entre sí.

Explicación: Los anagramas son palabras formadas al reorganizar las letras de otra. Para comprobarlo, podemos limpiar, ordenar y comparar las cadenas. La limpieza implica eliminar caracteres no alfanuméricos y convertir a un mismo formato (mayúsculas/minúsculas).

function isAnagram(str1, str2) { const clean = (str) => str.replace(/[^a-z0-9]/gi, '').toLowerCase(); const sorted = (str) => clean(str).split('').sort().join(''); return sorted(str1) === sorted(str2); } console.log(isAnagram('listen', 'silent')); // true console.log(isAnagram('hello', 'world')); // false

Calcular Factorial

Escribe una función para calcular el factorial de un entero no negativo.

Explicación: El factorial (n!) de un número es el producto de todos los enteros positivos menores o iguales a n. Podemos calcular esto iterativamente multiplicando números del 1 hasta n.

function factorial(n) { if (n < 0) return undefined; // El factorial no está definido para números negativos if (n === 0) return 1; let result = 1; for (let i = 1; i <= n; i++) { result *= i; } return result; } console.log(factorial(5)); // 120

Sumar Todos los Números en un Array

Escribe una función que devuelva la suma de todos los números en un array.

Explicación: Puedes usar el método reduce, que es ideal para acumular un único valor de un array. Toma una función de callback y un valor inicial (0 para la suma).

function sumArray(arr) { return arr.reduce((accumulator, currentValue) => accumulator + currentValue, 0); } console.log(sumArray([1, 2, 3, 4])); // 10

Aplanar un Array Anidado

Escribe una función para aplanar un array anidado. Para simplificar, asume solo un nivel de anidación.

Explicación: Para un solo nivel de anidación, puedes usar Array.prototype.concat() con el operador de propagación o el método flat() (ES2019).

function flattenArray(arr) { // Usando flat() (más simple si ES2019+ es aceptable) // return arr.flat(); // Usando reduce y concat return arr.reduce((acc, val) => acc.concat(val), []); } console.log(flattenArray([1, [2, 3], 4, [5]])); // [1, 2, 3, 4, 5]

Contar Vocales en una Cadena

Escribe una función que cuente el número de vocales (a, e, i, o, u) en una cadena dada.

Explicación: Itera a través de la cadena (sin distinción de mayúsculas/minúsculas) y comprueba si cada carácter es una vocal. Mantén un contador.

function countVowels(str) { const vowels = 'aeiou'; let count = 0; for (let char of str.toLowerCase()) { if (vowels.includes(char)) { count++; } } return count; } console.log(countVowels('Hello World')); // 3

Capitalizar la Primera Letra de Cada Palabra en una Oración

Escribe una función que convierta una oración a formato de título (la primera letra de cada palabra capitalizada).

Explicación: Divide la oración en palabras, luego itera a través de cada palabra, capitalizando la primera letra y haciendo el resto minúsculas. Finalmente, une las palabras de nuevo.

function titleCase(str) { return str.toLowerCase().split(' ').map(word => { return word.charAt(0).toUpperCase() + word.slice(1); }).join(' '); } console.log(titleCase('i am a little tea pot')); // 'I Am A Little Tea Pot'

Problema de las Dos Sumas

Dado un array de enteros nums y un entero target, devuelve los índices de los dos números tales que sumen target.

Explicación: Un enfoque común es usar un mapa hash (o un objeto JavaScript) para almacenar números y sus índices a medida que iteras. Para cada número, comprueba si target - numero_actual existe en el mapa.

function twoSum(nums, target) { const map = {}; for (let i = 0; i < nums.length; i++) { const complement = target - nums[i]; if (map[complement] !== undefined) { return [map[complement], i]; } map[nums[i]] = i; } return []; // O null, o lanzar error si no hay solución } console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]

Implementar un Contador Usando Clausuras

Crea una función que devuelva una función de contador. Cada vez que se llame a la función devuelta, debe incrementar y devolver un conteo.

Explicación: Esto demuestra las clausuras. La función interna tiene acceso a la variable count del ámbito de la función externa, incluso después de que la función externa haya terminado de ejecutarse.

function createCounter() { let count = 0; return function() { count++; return count; }; } const counter1 = createCounter(); console.log(counter1()); // 1 console.log(counter1()); // 2 const counter2 = createCounter(); console.log(counter2()); // 1

Encontrar el Primer Carácter No Repetido

Escribe una función que encuentre el primer carácter no repetido en una cadena.

Explicación: Puedes usar un mapa hash para contar las frecuencias de los caracteres. Primero, itera a través de la cadena para construir el mapa de frecuencias. Luego, itera a través de la cadena nuevamente y devuelve el primer carácter con un conteo de 1.

function firstNonRepeatingChar(str) { const charCount = {}; for (const char of str) { charCount[char] = (charCount[char] || 0) + 1; } for (const char of str) { if (charCount[char] === 1) { return char; } } return null; // O algún indicador si todos se repiten } console.log(firstNonRepeatingChar('aabbcdeeff')); // 'c' console.log(firstNonRepeatingChar('swiss')); // 'w'

Segmentación de Arrays (Array Chunking)

Escribe una función que divida un array en grupos de un tamaño especificado.

Explicación: Itera a través del array, tomando 'rebanadas' del tamaño especificado y empujándolas a un nuevo array. Maneja el caso en que el último segmento podría ser más pequeño.

function chunkArray(arr, size) { const chunked = []; let index = 0; while (index < arr.length) { chunked.push(arr.slice(index, index + size)); index += size; } return chunked; } console.log(chunkArray([1, 2, 3, 4, 5, 6, 7], 3)); // [[1, 2, 3], [4, 5, 6], [7]]

Comprobar si un Número es Primo

Escribe una función para determinar si un número dado es un número primo.

Explicación: Un número primo es un número natural mayor que 1 que no tiene divisores positivos aparte del 1 y de sí mismo. Itera desde 2 hasta la raíz cuadrada del número, comprobando la divisibilidad. Maneja casos límite como 1 y 2.

function isPrime(num) { if (num <= 1) return false; if (num <= 3) return true; if (num % 2 === 0 || num % 3 === 0) return false; for (let i = 5; i * i <= num; i = i + 6) { if (num % i === 0 || num % (i + 2) === 0) return false; } return true; } console.log(isPrime(7)); // true console.log(isPrime(10)); // false

Encontrar la Palabra Más Larga en una Cadena

Escribe una función que encuentre la palabra más larga en una oración.

Explicación: Divide la cadena en un array de palabras. Luego, itera a través del array, manteniendo un registro de la palabra más larga encontrada hasta ahora (o su longitud).

function findLongestWord(str) { const words = str.split(' '); let longestWord = ''; for (const word of words) { // Limpiar palabras si es necesario (ej. eliminar puntuación) if (word.length > longestWord.length) { longestWord = word; } } return longestWord; } console.log(findLongestWord('The quick brown fox jumped over the lazy dog')); // 'jumped'

Implementar `Array.prototype.map`

Implementa tu propia versión de la función Array.prototype.map.

Explicación: La función map toma una función de callback y crea un nuevo array aplicando la función de callback a cada elemento del array original. Tu función debe iterar a través del array y construir el nuevo array.

function myMap(arr, callback) { const mappedArray = []; for (let i = 0; i < arr.length; i++) { mappedArray.push(callback(arr[i], i, arr)); } return mappedArray; } const numbers = [1, 4, 9]; const roots = myMap(numbers, Math.sqrt); console.log(roots); // [1, 2, 3]

Implementar `Array.prototype.filter`

Implementa tu propia versión de la función Array.prototype.filter.

Explicación: La función filter toma una función de callback y crea un nuevo array con todos los elementos que pasan la prueba implementada por la función de callback proporcionada.

function myFilter(arr, callback) { const filteredArray = []; for (let i = 0; i < arr.length; i++) { if (callback(arr[i], i, arr)) { filteredArray.push(arr[i]); } } return filteredArray; } const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present']; const result = myFilter(words, word => word.length > 6); console.log(result); // ['exuberant', 'destruction', 'present']

Implementar `Array.prototype.reduce`

Implementa tu propia versión de la función Array.prototype.reduce.

Explicación: La función reduce ejecuta una función de callback 'reductora' proporcionada por el usuario en cada elemento del array, pasando el valor de retorno del cálculo del elemento precedente. El resultado final de ejecutar el reductor a través de todos los elementos del array es un único valor. Maneja el argumento del valor inicial.

function myReduce(arr, callback, initialValue) { let accumulator = initialValue; let startIndex = 0; if (initialValue === undefined) { if (arr.length === 0) throw new TypeError('Reduce de un array vacío sin valor inicial'); accumulator = arr[0]; startIndex = 1; } for (let i = startIndex; i < arr.length; i++) { accumulator = callback(accumulator, arr[i], i, arr); } return accumulator; } const array1 = [1, 2, 3, 4]; const sum = myReduce(array1, (acc, curr) => acc + curr, 0); console.log(sum); // 10

Memoización - Secuencia de Fibonacci

Implementa una función de Fibonacci usando memoización para optimizar el rendimiento.

Explicación: La secuencia de Fibonacci implica cálculos repetidos. La memoización almacena los resultados de llamadas a funciones costosas y devuelve el resultado en caché cuando las mismas entradas ocurren nuevamente.

function memoizedFib() { const cache = {}; function fib(n) { if (n in cache) { return cache[n]; } if (n <= 1) { return n; } const result = fib(n - 1) + fib(n - 2); cache[n] = result; return result; } return fib; } const fibonacci = memoizedFib(); console.log(fibonacci(10)); // 55 console.log(fibonacci(40)); // 102334155 (mucho más rápido que sin memoización)

Comprobar Corchetes Balanceados

Escribe una función para comprobar si una cadena que contiene corchetes {}[]() está balanceada.

Explicación: Usa una pila (stack). Cuando encuentres un corchete de apertura, empújala a la pila. Cuando encuentres un corchete de cierre, comprueba si la pila está vacía o si el elemento superior es el corchete de apertura correspondiente. Si coincide, saca el elemento de la pila. Si no, o si la pila está vacía, está desequilibrada. Al final, una pila vacía significa que está balanceada.

function isBalanced(str) { const stack = []; const map = { '(': ')', '{': '}', '[': ']' }; for (let char of str) { if (map[char]) { stack.push(char); } else if (Object.values(map).includes(char)) { if (stack.length === 0) return false; const lastOpen = stack.pop(); if (map[lastOpen] !== char) return false; } } return stack.length === 0; } console.log(isBalanced('({[]})')); // true console.log(isBalanced('([)]')); // false

Implementar una Cola Simple

Implementa una estructura de datos de Cola (Queue) con los métodos enqueue (añadir al final) y dequeue (eliminar del principio).

Explicación: Una cola sigue el principio FIFO (First-In, First-Out). Se puede usar un array, con push para enqueue y shift para dequeue.

class Queue { constructor() { this.items = []; } enqueue(element) { this.items.push(element); } dequeue() { if (this.isEmpty()) return 'Underflow'; return this.items.shift(); } front() { if (this.isEmpty()) return 'No elements in Queue'; return this.items[0]; } isEmpty() { return this.items.length === 0; } } const q = new Queue(); q.enqueue(10); q.enqueue(20); console.log(q.dequeue()); // 10 console.log(q.front()); // 20

Implementar una Pila Simple

Implementa una estructura de datos de Pila (Stack) con los métodos push (añadir a la cima) y pop (eliminar de la cima).

Explicación: Una pila sigue el principio LIFO (Last-In, First-Out). Se puede usar un array, con los métodos push y pop.

class Stack { constructor() { this.items = []; } push(element) { this.items.push(element); } pop() { if (this.isEmpty()) return 'Underflow'; return this.items.pop(); } peek() { return this.items[this.items.length - 1]; } isEmpty() { return this.items.length === 0; } } const s = new Stack(); s.push(10); s.push(20); console.log(s.pop()); // 20 console.log(s.peek()); // 10

Encontrar el Número Faltante en la Secuencia

Dado un array que contiene n números distintos tomados de 0, 1, 2, ..., n, encuentra el que falta en el array.

Explicación: La suma de los números del 0 al n se puede calcular usando la fórmula n*(n+1)/2. La suma real de los elementos del array se puede calcular. La diferencia entre estas dos sumas será el número que falta.

function findMissingNumber(nums) { const n = nums.length; const expectedSum = n * (n + 1) / 2; const actualSum = nums.reduce((sum, num) => sum + num, 0); return expectedSum - actualSum; } console.log(findMissingNumber([3, 0, 1])); // 2 console.log(findMissingNumber([9, 6, 4, 2, 3, 5, 7, 0, 1])); // 8

Función Debounce

Implementa una función debounce. Debounce asegura que una función no se llame de nuevo hasta que haya pasado una cierta cantidad de tiempo sin que se haya llamado.

Explicación: Usa setTimeout y clearTimeout. Cada vez que se llama a la función debounced, borra el timeout anterior y establece uno nuevo. La llamada a la función real solo ocurre cuando el timeout se completa.

function debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func.apply(this, args); }, delay); }; } // Ejemplo de uso: const sayHello = () => console.log('Hello!'); const debouncedHello = debounce(sayHello, 1000); debouncedHello(); // Se llama después de 1 segundo (si no se vuelve a llamar) debouncedHello(); // Reinicia el temporizador debouncedHello(); // Reinicia el temporizador, se ejecutará 1 segundo después de esta llamada.

Función Throttle

Implementa una función throttle. Throttle asegura que una función se llame como máximo una vez en un intervalo de tiempo especificado.

Explicación: Usa un flag para indicar si se permite una llamada. Cuando se llama, si se permite, ejecuta la función, establece el flag en falso y usa setTimeout para restablecer el flag después del intervalo.

function throttle(func, limit) { let inThrottle = false; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // Ejemplo de uso: const logScroll = () => console.log('Scrolling...'); const throttledScroll = throttle(logScroll, 1000); // window.addEventListener('scroll', throttledScroll); // Registrará como máximo una vez por segundo.

Función Currying

Escribe una función que tome una función con dos argumentos y devuelva una versión currificada de la misma.

Explicación: Currying transforma una función con múltiples argumentos en una secuencia de funciones, cada una tomando un solo argumento. f(a, b) se convierte en f(a)(b).

function curry(fn) { return function(a) { return function(b) { return fn(a, b); }; }; } function add(a, b) { return a + b; } const curriedAdd = curry(add); const add5 = curriedAdd(5); console.log(add5(3)); // 8 console.log(curriedAdd(10)(20)); // 30

Clonación Profunda de un Objeto

Escribe una función para realizar una clonación profunda de un objeto JavaScript.

Explicación: Un clon superficial copia solo las propiedades de nivel superior. Un clon profundo copia recursivamente todas las propiedades, incluyendo objetos y arrays anidados. Una forma simple (con limitaciones, por ejemplo, no maneja bien funciones, fechas, expresiones regulares) es usando JSON.parse(JSON.stringify(obj)). Un enfoque recursivo es más robusto.

function deepClone(obj) { // Versión simple (con limitaciones) try { return JSON.parse(JSON.stringify(obj)); } catch (e) { console.error("Clonación fallida:", e); return null; } // Versión recursiva más robusta (ejemplo básico): /* if (obj === null || typeof obj !== 'object') { return obj; } let clone = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { clone[key] = deepClone(obj[key]); } } return clone; */ } const original = { a: 1, b: { c: 2 } }; const cloned = deepClone(original); cloned.b.c = 3; console.log(original.b.c); // 2 (demuestra que es un clon profundo) console.log(cloned.b.c); // 3

Obtener Claves de un Objeto

¿Cómo obtener un array de claves de un objeto?

Explicación: Usa Object.keys(obj).

function getKeys(obj) { return Object.keys(obj); } console.log(getKeys({a: 1, b: 2})); // ['a', 'b']

Obtener Valores de un Objeto

¿Cómo obtener un array de valores de un objeto?

Explicación: Usa Object.values(obj).

function getValues(obj) { return Object.values(obj); } console.log(getValues({a: 1, b: 2})); // [1, 2]

Comprobar si un Array Incluye un Valor

¿Cómo comprobar si un array contiene un valor específico?

Explicación: Usa Array.prototype.includes(value).

function checkIncludes(arr, val) { return arr.includes(val); } console.log(checkIncludes([1, 2, 3], 2)); // true

Fusionar Dos Arrays

¿Cómo fusionar dos arrays en uno?

Explicación: Usa la sintaxis de propagación (...) o Array.prototype.concat().

function mergeArrays(arr1, arr2) { return [...arr1, ...arr2]; } console.log(mergeArrays([1, 2], [3, 4])); // [1, 2, 3, 4]

Explicar 'this' en el Ámbito Global

¿A qué se refiere this en el ámbito global en un navegador?

Explicación: En el contexto de ejecución global (fuera de cualquier función), this se refiere al objeto global, que es window en los navegadores web.

console.log(this === window); // true (en un entorno de navegador)

Explicar 'this' en un Método de Objeto

¿A qué se refiere this cuando se usa dentro de un método de un objeto?

Explicación: Cuando una función se llama como método de un objeto, this se refiere al objeto sobre el que se llama el método.

const myObject = { prop: 'Hello', greet() { return this.prop; } }; console.log(myObject.greet()); // 'Hello'

Explicar 'this' con Funciones Flecha

¿Cómo manejan las funciones flecha a this?

Explicación: Las funciones flecha no tienen su propio contexto this. En su lugar, heredan this del ámbito circundante (léxico) donde se definen.

function MyClass() { this.value = 42; setTimeout(() => { console.log(this.value); // Muestra 42 porque 'this' es heredado }, 100); } new MyClass();

Diferencia entre `let`, `const` y `var`

¿Cuáles son las principales diferencias entre let, const y var?

Explicación: var tiene un ámbito de función y se eleva (hoisted). let y const tienen un ámbito de bloque y se elevan (hoisted), pero están en una 'zona muerta temporal' hasta la declaración. const debe ser inicializada y no puede ser reasignada.

function scopeTest() { var a = 1; let b = 2; const c = 3; if (true) { var a = 10; // Redeclara y afecta a 'a' exterior let b = 20; // Nueva 'b' dentro del bloque // const c = 30; // Sería una nueva 'c' console.log(a, b, c); // 10, 20, 3 } console.log(a, b, c); // 10, 2, 3 } scopeTest();

¿Qué es una Promesa?

Explica qué es una Promesa en JavaScript.

Explicación: Una Promesa es un objeto que representa la eventual finalización (o falla) de una operación asíncrona y su valor resultante. Puede estar en uno de tres estados: pendiente, cumplida (fulfilled) o rechazada (rejected).

const myPromise = new Promise((resolve, reject) => { setTimeout(() => { resolve('¡Éxito!'); // reject('¡Error!'); }, 1000); }); myPromise .then(result => console.log(result)) .catch(error => console.error(error));

Usando `async/await`

¿Cómo simplifican async y await el trabajo con Promesas?

Explicación: async/await proporciona una sintaxis más sencilla sobre las Promesas, haciendo que el código asíncrono se vea y se comporte un poco más como código síncrono. Una función async siempre devuelve una Promesa, y await pausa la ejecución hasta que una Promesa se resuelva o rechace.

function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function run() { console.log('Iniciando...'); await delay(1000); console.log('Esperó 1 segundo.'); await delay(500); console.log('Esperó otros 0.5 segundos.'); return '¡Terminado!'; } run().then(console.log);

Convertir Cadena a Número

¿Cómo convertir una cadena de texto a un número?

Explicación: Usa parseInt(), parseFloat() o el operador unario más (+).

const str = '123.45'; console.log(parseInt(str)); // 123 console.log(parseFloat(str)); // 123.45 console.log(+str); // 123.45

Convertir Número a Cadena

¿Cómo convertir un número a una cadena de texto?

Explicación: Usa String(), number.toString() o la concatenación de cadenas.

const num = 123; console.log(String(num)); // '123' console.log(num.toString()); // '123' console.log('' + num); // '123'

¿Qué es `JSON.stringify`?

¿Qué hace JSON.stringify?

Explicación: Convierte un objeto o valor de JavaScript a una cadena JSON.

const obj = { name: 'Alice', age: 30 }; const jsonString = JSON.stringify(obj); console.log(jsonString); // '{"name":"Alice","age":30}'

¿Qué es `JSON.parse`?

¿Qué hace JSON.parse?

Explicación: Analiza una cadena JSON, construyendo el valor u objeto JavaScript descrito por la cadena.

const jsonString = '{"name":"Alice","age":30}'; const obj = JSON.parse(jsonString); console.log(obj.name); // 'Alice'

Operador de Propagación en Arrays

¿Cómo se usa el operador de propagación (spread operator) con arrays?

Explicación: Permite que un iterable (como un array) se expanda en lugares donde se esperan cero o más argumentos o elementos. Útil para copiar, fusionar y añadir elementos.

const arr1 = [1, 2]; const arr2 = [3, 4]; const combined = [...arr1, ...arr2]; // [1, 2, 3, 4] const copy = [...arr1]; // [1, 2]

Operador de Propagación en Objetos

¿Cómo se usa el operador de propagación (spread operator) con objetos?

Explicación: Permite copiar y fusionar propiedades de objetos.

const obj1 = { a: 1, b: 2 }; const obj2 = { b: 3, c: 4 }; const merged = { ...obj1, ...obj2 }; // { a: 1, b: 3, c: 4 } const copy = { ...obj1 }; // { a: 1, b: 2 }

Desestructuración de Arrays

Explica la desestructuración de arrays con un ejemplo.

Explicación: Es una sintaxis que permite desempaquetar valores de arrays en variables distintas.

const [a, b] = [10, 20]; console.log(a); // 10 console.log(b); // 20 const [x, , z] = [1, 2, 3]; console.log(z); // 3

Desestructuración de Objetos

Explica la desestructuración de objetos con un ejemplo.

Explicación: Permite desempaquetar propiedades de objetos en variables distintas.

const person = { name: 'Bob', age: 25 }; const { name, age } = person; console.log(name); // 'Bob' console.log(age); // 25 const { name: personName } = person; console.log(personName); // 'Bob'

Implementar `call`

¿Cómo podrías implementar una versión básica de Function.prototype.call?

Explicación: call invoca una función con un valor this especificado y argumentos proporcionados individualmente. Puedes adjuntar la función al contexto this, llamarla y luego eliminarla.

Function.prototype.myCall = function(context, ...args) { context = context || window; const uniqueId = Symbol(); // Usar una clave única context[uniqueId] = this; const result = context[uniqueId](...args); delete context[uniqueId]; return result; } function greet(greeting, punctuation) { console.log(`${greeting}, ${this.name}${punctuation}`); } greet.myCall({ name: 'Charlie' }, 'Hi', '!'); // Hi, Charlie!

Implementar `apply`

¿Cómo podrías implementar una versión básica de Function.prototype.apply?

Explicación: apply es similar a call, pero acepta los argumentos como un array.

Function.prototype.myApply = function(context, argsArray) { context = context || window; const uniqueId = Symbol(); context[uniqueId] = this; const result = context[uniqueId](...(argsArray || [])); delete context[uniqueId]; return result; } function greet(greeting, punctuation) { console.log(`${greeting}, ${this.name}${punctuation}`); } greet.myApply({ name: 'David' }, ['Hello', '.']); // Hello, David.

Explicar el Bucle de Eventos (Event Loop)

Explica brevemente el Bucle de Eventos de JavaScript.

Explicación: JavaScript es de un solo hilo, pero logra la concurrencia utilizando un bucle de eventos. La pila de llamadas maneja el código síncrono. Las Web APIs manejan operaciones asíncronas (como setTimeout, fetch). Cuando una operación asíncrona termina, su callback va a la cola de callbacks (o cola de microtareas para las Promesas). El bucle de eventos comprueba constantemente si la pila de llamadas está vacía; si lo está, mueve el siguiente callback de la cola a la pila para su ejecución.

console.log('Inicio'); setTimeout(() => { console.log('Callback de Timeout'); // Va a la Cola de Callbacks }, 0); Promise.resolve().then(() => { console.log('Promesa Resuelta'); // Va a la Cola de Microtareas }); console.log('Fin'); // Orden de Salida: Inicio, Fin, Promesa Resuelta, Callback de Timeout // (Las Microtareas se ejecutan antes que las Macrotareas/Callbacks)

Búsqueda Binaria

Implementa una función de búsqueda binaria para un array ordenado.

Explicación: La búsqueda binaria encuentra eficientemente un elemento en un array ordenado dividiendo repetidamente el intervalo de búsqueda por la mitad. Si el valor de la clave de búsqueda es menor que el elemento en el medio del intervalo, se reduce el intervalo a la mitad inferior. De lo contrario, se reduce a la mitad superior. Esto se hace hasta que se encuentra el valor o el intervalo está vacío.

function binarySearch(sortedArray, key) { let start = 0; let end = sortedArray.length - 1; while (start <= end) { let middle = Math.floor((start + end) / 2); if (sortedArray[middle] === key) { return middle; // Encontrado } else if (sortedArray[middle] < key) { start = middle + 1; // Buscar en la mitad derecha } else { end = middle - 1; // Buscar en la mitad izquierda } } return -1; // No encontrado } console.log(binarySearch([1, 3, 5, 7, 9, 11], 7)); // 3 console.log(binarySearch([1, 3, 5, 7, 9, 11], 2)); // -1

Merge Sort (Ordenación por Mezcla)

Implementa el algoritmo Merge Sort.

Explicación: Merge Sort es un algoritmo de divide y vencerás. Divide el array de entrada en dos mitades, se llama a sí mismo para las dos mitades y luego fusiona las dos mitades ordenadas. La función de fusión es crucial; combina dos sub-arrays ordenados en un array ordenado.

function mergeSort(arr) { if (arr.length <= 1) return arr; const mid = Math.floor(arr.length / 2); const left = mergeSort(arr.slice(0, mid)); const right = mergeSort(arr.slice(mid)); return merge(left, right); } function merge(left, right) { let result = []; let leftIndex = 0; let rightIndex = 0; while (leftIndex < left.length && rightIndex < right.length) { if (left[leftIndex] < right[rightIndex]) { result.push(left[leftIndex]); leftIndex++; } else { result.push(right[rightIndex]); rightIndex++; } } return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex)); } console.log(mergeSort([38, 27, 43, 3, 9, 82, 10])); // [3, 9, 10, 27, 38, 43, 82]

Quick Sort (Ordenación Rápida)

Implementa el algoritmo Quick Sort.

Explicación: Quick Sort también es un algoritmo de divide y vencerás. Elige un elemento como pivote y particiona el array dado alrededor del pivote elegido. Los elementos más pequeños que el pivote van a la izquierda, y los elementos más grandes van a la derecha. Luego ordena recursivamente los sub-arrays.

function quickSort(arr) { if (arr.length <= 1) return arr; const pivot = arr[arr.length - 1]; const left = []; const right = []; for (let i = 0; i < arr.length - 1; i++) { if (arr[i] < pivot) { left.push(arr[i]); } else { right.push(arr[i]); } } return [...quickSort(left), pivot, ...quickSort(right)]; } console.log(quickSort([10, 8, 2, 1, 6, 3, 9, 4, 7, 5])); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Bubble Sort (Ordenación de Burbuja)

Implementa el algoritmo Bubble Sort.

Explicación: Bubble Sort es un algoritmo de ordenación simple que recorre repetidamente la lista, compara elementos adyacentes y los intercambia si están en el orden incorrecto. El paso a través de la lista se repite hasta que la lista está ordenada.

function bubbleSort(arr) { let n = arr.length; let swapped; do { swapped = false; for (let i = 0; i < n - 1; i++) { if (arr[i] > arr[i + 1]) { [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]]; // Intercambiar swapped = true; } } n--; // Optimización: el último elemento ya está en su lugar } while (swapped); return arr; } console.log(bubbleSort([64, 34, 25, 12, 22, 11, 90])); // [11, 12, 22, 25, 34, 64, 90]

Subcadena Más Larga Sin Caracteres Repetidos

Dada una cadena, encuentra la longitud de la subcadena más larga sin caracteres que se repitan.

Explicación: Usa la técnica de 'ventana deslizante' (sliding window). Mantén una ventana (subcadena) y un conjunto de caracteres en esa ventana. Expande la ventana moviendo el puntero derecho. Si se encuentra un carácter repetido, encoge la ventana desde la izquierda hasta que se elimine el carácter repetido.

function lengthOfLongestSubstring(s) { let maxLength = 0; let start = 0; const charMap = {}; for (let end = 0; end < s.length; end++) { const char = s[end]; if (charMap[char] >= start) { start = charMap[char] + 1; } charMap[char] = end; maxLength = Math.max(maxLength, end - start + 1); } return maxLength; } console.log(lengthOfLongestSubstring('abcabcbb')); // 3 ('abc') console.log(lengthOfLongestSubstring('bbbbb')); // 1 ('b') console.log(lengthOfLongestSubstring('pwwkew')); // 3 ('wke')

Implementar una Lista Enlazada

Implementa una Lista Enlazada Simple con los métodos add y print.

Explicación: Una lista enlazada es una estructura de datos lineal donde los elementos no se almacenan en ubicaciones de memoria contiguas. Cada elemento (nodo) apunta al siguiente. Necesitas una clase Node y una clase LinkedList para gestionar la cabeza y añadir nuevos nodos.

class Node { constructor(data, next = null) { this.data = data; this.next = next; } } class LinkedList { constructor() { this.head = null; } add(data) { const newNode = new Node(data); if (!this.head) { this.head = newNode; } else { let current = this.head; while (current.next) { current = current.next; } current.next = newNode; } } print() { let current = this.head; let list = ''; while (current) { list += current.data + ' -> '; current = current.next; } console.log(list + 'null'); } } const list = new LinkedList(); list.add(10); list.add(20); list.add(30); list.print(); // 10 -> 20 -> 30 -> null

Implementar un Árbol de Búsqueda Binaria (BST)

Implementa un Árbol de Búsqueda Binaria con los métodos insert y find.

Explicación: Un BST es una estructura de datos de árbol binario basada en nodos que tiene las siguientes propiedades: el subárbol izquierdo de un nodo contiene solo nodos con claves menores que la clave del nodo. El subárbol derecho de un nodo contiene solo nodos con claves mayores que la clave del nodo. Ambos subárboles también deben ser árboles de búsqueda binaria.

class Node { constructor(data) { this.data = data; this.left = null; this.right = null; } } class BST { constructor() { this.root = null; } insert(data) { const newNode = new Node(data); if (!this.root) { this.root = newNode; return; } this._insertNode(this.root, newNode); } _insertNode(node, newNode) { if (newNode.data < node.data) { if (!node.left) node.left = newNode; else this._insertNode(node.left, newNode); } else { if (!node.right) node.right = newNode; else this._insertNode(node.right, newNode); } } find(data) { return this._findNode(this.root, data); } _findNode(node, data) { if (!node) return null; if (data < node.data) return this._findNode(node.left, data); else if (data > node.data) return this._findNode(node.right, data); else return node; } } const bst = new BST(); bst.insert(10); bst.insert(5); bst.insert(15); bst.insert(7); console.log(bst.find(7)); // Node { data: 7, left: null, right: null } console.log(bst.find(12)); // null

Rotar un Array

Escribe una función para rotar un array a la derecha k pasos.

Explicación: Rotar un array significa mover sus elementos. Para una rotación a la derecha, el último elemento se convierte en el primero. Repetir esto k veces funciona pero es ineficiente. Una mejor manera es usar la manipulación de arrays o calcular las nuevas posiciones.

function rotateArray(nums, k) { k = k % nums.length; // Manejar casos donde k > longitud if (k === 0) return nums; const partToMove = nums.splice(nums.length - k); nums.unshift(...partToMove); return nums; } console.log(rotateArray([1, 2, 3, 4, 5, 6, 7], 3)); // [5, 6, 7, 1, 2, 3, 4]

Encontrar la Intersección de Dos Arrays

Dados dos arrays, escribe una función para calcular su intersección (elementos comunes a ambos).

Explicación: Un buen enfoque es convertir un array en un Set para búsquedas en tiempo promedio de O(1). Luego, itera a través del segundo array y comprueba si cada elemento existe en el set. Recoge las coincidencias.

function intersection(nums1, nums2) { const set1 = new Set(nums1); const resultSet = new Set(); for (const num of nums2) { if (set1.has(num)) { resultSet.add(num); } } return [...resultSet]; } console.log(intersection([1, 2, 2, 1], [2, 2])); // [2] console.log(intersection([4, 9, 5], [9, 4, 9, 8, 4])); // [9, 4]

Agrupar Anagramas

Dado un array de cadenas, agrupa los anagramas juntos.

Explicación: La clave es encontrar una firma única para cada grupo de anagramas. Una forma común es ordenar cada palabra alfabéticamente. Las palabras que resultan en la misma cadena ordenada son anagramas. Usa un mapa hash donde las claves son palabras ordenadas y los valores son arrays de palabras originales.

function groupAnagrams(strs) { const map = {}; for (const str of strs) { const sortedStr = str.split('').sort().join(''); if (!map[sortedStr]) { map[sortedStr] = []; } map[sortedStr].push(str); } return Object.values(map); } console.log(groupAnagrams(['eat', 'tea', 'tan', 'ate', 'nat', 'bat'])); // Salida: [['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']]

Mover Ceros al Final

Dado un array nums, escribe una función para mover todos los 0 al final, manteniendo el orden relativo de los elementos no nulos.

Explicación: Usa un enfoque de 'dos punteros' o 'bola de nieve'. Un puntero rastrea la posición donde debe ir el siguiente elemento no nulo. Itera a través del array; si un elemento no es cero, colócalo en la posición del puntero e incrementa el puntero. Finalmente, rellena el resto con ceros.

function moveZeroes(nums) { let nonZeroIndex = 0; // Mover todos los elementos no nulos al principio for (let i = 0; i < nums.length; i++) { if (nums[i] !== 0) { nums[nonZeroIndex] = nums[i]; nonZeroIndex++; } } // Rellenar el resto con ceros for (let i = nonZeroIndex; i < nums.length; i++) { nums[i] = 0; } return nums; } console.log(moveZeroes([0, 1, 0, 3, 12])); // [1, 3, 12, 0, 0]

Subarray de Suma Máxima (Algoritmo de Kadane)

Dado un array de enteros nums, encuentra el subarray contiguo (que contenga al menos un número) que tenga la suma más grande y devuelve su suma.

Explicación: El algoritmo de Kadane es una forma eficiente de resolver esto. Itera a través del array, manteniendo un registro de la suma currentMax que termina en la posición actual y la suma globalMax encontrada hasta ahora. Si currentMax se vuelve negativo, reinícialo a 0 (o mejor dicho, al elemento actual).

function maxSubArray(nums) { let globalMax = -Infinity; let currentMax = 0; for (let i = 0; i < nums.length; i++) { currentMax = Math.max(nums[i], currentMax + nums[i]); if (currentMax > globalMax) { globalMax = currentMax; } } return globalMax; } console.log(maxSubArray([-2, 1, -3, 4, -1, 2, 1, -5, 4])); // 6 (de [4, -1, 2, 1])

Permutaciones de una Cadena

Escribe una función para generar todas las permutaciones de una cadena dada.

Explicación: Esto se resuelve típicamente usando recursión y backtracking. Para cada carácter, fíjalo y genera recursivamente las permutaciones para el resto de la cadena. Caso base: cuando la cadena tiene un solo carácter, devuélvela.

function stringPermutations(str) { if (str.length === 0) return ['']; if (str.length === 1) return [str]; const results = []; for (let i = 0; i < str.length; i++) { const char = str[i]; const remainingChars = str.slice(0, i) + str.slice(i + 1); const perms = stringPermutations(remainingChars); for (const perm of perms) { results.push(char + perm); } } return [...new Set(results)]; // Usar Set para manejar caracteres duplicados si es necesario } console.log(stringPermutations('abc')); // ['abc', 'acb', 'bac', 'bca', 'cab', 'cba'] console.log(stringPermutations('aab')); // ['aab', 'aba', 'baa']

Máximo Común Divisor (MCD)

Escribe una función para encontrar el Máximo Común Divisor (MCD) de dos números.

Explicación: El algoritmo de Euclides es un método eficiente. Si b es 0, a es el MCD. De lo contrario, el MCD de a y b es el mismo que el MCD de b y a % b (el resto de a dividido por b).

function gcd(a, b) { while (b !== 0) { let temp = b; b = a % b; a = temp; } return a; } console.log(gcd(48, 18)); // 6 console.log(gcd(101, 103)); // 1

Mínimo Común Múltiplo (MCM)

Escribe una función para encontrar el Mínimo Común Múltiplo (MCM) de dos números.

Explicación: El MCM se puede calcular usando el MCD con la fórmula: MCM(a, b) = |a * b| / MCD(a, b).

function gcd(a, b) { // Helper del problema anterior while (b !== 0) { let temp = b; b = a % b; a = temp; } return a; } function lcm(a, b) { if (a === 0 || b === 0) return 0; return Math.abs(a * b) / gcd(a, b); } console.log(lcm(15, 20)); // 60 console.log(lcm(7, 5)); // 35

Implementar Promise.all

Implementa una función que se comporte como Promise.all.

Explicación: Promise.all toma un array de promesas y devuelve una única promesa que se resuelve cuando todas las promesas de entrada se han resuelto, o se rechaza si alguna promesa de entrada se rechaza. El valor resuelto es un array de los valores resueltos.

function myPromiseAll(promises) { return new Promise((resolve, reject) => { const results = []; let completedCount = 0; const numPromises = promises.length; if (numPromises === 0) { resolve([]); return; } promises.forEach((promise, index) => { Promise.resolve(promise) .then(value => { results[index] = value; completedCount++; if (completedCount === numPromises) { resolve(results); } }) .catch(reject); // Rechazar inmediatamente ante cualquier error }); }); } // Ejemplo de uso: const p1 = Promise.resolve(3); const p2 = 42; const p3 = new Promise((resolve) => setTimeout(resolve, 100, 'foo')); myPromiseAll([p1, p2, p3]).then(values => console.log(values)); // [3, 42, 'foo']

Invertir Árbol Binario

Escribe una función para invertir un árbol binario.

Explicación: Para invertir un árbol binario, intercambias los hijos izquierdo y derecho de cada nodo. Esto se puede hacer de forma recursiva o iterativa (usando una cola o pila).

class Node { constructor(val, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function invertTree(root) { if (root === null) { return null; } // Intercambiar los hijos [root.left, root.right] = [root.right, root.left]; // Recorrer recursivamente los hijos izquierdo y derecho invertTree(root.left); invertTree(root.right); return root; } // Ejemplo: 4 -> [2, 7] -> [1, 3, 6, 9] // Se convierte en: 4 -> [7, 2] -> [9, 6, 3, 1]

Profundidad Máxima de un Árbol Binario

Dado un árbol binario, encuentra su profundidad máxima.

Explicación: La profundidad máxima es el número de nodos a lo largo del camino más largo desde el nodo raíz hasta el nodo hoja más lejano. Esto se puede encontrar recursivamente: ProfundidadMáxima = 1 + max(ProfundidadMáxima(izquierda), ProfundidadMáxima(derecha)). El caso base es cuando un nodo es nulo, su profundidad es 0.

class Node { constructor(val, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function maxDepth(root) { if (root === null) { return 0; } const leftDepth = maxDepth(root.left); const rightDepth = maxDepth(root.right); return Math.max(leftDepth, rightDepth) + 1; } // Ejemplo: 3 -> [9, 20] -> [null, null, 15, 7] const tree = new Node(3, new Node(9), new Node(20, new Node(15), new Node(7))); console.log(maxDepth(tree)); // 3

Mejor Momento para Comprar/Vender Acciones

Se le da un array prices donde prices[i] es el precio de una acción dada en el día i. Quiere maximizar su ganancia eligiendo un solo día para comprar una acción y eligiendo un día diferente en el futuro para vender esa acción. Devuelve la ganancia máxima.

Explicación: Itera a través de los precios, manteniendo un registro del precio mínimo encontrado hasta ahora (minPrice) y la ganancia máxima encontrada hasta ahora (maxProfit). Para cada día, calcula la ganancia potencial si vendieras hoy (precio actual - minPrice) y actualiza maxProfit si es mayor.

function maxProfit(prices) { let minPrice = Infinity; let maxProfit = 0; for (let i = 0; i < prices.length; i++) { if (prices[i] < minPrice) { minPrice = prices[i]; } else if (prices[i] - minPrice > maxProfit) { maxProfit = prices[i] - minPrice; } } return maxProfit; } console.log(maxProfit([7, 1, 5, 3, 6, 4])); // 5 (Comprar en 1, Vender en 6) console.log(maxProfit([7, 6, 4, 3, 1])); // 0 (No es posible obtener ganancias)

Número Único

Dado un array no vacío de enteros nums, cada elemento aparece dos veces excepto uno. Encuentra ese único elemento.

Explicación: Una forma muy eficiente de resolver esto es usando el operador bit a bit XOR (^). Hacer un XOR de un número consigo mismo da como resultado 0. Hacer un XOR de un número con 0 da como resultado el número mismo. Si haces un XOR de todos los números en el array, los pares se cancelarán (se convertirán en 0), dejando solo el número único.

function singleNumber(nums) { let result = 0; for (const num of nums) { result ^= num; } return result; } console.log(singleNumber([2, 2, 1])); // 1 console.log(singleNumber([4, 1, 2, 1, 2])); // 4

Elemento Mayoritario

Dado un array nums de tamaño n, devuelve el elemento mayoritario. El elemento mayoritario es el elemento que aparece más de ⌊n / 2⌋ veces.

Explicación: El Algoritmo de Votación de Boyer-Moore es una forma eficiente. Inicializa un candidato y un contador. Itera a través del array. Si contador es 0, establece el elemento actual como el candidato. Si el elemento actual coincide con el candidato, incrementa contador; de lo contrario, decrementa contador.

function majorityElement(nums) { let candidate = null; let count = 0; for (const num of nums) { if (count === 0) { candidate = num; } count += (num === candidate) ? 1 : -1; } return candidate; } console.log(majorityElement([3, 2, 3])); // 3 console.log(majorityElement([2, 2, 1, 1, 1, 2, 2])); // 2

Subir Escaleras

Estás subiendo una escalera. Se necesitan n escalones para llegar a la cima. Cada vez puedes subir 1 o 2 escalones. ¿De cuántas formas distintas puedes subir a la cima?

Explicación: Este es un problema clásico de programación dinámica, muy similar a la secuencia de Fibonacci. El número de formas de llegar al escalón n es la suma de las formas de llegar al escalón n-1 (tomando 1 escalón) y las formas de llegar al escalón n-2 (tomando 2 escalones). dp[n] = dp[n-1] + dp[n-2].

function climbStairs(n) { if (n <= 2) return n; let oneStepBefore = 2; let twoStepsBefore = 1; let allWays = 0; for (let i = 3; i <= n; i++) { allWays = oneStepBefore + twoStepsBefore; twoStepsBefore = oneStepBefore; oneStepBefore = allWays; } return allWays; } console.log(climbStairs(2)); // 2 (1+1, 2) console.log(climbStairs(3)); // 3 (1+1+1, 1+2, 2+1) console.log(climbStairs(4)); // 5

Producto de Array Excepto Sí Mismo

Dado un array de enteros nums, devuelve un array answer tal que answer[i] sea igual al producto de todos los elementos de nums excepto nums[i]. No uses el operador de división.

Explicación: Puedes resolver esto calculando productos de prefijos y productos de sufijos. El resultado en el índice i es el producto de todos los elementos antes de i multiplicado por el producto de todos los elementos después de i.

function productExceptSelf(nums) { const n = nums.length; const answer = new Array(n).fill(1); // Calcular productos de prefijos let prefix = 1; for (let i = 0; i < n; i++) { answer[i] = prefix; prefix *= nums[i]; } // Calcular productos de sufijos y multiplicar con el prefijo let suffix = 1; for (let i = n - 1; i >= 0; i--) { answer[i] *= suffix; suffix *= nums[i]; } return answer; } console.log(productExceptSelf([1, 2, 3, 4])); // [24, 12, 8, 6]

Número de Islas

Dada una cuadrícula 2D de '1's (tierra) y '0's (agua), cuenta el número de islas. Una isla está rodeada de agua y se forma conectando tierras adyacentes horizontal o verticalmente.

Explicación: Itera a través de cada celda de la cuadrícula. Si encuentras un '1' (tierra), incrementa el contador de islas y comienza una Búsqueda en Profundidad (DFS) o Búsqueda en Amplitud (BFS) desde esa celda. Durante la búsqueda, marca todos los '1's conectados como '0' (o visitados) para evitar contarlos de nuevo.

function numIslands(grid) { if (!grid || grid.length === 0) return 0; let count = 0; const rows = grid.length; const cols = grid[0].length; function dfs(r, c) { if (r < 0 || c < 0 || r >= rows || c >= cols || grid[r][c] === '0') { return; } grid[r][c] = '0'; // Marcar como visitado dfs(r + 1, c); dfs(r - 1, c); dfs(r, c + 1); dfs(r, c - 1); } for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { if (grid[r][c] === '1') { count++; dfs(r, c); } } } return count; } const grid1 = [ ['1', '1', '1', '1', '0'], ['1', '1', '0', '1', '0'], ['1', '1', '0', '0', '0'], ['0', '0', '0', '0', '0'] ]; // console.log(numIslands(grid1)); // 1 (Necesita ejecutarse en una copia o restaurar la cuadrícula)

Caché LRU

Diseña e implementa una estructura de datos para una Caché de Último Uso (LRU). Debe soportar las operaciones get y put.

Explicación: Una caché LRU expulsa el elemento menos recientemente usado cuando se alcanza la capacidad. Una implementación común utiliza un Map (o un mapa hash) para búsquedas en O(1) en tiempo promedio y una Lista Doblemente Enlazada para actualizaciones en O(1) del estado 'recientemente usado'. El Map de JavaScript mantiene el orden de inserción, lo que puede simplificar una implementación básica.

class LRUCache { constructor(capacity) { this.capacity = capacity; this.cache = new Map(); } get(key) { if (!this.cache.has(key)) return -1; const value = this.cache.get(key); this.cache.delete(key); // Eliminar para reinsertar al 'final' (más reciente) this.cache.set(key, value); return value; } put(key, value) { if (this.cache.has(key)) { this.cache.delete(key); } else if (this.cache.size >= this.capacity) { const oldestKey = this.cache.keys().next().value; this.cache.delete(oldestKey); } this.cache.set(key, value); } } const cache = new LRUCache(2); cache.put(1, 1); cache.put(2, 2); console.log(cache.get(1)); // 1 cache.put(3, 3); // Expulsa 2 console.log(cache.get(2)); // -1

Generar Paréntesis

Dadas n pares de paréntesis, escribe una función para generar todas las combinaciones de paréntesis bien formados.

Explicación: Este es un problema clásico de backtracking. Mantén los conteos de paréntesis abiertos y cerrados. Puedes agregar un paréntesis abierto si open < n. Puedes agregar un paréntesis cerrado si close < open. El caso base es cuando la longitud de la cadena alcanza 2 * n.

function generateParenthesis(n) { const results = []; function backtrack(currentString, openCount, closeCount) { if (currentString.length === n * 2) { results.push(currentString); return; } if (openCount < n) { backtrack(currentString + '(', openCount + 1, closeCount); } if (closeCount < openCount) { backtrack(currentString + ')', openCount, closeCount + 1); } } backtrack('', 0, 0); return results; } console.log(generateParenthesis(3)); // ['((()))', '(()())', '(())()', '()(())', '()()()']

Contenedor con Más Agua

Dados n enteros no negativos a1, a2, ..., an, donde cada uno representa un punto en la coordenada (i, ai). Se dibujan n líneas verticales de tal manera que los dos puntos finales de la línea i están en (i, ai) y (i, 0). Encuentra dos líneas que, junto con el eje x, formen un contenedor, de modo que el contenedor contenga la mayor cantidad de agua.

Explicación: Usa el enfoque de dos punteros. Comienza con un puntero al principio y otro al final. Calcula el área. El área está limitada por la línea más corta. Para encontrar potencialmente un área más grande, mueve el puntero que apunta a la línea más corta hacia adentro.

function maxArea(height) { let max = 0; let left = 0; let right = height.length - 1; while (left < right) { const h = Math.min(height[left], height[right]); const w = right - left; max = Math.max(max, h * w); if (height[left] < height[right]) { left++; } else { right--; } } return max; } console.log(maxArea([1, 8, 6, 2, 5, 4, 8, 3, 7])); // 49

3Sum

Dado un array nums de n enteros, ¿hay elementos a, b, c en nums tal que a + b + c = 0? Encuentra todas las tripletas únicas en el array que sumen cero.

Explicación: Primero, ordena el array. Luego, itera a través del array con un puntero i. Para cada i, usa dos punteros más, left (comenzando en i+1) y right (comenzando al final), para encontrar dos números que sumen -nums[i]. Maneja los duplicados para asegurar tripletas únicas.

function threeSum(nums) { nums.sort((a, b) => a - b); const results = []; for (let i = 0; i < nums.length - 2; i++) { if (i > 0 && nums[i] === nums[i - 1]) continue; // Saltar duplicados para i let left = i + 1; let right = nums.length - 1; let target = -nums[i]; while (left < right) { let sum = nums[left] + nums[right]; if (sum === target) { results.push([nums[i], nums[left], nums[right]]); while (left < right && nums[left] === nums[left + 1]) left++; // Saltar duplicados while (left < right && nums[right] === nums[right - 1]) right--; // Saltar duplicados left++; right--; } else if (sum < target) { left++; } else { right--; } } } return results; } console.log(threeSum([-1, 0, 1, 2, -1, -4])); // [[-1, -1, 2], [-1, 0, 1]]

Posición de Inserción de Búsqueda

Dado un array ordenado de enteros distintos y un valor objetivo, devuelve el índice si se encuentra el objetivo. Si no, devuelve el índice donde estaría si se insertara en orden.

Explicación: Esta es una variación de la búsqueda binaria. Realiza una búsqueda binaria, pero si no se encuentra el elemento, el puntero start terminará en la posición donde el elemento debería ser insertado.

function searchInsert(nums, target) { let start = 0; let end = nums.length - 1; while (start <= end) { let mid = Math.floor((start + end) / 2); if (nums[mid] === target) { return mid; } else if (nums[mid] < target) { start = mid + 1; } else { end = mid - 1; } } return start; // Si no se encuentra, start es el punto de inserción } console.log(searchInsert([1, 3, 5, 6], 5)); // 2 console.log(searchInsert([1, 3, 5, 6], 2)); // 1 console.log(searchInsert([1, 3, 5, 6], 7)); // 4

Fusionar Dos Listas Ordenadas (Listas Enlazadas)

Fusiona dos listas enlazadas ordenadas y devuélvela como una nueva lista ordenada. La nueva lista debe crearse empalmando los nodos de las dos primeras listas.

Explicación: Usa un nodo de cabeza ficticio para simplificar el proceso. Usa un puntero para construir la nueva lista. Compara los nodos actuales de ambas listas de entrada y añade el más pequeño a la nueva lista, avanzando el puntero de esa lista. Repite hasta que una lista se agote, luego añade el resto de la otra lista.

class ListNode { constructor(val = 0, next = null) { this.val = val; this.next = next; } } function mergeTwoLists(l1, l2) { let dummyHead = new ListNode(); let current = dummyHead; while (l1 !== null && l2 !== null) { if (l1.val < l2.val) { current.next = l1; l1 = l1.next; } else { current.next = l2; l2 = l2.next; } current = current.next; } current.next = l1 !== null ? l1 : l2; return dummyHead.next; } // Ejemplo: 1->2->4 y 1->3->4 => 1->1->2->3->4->4

Árbol Simétrico

Dado un árbol binario, verifica si es un espejo de sí mismo (es decir, simétrico alrededor de su centro).

Explicación: Esto se puede resolver recursivamente. Un árbol es simétrico si el subárbol izquierdo es una imagen especular del subárbol derecho. Crea una función auxiliar isMirror(t1, t2) que verifica si dos árboles son espejos. Debe verificar: 1. t1.val === t2.val. 2. t1.left es un espejo de t2.right. 3. t1.right es un espejo de t2.left.

class Node { constructor(val, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function isSymmetric(root) { if (!root) return true; function isMirror(t1, t2) { if (!t1 && !t2) return true; if (!t1 || !t2 || t1.val !== t2.val) return false; return isMirror(t1.left, t2.right) && isMirror(t1.right, t2.left); } return isMirror(root.left, root.right); } // Ejemplo: 1 -> [2, 2] -> [3, 4, 4, 3] es simétrico.

Recorrido por Niveles (BST/Árbol)

Dado un árbol binario, devuelve el recorrido por niveles de los valores de sus nodos (es decir, de izquierda a derecha, nivel por nivel).

Explicación: Esto es una Búsqueda en Amplitud (BFS). Usa una cola. Empieza añadiendo la raíz a la cola. Mientras la cola no esté vacía, procesa todos los nodos en el nivel actual. Por cada nodo procesado, añade sus hijos (si existen) a la cola para el siguiente nivel.

class Node { constructor(val, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function levelOrder(root) { if (!root) return []; const result = []; const queue = [root]; while (queue.length > 0) { const levelSize = queue.length; const currentLevel = []; for (let i = 0; i < levelSize; i++) { const node = queue.shift(); currentLevel.push(node.val); if (node.left) queue.push(node.left); if (node.right) queue.push(node.right); } result.push(currentLevel); } return result; } // Ejemplo: 3 -> [9, 20] -> [null, null, 15, 7] // Salida: [[3], [9, 20], [15, 7]]

Convertir Array Ordenado a BST Equilibrado en Altura

Dado un array donde los elementos están ordenados en orden ascendente, conviértelo en un árbol de búsqueda binario (BST) equilibrado en altura.

Explicación: Para crear un BST equilibrado en altura, debes elegir el elemento central del array como raíz. Luego, construye recursivamente el subárbol izquierdo a partir de la mitad izquierda del array y el subárbol derecho a partir de la mitad derecha.

class TreeNode { constructor(val = 0, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function sortedArrayToBST(nums) { if (!nums || nums.length === 0) return null; function buildBST(start, end) { if (start > end) return null; const mid = Math.floor((start + end) / 2); const node = new TreeNode(nums[mid]); node.left = buildBST(start, mid - 1); node.right = buildBST(mid + 1, end); return node; } return buildBST(0, nums.length - 1); } // Ejemplo: [-10, -3, 0, 5, 9] // Salida: Un árbol como [0, -3, 9, -10, null, 5, null]

Implementar `bind`

Implementa tu propia versión de Function.prototype.bind.

Explicación: bind crea una nueva función que, cuando se llama, tiene su palabra clave this establecida en el valor proporcionado, con una secuencia dada de argumentos precediendo a cualquiera de los proporcionados cuando se llama a la nueva función. Usa apply o call dentro de una función devuelta, manejando la aplicación parcial (argumentos preestablecidos).

Function.prototype.myBind = function(context, ...bindArgs) { const originalFunc = this; return function(...callArgs) { return originalFunc.apply(context, [...bindArgs, ...callArgs]); }; } const module = { x: 42, getX: function() { return this.x; } }; const unboundGetX = module.getX; console.log(unboundGetX()); // undefined (this es global/window) const boundGetX = unboundGetX.myBind(module); console.log(boundGetX()); // 42

Encontrar el K-ésimo Elemento Más Grande

Encuentra el k-ésimo elemento más grande en un array no ordenado. Ten en cuenta que es el k-ésimo elemento más grande en el orden ordenado, no el k-ésimo elemento distinto.

Explicación: Un enfoque simple es ordenar el array y luego tomar el elemento en el índice n - k. Un enfoque más eficiente para arrays grandes implica el uso de un min-heap o un algoritmo de selección como Quickselect.

function findKthLargest(nums, k) { // Enfoque simple: Ordenar nums.sort((a, b) => b - a); // Ordenar en orden descendente return nums[k - 1]; // Nota: Quickselect sería más eficiente en una entrevista // pero la ordenación es más fácil de implementar rápidamente. } console.log(findKthLargest([3, 2, 1, 5, 6, 4], 2)); // 5 console.log(findKthLargest([3, 2, 3, 1, 2, 4, 5, 5, 6], 4)); // 4

Comprobar si un Objeto tiene una Propiedad

¿Cómo puedes comprobar si un objeto tiene una propiedad específica?

Explicación: Puedes usar el operador in (comprueba propiedades propias y heredadas), Object.prototype.hasOwnProperty.call(obj, prop) (comprueba solo propiedades propias), o simplemente obj.prop !== undefined (puede ser complicado con valores undefined).

const person = { name: 'Eve', age: 28 }; function hasProp(obj, prop) { console.log(`Usando 'in': ${prop in obj}`); console.log(`Usando 'hasOwnProperty': ${Object.prototype.hasOwnProperty.call(obj, prop)}`); } hasProp(person, 'name'); // true, true hasProp(person, 'toString'); // true, false (toString es heredado)

Entero a Romano

Escribe una función para convertir un entero a su representación en números romanos.

Explicación: Crea un mapeo de números romanos y sus valores correspondientes, ordenados de mayor a menor. Itera a través de este mapa. Para cada valor, réstalo del número de entrada tantas veces como sea posible, añadiendo el número romano cada vez.

function intToRoman(num) { const map = { M: 1000, CM: 900, D: 500, CD: 400, C: 100, XC: 90, L: 50, XL: 40, X: 10, IX: 9, V: 5, IV: 4, I: 1 }; let result = ''; for (let key in map) { while (num >= map[key]) { result += key; num -= map[key]; } } return result; } console.log(intToRoman(3)); // III console.log(intToRoman(58)); // LVIII console.log(intToRoman(1994)); // MCMXCIV

Romano a Entero

Escribe una función para convertir un número romano a un entero.

Explicación: Crea un mapa de símbolos romanos a valores. Itera a través de la cadena. Si el valor del símbolo actual es menor que el valor del siguiente símbolo (como 'IV' o 'IX'), resta el valor actual; de lo contrario, súmalo.

function romanToInt(s) { const map = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000 }; let result = 0; for (let i = 0; i < s.length; i++) { const currentVal = map[s[i]]; const nextVal = map[s[i + 1]]; if (nextVal > currentVal) { result -= currentVal; } else { result += currentVal; } } return result; } console.log(romanToInt('III')); // 3 console.log(romanToInt('LVIII')); // 58 console.log(romanToInt('MCMXCIV')); // 1994

Implementar un `Set`

Implementa una estructura de datos Set básica (sin usar el Set incorporado) con los métodos add, has, delete y size.

Explicación: Puedes usar un objeto JavaScript (hash map) como almacenamiento subyacente. Las claves serán los elementos del conjunto (es posible que necesites manejar cómo almacenar diferentes tipos y asegurar la unicidad como claves).

class MySet { constructor() { this.items = {}; this._size = 0; } add(element) { if (!this.has(element)) { this.items[element] = element; // Almacena el elemento this._size++; return true; } return false; } delete(element) { if (this.has(element)) { delete this.items[element]; this._size--; return true; } return false; } has(element) { return Object.prototype.hasOwnProperty.call(this.items, element); } get size() { return this._size; } } const set = new MySet(); set.add(1); set.add(2); console.log(set.has(1)); // true console.log(set.size); // 2 set.delete(1); console.log(set.has(1)); // false

Triángulo de Pascal

Dado un entero numRows, genera las primeras numRows del triángulo de Pascal.

Explicación: En el triángulo de Pascal, cada número es la suma de los dos números directamente encima de él. Comienza con [[1]]. Para cada fila subsiguiente, comienza y termina con 1. Cada elemento central es la suma del elemento en el mismo índice y el índice anterior de la fila superior.

function generatePascalsTriangle(numRows) { if (numRows === 0) return []; const triangle = [[1]]; for (let i = 1; i < numRows; i++) { const prevRow = triangle[i - 1]; const newRow = [1]; for (let j = 1; j < i; j++) { newRow.push(prevRow[j - 1] + prevRow[j]); } newRow.push(1); triangle.push(newRow); } return triangle; } console.log(generatePascalsTriangle(5)); // [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1]]

Búsqueda de Palabras

Dado un tablero 2D y una palabra, encuentra si la palabra existe en la cuadrícula. La palabra se puede construir a partir de letras de celdas adyacentes secuencialmente, donde las celdas 'adyacentes' son vecinas horizontal o verticalmente.

Explicación: Esto requiere una Búsqueda en Profundidad (DFS) con retroceso. Itera a través de cada celda. Si una celda coincide con la primera letra de la palabra, inicia un DFS. La función DFS comprueba las celdas vecinas, asegurándose de que coincidan con la siguiente letra y que no hayan sido visitadas en la ruta actual. Si una ruta falla, retrocede desmarcando la celda.

function exist(board, word) { const rows = board.length; const cols = board[0].length; function dfs(r, c, index) { if (index === word.length) return true; // Palabra encontrada if (r < 0 || c < 0 || r >= rows || c >= cols || board[r][c] !== word[index]) { return false; } const temp = board[r][c]; board[r][c] = '#'; // Marcar como visitado const found = dfs(r + 1, c, index + 1) || dfs(r - 1, c, index + 1) || dfs(r, c + 1, index + 1) || dfs(r, c - 1, index + 1); board[r][c] = temp; // Retroceder return found; } for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { if (board[r][c] === word[0] && dfs(r, c, 0)) { return true; } } } return false; } const board = [['A','B','C','E'],['S','F','C','S'],['A','D','E','E']]; console.log(exist(board, 'ABCCED')); // true console.log(exist(board, 'SEE')); // true console.log(exist(board, 'ABCB')); // false

Subcadena de Ventana Mínima

Dadas dos cadenas s y t, encuentra la ventana mínima en s que contendrá todos los caracteres en t. Si no hay tal ventana en s que cubra todos los caracteres en t, devuelve la cadena vacía "".

Explicación: Usa un enfoque de ventana deslizante con dos punteros (left y right) y mapas hash. Un mapa almacena los recuentos de caracteres necesarios de t. Otro mapa almacena los recuentos en la ventana actual. Expande la ventana con right. Una vez que la ventana contiene todos los caracteres necesarios, intenta encogerla con left para encontrar la ventana mínima posible.

function minWindow(s, t) { if (!t || !s || s.length < t.length) return ""; const tMap = {}; for (const char of t) tMap[char] = (tMap[char] || 0) + 1; let required = Object.keys(tMap).length; let formed = 0; const windowMap = {}; let left = 0; let minLen = Infinity; let result = ""; for (let right = 0; right < s.length; right++) { const char = s[right]; windowMap[char] = (windowMap[char] || 0) + 1; if (tMap[char] && windowMap[char] === tMap[char]) { formed++; } while (left <= right && formed === required) { if (right - left + 1 < minLen) { minLen = right - left + 1; result = s.substring(left, right + 1); } const leftChar = s[left]; windowMap[leftChar]--; if (tMap[leftChar] && windowMap[leftChar] < tMap[leftChar]) { formed--; } left++; } } return result; } console.log(minWindow('ADOBECODEBANC', 'ABC')); // 'BANC'

Invertir Lista Enlazada

Dada la head de una lista enlazada individualmente, invierte la lista y devuelve la lista invertida.

Explicación: Debes iterar a través de la lista, cambiando el puntero next de cada nodo para que apunte al nodo anterior. Mantén un registro de los nodos previous, current y next durante la iteración.

class ListNode { constructor(val = 0, next = null) { this.val = val; this.next = next; } } function reverseList(head) { let prev = null; let current = head; let next = null; while (current !== null) { next = current.next; // Almacena el siguiente nodo current.next = prev; // Invierte el puntero del nodo actual prev = current; // Mueve prev un paso adelante current = next; // Mueve current un paso adelante } return prev; // La nueva cabeza es el último 'prev' } // Ejemplo: 1->2->3->4->5 se convierte en 5->4->3->2->1

Detectar Ciclo en Lista Enlazada

Dada head, la cabeza de una lista enlazada, determina si la lista enlazada tiene un ciclo.

Explicación: Usa el algoritmo 'Tortuga y Liebre de Floyd'. Ten dos punteros, uno que se mueve un paso a la vez (slow) y otro que se mueve dos pasos a la vez (fast). Si hay un ciclo, el puntero fast eventualmente alcanzará al puntero slow.

class ListNode { constructor(val = 0, next = null) { this.val = val; this.next = next; } } function hasCycle(head) { if (!head || !head.next) return false; let slow = head; let fast = head.next; while (slow !== fast) { if (!fast || !fast.next) return false; // Se llegó al final, no hay ciclo slow = slow.next; fast = fast.next.next; } return true; // Los punteros se encontraron, existe un ciclo } // Ejemplo: 3->2->0->-4 con -4 apuntando de nuevo a 2. hasCycle devuelve true.

Implementar `Object.create`

Implementa una función que imite el comportamiento de Object.create(proto).

Explicación: Object.create crea un nuevo objeto, utilizando un objeto existente como prototipo del objeto recién creado. Puedes lograr esto creando una función constructora temporal, configurando su prototipo en el proto de entrada y luego devolviendo una nueva instancia de la misma.

function myObjectCreate(proto) { if (typeof proto !== 'object' && typeof proto !== 'function') { if (proto !== null) { throw new TypeError('Object prototype may only be an Object or null: ' + proto); } } function F() {} F.prototype = proto; return new F(); } const person = { isHuman: false, printIntroduction: function() { console.log(`Mi nombre es ${this.name}. ¿Soy humano? ${this.isHuman}`); } }; const me = myObjectCreate(person); me.name = 'Matthew'; me.isHuman = true; me.printIntroduction(); // Mi nombre es Matthew. ¿Soy humano? true console.log(Object.getPrototypeOf(me) === person); // true

¿Qué es Hoisting?

Explica hoisting en JavaScript y proporciona un ejemplo.

Explicación: Hoisting es el comportamiento predeterminado de JavaScript de mover las declaraciones a la parte superior del ámbito actual (global o función) antes de la ejecución del código. Las declaraciones de variables (var) se elevan y se inicializan con undefined. Las declaraciones de funciones se elevan completamente (tanto el nombre como el cuerpo). let y const se elevan pero no se inicializan, lo que lleva a una Zona Muerta Temporal.

console.log(myVar); // undefined (var se eleva y se inicializa con undefined) // console.log(myLet); // ReferenceError: Cannot access 'myLet' before initialization (TDZ) myFunc(); // 'Hello!' (La declaración de función se eleva completamente) var myVar = 'Soy una var'; let myLet = 'Soy una let'; function myFunc() { console.log('¡Hola!'); }

Explicar el Burbujeo y la Captura de Eventos

Explica el Burbujeo y la Captura de Eventos en el contexto del DOM.

Explicación: Estas son dos fases de la propagación de eventos en el DOM HTML. Fase de Captura: El evento viaja desde la window hasta el elemento objetivo. Fase de Burbujeo: El evento viaja desde el elemento objetivo de vuelta a la window. Por defecto, la mayoría de los manejadores de eventos se registran durante la fase de burbujeo. Puedes usar addEventListener(type, listener, useCapture) con useCapture = true para manejar eventos durante la fase de captura.

// En una estructura HTML: <div><p><span>Haz clic aquí</span></p></div> // Si haces clic en el <span>: // Captura: window -> document -> html -> body -> div -> p -> span // Burbujeo: span -> p -> div -> body -> html -> document -> window /* Ejemplo JS div.addEventListener('click', () => console.log('Div clickeado'), true); // Captura p.addEventListener('click', () => console.log('P clickeado'), false); // Burbujeo span.addEventListener('click', () => console.log('Span clickeado'), false); // Burbujeo // La salida sería: Div clickeado, Span clickeado, P clickeado */

Implementar `JSON.parse` manualmente (básico)

Intenta implementar una versión muy básica de JSON.parse (maneja objetos simples, arrays, cadenas, números).

Explicación: Esta es una tarea muy compleja en su totalidad, pero para un entorno de codificación en vivo, se te podría pedir que manejes un subconjunto muy simplificado. Necesitarías analizar la cadena, identificando los límites de objetos {} y arrays [], pares clave-valor ("key": value), y tipos de datos básicos. eval o new Function pueden hacer trampa en esto, pero son peligrosos. Un analizador real usaría un lexer/tokenizer y un analizador.

function basicJsonParse(jsonString) { // ADVERTENCIA: Usar new Function es inseguro como eval. // Este es un ejemplo simplificado e INSEGURO solo para demostración. // Una implementación real requiere un analizador adecuado. try { return (new Function('return ' + jsonString))(); } catch (e) { throw new SyntaxError('JSON inválido: ' + e.message); } } console.log(basicJsonParse('{"a": 1, "b": ["x", "y"]}')); // { a: 1, b: ['x', 'y'] }

Aplanar un Array Profundamente Anidado

Escribe una función para aplanar un array profundamente anidado.

Explicación: A diferencia del aplanamiento anterior (un nivel), este necesita manejar cualquier nivel de anidamiento. La recursión es una opción natural. Itera a través del array. Si un elemento es un array, llama recursivamente a flatten sobre él y concatena el resultado. De lo contrario, añade el elemento.

function deepFlatten(arr) { let flattened = []; arr.forEach(item => { if (Array.isArray(item)) { flattened = flattened.concat(deepFlatten(item)); } else { flattened.push(item); } }); return flattened; // ES2019+ proporciona una forma mucho más simple: // return arr.flat(Infinity); } console.log(deepFlatten([1, [2, [3, 4], 5], 6])); // [1, 2, 3, 4, 5, 6]

Implementar un Trie (Árbol de Prefijos)

Implementa una estructura de datos Trie con los métodos insert, search y startsWith.

Explicación: Un Trie es una estructura de datos similar a un árbol que se utiliza para almacenar y recuperar claves de manera eficiente en un conjunto de datos de cadenas. Cada nodo representa un carácter, y las rutas desde la raíz representan palabras o prefijos.

class TrieNode { constructor() { this.children = {}; this.isEndOfWord = false; } } class Trie { constructor() { this.root = new TrieNode(); } insert(word) { let node = this.root; for (const char of word) { if (!node.children[char]) { node.children[char] = new TrieNode(); } node = node.children[char]; } node.isEndOfWord = true; } search(word) { let node = this.root; for (const char of word) { if (!node.children[char]) return false; node = node.children[char]; } return node.isEndOfWord; } startsWith(prefix) { let node = this.root; for (const char of prefix) { if (!node.children[char]) return false; node = node.children[char]; } return true; } } const trie = new Trie(); trie.insert('manzana'); console.log(trie.search('manzana')); // true console.log(trie.search('manz')); // false console.log(trie.startsWith('manz')); // true

Mezclar un Array (Fisher-Yates)

Escribe una función para mezclar un array en su lugar utilizando el algoritmo Fisher-Yates (o Knuth).

Explicación: El algoritmo Fisher-Yates itera un array desde el último elemento hasta el primero. En cada iteración, intercambia el elemento actual con un elemento elegido aleatoriamente desde el principio del array hasta el elemento actual (inclusive).

function shuffleArray(arr) { for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [arr[i], arr[j]] = [arr[j], arr[i]]; // Intercambiar elementos } return arr; } console.log(shuffleArray([1, 2, 3, 4, 5])); // ej., [3, 5, 1, 2, 4]

Componer Funciones

Implementa una función compose que toma múltiples funciones y devuelve una nueva función que las aplica de derecha a izquierda.

Explicación: La composición de funciones (f ∘ g)(x) = f(g(x)) aplica una función al resultado de otra. Una función compose(f, g, h) general significaría f(g(h(x))). Usa reduceRight para una implementación elegante.

function compose(...funcs) { return function(initialArg) { return funcs.reduceRight((acc, func) => func(acc), initialArg); }; } const add5 = x => x + 5; const multiply3 = x => x * 3; const subtract2 = x => x - 2; const composedFunc = compose(subtract2, multiply3, add5); console.log(composedFunc(10)); // (10 + 5) * 3 - 2 = 15 * 3 - 2 = 45 - 2 = 43

Encadenar Funciones (Pipe)

Implementa una función pipe que toma múltiples funciones y devuelve una nueva función que las aplica de izquierda a derecha.

Explicación: Similar a compose, pero el orden de aplicación se invierte: pipe(f, g, h) significa h(g(f(x))). Usa reduce para la implementación.

function pipe(...funcs) { return function(initialArg) { return funcs.reduce((acc, func) => func(acc), initialArg); }; } const add5 = x => x + 5; const multiply3 = x => x * 3; const subtract2 = x => x - 2; const pipedFunc = pipe(add5, multiply3, subtract2); console.log(pipedFunc(10)); // (10 + 5) * 3 - 2 = 15 * 3 - 2 = 45 - 2 = 43

Problema del Cambio de Monedas

Dado un array de denominaciones de monedas y una cantidad, encuentra el número mínimo de monedas para alcanzar esa cantidad. Asume un suministro infinito de cada moneda.

Explicación: Este es un problema clásico de programación dinámica. Crea un array dp donde dp[i] almacena el número mínimo de monedas necesarias para la cantidad i. dp[i] = min(dp[i - moneda]) + 1 para todas las monedas.

function coinChange(coins, amount) { const dp = new Array(amount + 1).fill(Infinity); dp[0] = 0; for (let i = 1; i <= amount; i++) { for (const coin of coins) { if (i - coin >= 0) { dp[i] = Math.min(dp[i], dp[i - coin] + 1); } } } return dp[amount] === Infinity ? -1 : dp[amount]; } console.log(coinChange([1, 2, 5], 11)); // 3 (5 + 5 + 1) console.log(coinChange([2], 3)); // -1

Ancestro Común Más Bajo (BST)

Dado un árbol de búsqueda binario (BST), encuentra el ancestro común más bajo (LCA) de dos nodos dados en el BST.

Explicación: El LCA es el nodo más profundo que tiene ambos nodos dados como descendientes. En un BST, puedes encontrarlo recorriendo desde la raíz. Si ambos nodos son más pequeños que el nodo actual, ve a la izquierda. Si ambos son más grandes, ve a la derecha. Si uno es más pequeño y otro es más grande (o uno es el nodo actual), entonces el nodo actual es el LCA.

class Node { constructor(val) { this.val = val; this.left = this.right = null; } } function lowestCommonAncestor(root, p, q) { let current = root; while (current) { if (p.val > current.val && q.val > current.val) { current = current.right; } else if (p.val < current.val && q.val < current.val) { current = current.left; } else { return current; // LCA encontrado } } return null; // No debería ocurrir en un BST válido con p y q } // Ejemplo: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 => LCA es 6

Serializar y Deserializar Árbol Binario

Diseña un algoritmo para serializar y deserializar un árbol binario.

Explicación: La serialización convierte un árbol en una cadena o array. La deserialización reconstruye el árbol. Una forma común es el Recorrido Pre-orden (DFS). Usa un marcador especial (por ejemplo, '#') para los nodos nulos para preservar la estructura.

class Node { constructor(val) { this.val = val; this.left = this.right = null; } } function serialize(root) { const values = []; function dfs(node) { if (!node) { values.push('#'); return; } values.push(node.val); dfs(node.left); dfs(node.right); } dfs(root); return values.join(','); } function deserialize(data) { const values = data.split(','); let index = 0; function buildTree() { if (values[index] === '#') { index++; return null; } const node = new Node(parseInt(values[index])); index++; node.left = buildTree(); node.right = buildTree(); return node; } return buildTree(); } // Ejemplo: 1 -> [2, 3] -> [null, null, 4, 5] // Serializado: '1,2,#,#,3,4,#,#,5,#,#'

Implementar `setInterval` con `setTimeout`

Implementa una función mySetInterval que imita setInterval pero usa setTimeout recursivamente.

Explicación: setInterval ejecuta una función repetidamente cada N milisegundos. Puedes lograr esto haciendo que una función se llame a sí misma con setTimeout después de cada ejecución. También necesitas una forma de limpiarlo (myClearInterval).

function mySetInterval(callback, delay, ...args) { const interval = { timerId: null }; function run() { interval.timerId = setTimeout(() => { callback.apply(this, args); run(); // Programa la siguiente llamada }, delay); } run(); return interval; // Devuelve un objeto para permitir la limpieza } function myClearInterval(interval) { clearTimeout(interval.timerId); } // Ejemplo de uso: let count = 0; const intervalId = mySetInterval(() => { console.log(`Hola: ${++count}`); if (count === 3) myClearInterval(intervalId); }, 500);

Recorrido de Grafos (BFS y DFS)

Implementa la Búsqueda en Amplitud (BFS) y la Búsqueda en Profundidad (DFS) para un grafo dado (representación de lista de adyacencia).

Explicación: BFS explora primero los vecinos (usando una Cola), mientras que DFS explora lo más lejos posible a lo largo de cada rama (usando una Pila o recursión). Mantén un registro de los nodos visitados para evitar ciclos y trabajo redundante.

const graph = { A: ['B', 'C'], B: ['D'], C: ['E'], D: [], E: ['F'], F: [] }; function bfs(graph, startNode) { const queue = [startNode]; const visited = new Set(); const result = []; visited.add(startNode); while (queue.length > 0) { const node = queue.shift(); result.push(node); for (const neighbor of graph[node]) { if (!visited.has(neighbor)) { visited.add(neighbor); queue.push(neighbor); } } } return result; } function dfs(graph, startNode) { const stack = [startNode]; const visited = new Set(); const result = []; while (stack.length > 0) { const node = stack.pop(); if (!visited.has(node)) { visited.add(node); result.push(node); // Añadir vecinos en orden inverso para procesarlos en orden más tarde (opcional) for (let i = graph[node].length - 1; i >= 0; i--) { stack.push(graph[node][i]); } } } return result; } console.log('BFS:', bfs(graph, 'A')); // ['A', 'B', 'C', 'D', 'E', 'F'] console.log('DFS:', dfs(graph, 'A')); // ['A', 'B', 'D', 'C', 'E', 'F']

Rotar Imagen (Matriz)

Se le da una matriz 2D de n x n que representa una imagen. Rota la imagen 90 grados (en el sentido de las agujas del reloj) en su lugar.

Explicación: Una forma común de lograr esto es primero transponer la matriz (intercambiando matrix[i][j] con matrix[j][i]) y luego invirtiendo cada fila.

function rotate(matrix) { const n = matrix.length; // Transponer for (let i = 0; i < n; i++) { for (let j = i; j < n; j++) { [matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]]; } } // Invertir cada fila for (let i = 0; i < n; i++) { matrix[i].reverse(); } return matrix; } const matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; console.log(rotate(matrix)); // [[7, 4, 1], [8, 5, 2], [9, 6, 3]]

Recorrido de Matriz Espiral

Dada una matriz de m x n, devuelve todos los elementos de la matriz en orden espiral.

Explicación: Usa cuatro punteros para definir los límites: top, bottom, left, right. Recorre la fila superior, luego la columna derecha, luego la fila inferior, luego la columna izquierda, reduciendo los límites después de cada recorrido, hasta que left cruce right o top cruce bottom.

function spiralOrder(matrix) { if (!matrix || matrix.length === 0) return []; const rows = matrix.length, cols = matrix[0].length; const result = []; let top = 0, bottom = rows - 1, left = 0, right = cols - 1; while (top <= bottom && left <= right) { for (let i = left; i <= right; i++) result.push(matrix[top][i]); top++; for (let i = top; i <= bottom; i++) result.push(matrix[i][right]); right--; if (top <= bottom) { for (let i = right; i >= left; i--) result.push(matrix[bottom][i]); bottom--; } if (left <= right) { for (let i = bottom; i >= top; i--) result.push(matrix[i][left]); left++; } } return result; } const matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; console.log(spiralOrder(matrix)); // [1, 2, 3, 6, 9, 8, 7, 4, 5]

Establecer Ceros en Matriz

Dada una matriz m x n, si un elemento es 0, establece toda su fila y columna en 0. Hazlo in situ.

Explicación: Un enfoque ingenuo requiere un espacio extra de O(m*n). Para hacerlo en un espacio de O(1) (o O(m+n)), puedes usar la primera fila y la primera columna para almacenar información sobre qué filas/columnas deben ser puestas a cero. Necesitas indicadores separados para saber si la primera fila/columna en sí mismas necesitan ser puestas a cero.

function setZeroes(matrix) { const rows = matrix.length, cols = matrix[0].length; let firstColZero = false; for (let r = 0; r < rows; r++) { if (matrix[r][0] === 0) firstColZero = true; for (let c = 1; c < cols; c++) { if (matrix[r][c] === 0) { matrix[r][0] = 0; matrix[0][c] = 0; } } } for (let r = rows - 1; r >= 0; r--) { for (let c = cols - 1; c >= 1; c--) { if (matrix[r][0] === 0 || matrix[0][c] === 0) { matrix[r][c] = 0; } } if (firstColZero) matrix[r][0] = 0; } return matrix; } // Ejemplo: [[1,1,1],[1,0,1],[1,1,1]] => [[1,0,1],[0,0,0],[1,0,1]]

Implementar `Promise.race`

Implementa una función que se comporte como Promise.race.

Explicación: Promise.race toma un array de promesas y devuelve una única promesa. Esta promesa se resuelve (se cumple o se rechaza) tan pronto como cualquiera de las promesas de entrada se resuelve, con el valor o la razón de esa promesa.

function myPromiseRace(promises) { return new Promise((resolve, reject) => { if (!promises || promises.length === 0) { return; // O resolver/rechazar dependiendo de la especificación } promises.forEach(promise => { Promise.resolve(promise).then(resolve, reject); }); }); } const p1 = new Promise((resolve) => setTimeout(resolve, 500, 'uno')); const p2 = new Promise((resolve) => setTimeout(resolve, 100, 'dos')); myPromiseRace([p1, p2]).then(value => console.log(value)); // 'dos'

Patrón Singleton

Implementa el patrón de diseño Singleton en JavaScript.

Explicación: El patrón Singleton asegura que una clase tenga solo una instancia y proporciona un punto de acceso global a ella. Esto se puede lograr utilizando un cierre para mantener la instancia.

const Singleton = (function() { let instance; function createInstance() { // Métodos y variables privados const privateVar = 'Soy privada'; function privateMethod() { console.log('Privado'); } return { // Métodos y variables públicos publicVar: 'Soy pública', publicMethod: function() { console.log('Público'); }, getPrivate: function() { return privateVar; } }; } return { getInstance: function() { if (!instance) { instance = createInstance(); } return instance; } }; })(); const instance1 = Singleton.getInstance(); const instance2 = Singleton.getInstance(); console.log(instance1 === instance2); // true console.log(instance1.getPrivate()); // 'Soy privada'

Validar Dirección IP

Escribe una función para verificar si una cadena dada es una dirección IPv4 o IPv6 válida.

Explicación: Para IPv4, verifica si hay 4 números (0-255) separados por puntos, sin ceros iniciales (excepto para '0' mismo). Para IPv6, verifica si hay 8 grupos de 1-4 dígitos hexadecimales separados por dos puntos (maneja variaciones como '::'). Se puede usar Regex, pero el análisis manual suele ser más claro para las entrevistas.

function validIPAddress(IP) { function isIPv4(s) { const parts = s.split('.'); if (parts.length !== 4) return false; for (const part of parts) { if (!/^[0-9]+$/.test(part)) return false; if (part.length > 1 && part.startsWith('0')) return false; const num = parseInt(part); if (num < 0 || num > 255) return false; } return true; } function isIPv6(s) { const parts = s.split(':'); if (parts.length !== 8) return false; // Simplificado: No '::' for (const part of parts) { if (part.length < 1 || part.length > 4) return false; if (!/^[0-9a-fA-F]+$/.test(part)) return false; } return true; } if (IP.includes('.')) return isIPv4(IP) ? 'IPv4' : 'Neither'; if (IP.includes(':')) return isIPv6(IP) ? 'IPv6' : 'Neither'; return 'Neither'; } console.log(validIPAddress('172.16.254.1')); // IPv4 console.log(validIPAddress('2001:0db8:85a3:0000:0000:8a2e:0370:7334')); // IPv6 (Simplificado) console.log(validIPAddress('256.256.256.256')); // Neither

Encontrar Elemento Pico

Un elemento pico es un elemento que es estrictamente mayor que sus vecinos. Dado un array de entrada nums, encuentra un elemento pico y devuelve su índice.

Explicación: Como cualquier pico servirá, y nums[-1] y nums[n] se consideran -Infinity, puedes usar una búsqueda binaria modificada. Si nums[mid] es menor que nums[mid+1], debe existir un pico a la derecha. De lo contrario, debe existir un pico a la izquierda (o mid mismo es un pico).

function findPeakElement(nums) { let left = 0; let right = nums.length - 1; while (left < right) { let mid = Math.floor((left + right) / 2); if (nums[mid] < nums[mid + 1]) { left = mid + 1; // El pico está a la derecha } else { right = mid; // El pico es mid o está a la izquierda } } return left; // 'left' será el índice de un pico } console.log(findPeakElement([1, 2, 3, 1])); // 2 (índice de 3) console.log(findPeakElement([1, 2, 1, 3, 5, 6, 4])); // 5 (índice de 6) o 1 (índice de 2)

Contar Bits

Dado un entero n, devuelve un array ans de longitud n + 1 tal que para cada i (0 <= i <= n), ans[i] es el número de 1's en la representación binaria de i.

Explicación: Puedes resolver esto usando programación dinámica. Observa la relación: dp[i] = dp[i >> 1] + (i & 1). El número de 1s en i es el número de 1s en i desplazado a la derecha (es decir, i/2), más 1 si i es impar.

function countBits(n) { const dp = new Array(n + 1).fill(0); for (let i = 1; i <= n; i++) { dp[i] = dp[i >> 1] + (i & 1); // O: dp[i] = dp[i & (i - 1)] + 1; (Elimina el último bit establecido) } return dp; } console.log(countBits(5)); // [0, 1, 1, 2, 1, 2]

Potencia de Dos

Dado un entero n, devuelve true si es una potencia de dos. De lo contrario, devuelve false.

Explicación: Una potencia de dos en representación binaria tiene exactamente un bit '1' (por ejemplo, 1=1, 2=10, 4=100, 8=1000). Un truco ingenioso con bits es verificar si n > 0 y (n & (n - 1)) === 0. Si n es una potencia de dos, n-1 tendrá todos los bits debajo de ese '1' establecidos en '1'. Al aplicarles AND, el resultado es 0.

function isPowerOfTwo(n) { return n > 0 && (n & (n - 1)) === 0; } console.log(isPowerOfTwo(16)); // true console.log(isPowerOfTwo(1)); // true console.log(isPowerOfTwo(18)); // false

Fusionar Intervalos

Dado un array de intervals donde intervals[i] = [starti, endi], fusiona todos los intervalos superpuestos y devuelve un array de los intervalos no superpuestos.

Explicación: Primero, ordena los intervalos según sus tiempos de inicio. Luego, itera a través de los intervalos ordenados. Si el intervalo actual se superpone con el último intervalo en la lista de resultados, fúndelos actualizando el tiempo final del último intervalo. De lo contrario, añade el intervalo actual a la lista de resultados.

function mergeIntervals(intervals) { if (intervals.length === 0) return []; intervals.sort((a, b) => a[0] - b[0]); const merged = [intervals[0]]; for (let i = 1; i < intervals.length; i++) { const last = merged[merged.length - 1]; const current = intervals[i]; if (current[0] <= last[1]) { // Superposición: fusionar last[1] = Math.max(last[1], current[1]); } else { // No superposición: añadir nuevo intervalo merged.push(current); } } return merged; } console.log(mergeIntervals([[1, 3], [2, 6], [8, 10], [15, 18]])); // [[1, 6], [8, 10], [15, 18]]

Separación de Palabras

Dada una cadena s y un diccionario de cadenas wordDict, devuelve true si s puede ser segmentada en una secuencia separada por espacios de una o más palabras del diccionario.

Explicación: Usa programación dinámica. Crea un array booleano dp donde dp[i] es verdadero si la subcadena s[0...i-1] puede ser segmentada. dp[i] es verdadero si existe un j < i tal que dp[j] es verdadero y la subcadena s[j...i-1] está en el diccionario.

function wordBreak(s, wordDict) { const wordSet = new Set(wordDict); const n = s.length; const dp = new Array(n + 1).fill(false); dp[0] = true; // Caso base: cadena vacía for (let i = 1; i <= n; i++) { for (let j = 0; j < i; j++) { if (dp[j] && wordSet.has(s.substring(j, i))) { dp[i] = true; break; } } } return dp[n]; } console.log(wordBreak('leetcode', ['leet', 'code'])); // true console.log(wordBreak('applepenapple', ['apple', 'pen'])); // true console.log(wordBreak('catsandog', ['cats', 'dog', 'sand', 'and', 'cat'])); // false

Implementar `Array.prototype.flat`

Implementa tu propia versión de Array.prototype.flat, que crea un nuevo array con todos los elementos de sub-arrays concatenados recursivamente hasta una profundidad especificada.

Explicación: Usa recursión. Itera a través del array. Si un elemento es un array y la profundidad actual es menor que la profundidad especificada, llama recursivamente a flatten sobre él. De lo contrario, añade el elemento. Maneja la profundidad predeterminada de 1.

function myFlat(arr, depth = 1) { let flattened = []; arr.forEach(item => { if (Array.isArray(item) && depth > 0) { flattened.push(...myFlat(item, depth - 1)); } else { flattened.push(item); } }); return flattened; } console.log(myFlat([1, [2, [3, 4], 5], 6])); // [2, [3, 4], 5] console.log(myFlat([1, [2, [3, 4], 5], 6], 2)); // [1, 2, 3, 4, 5, 6]

Invertir Palabras en una Cadena

Dada una cadena de entrada s, invierte el orden de las palabras.

Explicación: Una 'palabra' se define como una secuencia de caracteres sin espacios. Recorta la cadena, divídela por uno o más espacios, filtra cualquier cadena vacía resultante de múltiples espacios, invierte el array y únelo de nuevo con espacios simples.

function reverseWords(s) { return s.trim().split(/\s+/).reverse().join(' '); } console.log(reverseWords('el cielo es azul')); // 'azul es cielo el' console.log(reverseWords(' hola mundo ')); // 'mundo hola' console.log(reverseWords('un buen ejemplo')); // 'ejemplo buen un'

Analizador de Cadena de Consulta

Escribe una función para analizar una cadena de consulta URL en un objeto.

Explicación: Divide la cadena por &. Para cada parte, divide por = para obtener la clave y el valor. Decodifica los componentes URI tanto para la clave como para el valor. Maneja los casos en los que una clave puede aparecer varias veces (crea un array) o no tiene valor.

function parseQueryString(query) { if (query.startsWith('?')) { query = query.substring(1); } const result = {}; if (!query) return result; query.split('&').forEach(pair => { const [key, value] = pair.split('='); const decodedKey = decodeURIComponent(key); const decodedValue = value !== undefined ? decodeURIComponent(value) : true; if (result.hasOwnProperty(decodedKey)) { if (Array.isArray(result[decodedKey])) { result[decodedKey].push(decodedValue); } else { result[decodedKey] = [result[decodedKey], decodedValue]; } } else { result[decodedKey] = decodedValue; } }); return result; } console.log(parseQueryString('?foo=bar&baz=qux&baz=quux&corge')); // { foo: 'bar', baz: ['qux', 'quux'], corge: true }

Usar `Proxy` para Validación

Demuestra cómo usar un objeto Proxy para la validación de propiedades de objetos.

Explicación: Un Proxy te permite interceptar y personalizar operaciones realizadas en un objeto. Usa el set trap para validar valores antes de asignarlos a una propiedad. Lanza un error si la validación falla.

const validator = { set: function(obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('La edad no es un entero'); } if (value < 0 || value > 150) { throw new RangeError('La edad parece inválida'); } } // Comportamiento predeterminado: Establecer la propiedad obj[prop] = value; return true; } }; const person = {}; const personProxy = new Proxy(person, validator); personProxy.age = 30; // OK console.log(personProxy.age); // 30 // personProxy.age = 'treinta'; // Lanza TypeError // personProxy.age = 200; // Lanza RangeError

Salas de Reuniones

Dado un array de intervalos de tiempo de reunión [[start1, end1], [start2, end2], ...], determina si una persona podría asistir a todas las reuniones.

Explicación: Si una persona puede asistir a todas las reuniones, significa que no hay dos reuniones que se superpongan. Para comprobar esto, ordena los intervalos por sus tiempos de inicio. Luego, itera a través de los intervalos ordenados y verifica si el tiempo de inicio de la reunión actual es anterior al tiempo de finalización de la reunión anterior. Si lo es, hay una superposición y la persona no puede asistir a todas.

function canAttendMeetings(intervals) { if (intervals.length < 2) return true; intervals.sort((a, b) => a[0] - b[0]); for (let i = 1; i < intervals.length; i++) { if (intervals[i][0] < intervals[i - 1][1]) { return false; // Superposición detectada } } return true; } console.log(canAttendMeetings([[0, 30], [5, 10], [15, 20]])); // false (5<30) console.log(canAttendMeetings([[7, 10], [2, 4]])); // true

Implementar `Promise.any`

Implementa una función que se comporte como Promise.any.

Explicación: Promise.any toma un array de promesas y devuelve una única promesa. Esta promesa se cumple tan pronto como cualquiera de las promesas de entrada se cumple. Si todas las promesas de entrada se rechazan, se rechaza con un AggregateError que contiene todas las razones de rechazo.

function myPromiseAny(promises) { return new Promise((resolve, reject) => { const numPromises = promises.length; if (numPromises === 0) { reject(new AggregateError([], 'Todas las promesas fueron rechazadas')); return; } let rejectionCount = 0; const errors = []; promises.forEach((promise, index) => { Promise.resolve(promise) .then(resolve) // Resolver tan pronto como una se cumpla .catch(error => { errors[index] = error; rejectionCount++; if (rejectionCount === numPromises) { reject(new AggregateError(errors, 'Todas las promesas fueron rechazadas')); } }); }); }); } // Ejemplo: myPromiseAny([Promise.reject('err1'), Promise.resolve('ok')]) -> 'ok' // Ejemplo: myPromiseAny([Promise.reject('err1'), Promise.reject('err2')]) -> AggregateError

Patrón Observador

Implementa el patrón de diseño Observador (Publicador/Suscriptor) en JavaScript.

Explicación: El patrón Observador define una dependencia de uno a muchos entre objetos. Cuando un objeto (Sujeto) cambia de estado, todos sus dependientes (Observadores) son notificados y actualizados automáticamente. El Sujeto mantiene una lista de observadores y proporciona métodos para añadirlos, eliminarlos y notificarlos.

class Subject { constructor() { this.observers = []; } subscribe(observer) { this.observers.push(observer); } unsubscribe(observer) { this.observers = this.observers.filter(obs => obs !== observer); } notify(data) { this.observers.forEach(observer => observer.update(data)); } } class Observer { constructor(name) { this.name = name; } update(data) { console.log(`${this.name} recibido: ${data}`); } } const subject = new Subject(); const obs1 = new Observer('Observador 1'); const obs2 = new Observer('Observador 2'); subject.subscribe(obs1); subject.subscribe(obs2); subject.notify('¡Algo ha pasado!'); // Observador 1 recibido: ¡Algo ha pasado! // Observador 2 recibido: ¡Algo ha pasado!

Encontrar Número Duplicado

Dado un array de enteros nums que contiene n + 1 enteros donde cada entero está en el rango [1, n] inclusive, encuentra el número repetido.

Explicación: Dado que los números están en [1, n] y el tamaño del array es n+1, se garantiza al menos un duplicado. Esto se puede mapear a un problema de 'Encontrar ciclo en una lista enlazada' (Tortuga y Liebre de Floyd). Trata los valores del array como punteros: índice -> nums[índice].

function findDuplicate(nums) { let tortoise = nums[0]; let hare = nums[0]; // Fase 1: Encontrar el punto de intersección do { tortoise = nums[tortoise]; hare = nums[nums[hare]]; } while (tortoise !== hare); // Fase 2: Encontrar la entrada al ciclo let ptr1 = nums[0]; let ptr2 = tortoise; while (ptr1 !== ptr2) { ptr1 = nums[ptr1]; ptr2 = nums[ptr2]; } return ptr1; } console.log(findDuplicate([1, 3, 4, 2, 2])); // 2 console.log(findDuplicate([3, 1, 3, 4, 2])); // 3

Sanitizador Básico de HTML

Escribe una función básica para sanear una cadena HTML, eliminando etiquetas potencialmente dañinas (como <script>) mientras se mantienen las etiquetas seguras (como <p>, <b>).

Explicación: Este es un tema complejo, y los sanitizadores del mundo real son sofisticados. Para una entrevista, un enfoque básico podría implicar el uso de Regex para eliminar etiquetas específicas o solo permitir una lista blanca de etiquetas. Esto no es seguro para producción.

function basicSanitize(html) { // ADVERTENCIA: Este es un ejemplo muy básico e inseguro. // NO lo uses en producción. Usa una librería como DOMPurify. // Eliminar etiquetas de script let sanitized = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, ''); // Ejemplo: Eliminar atributos onclick (muy básico) sanitized = sanitized.replace(/\s(on\w+)=['"]?[^'"]*['"]?/gi, ''); return sanitized; } console.log(basicSanitize('<p>Hola <script>alert("XSS")</script> <b onclick="danger()">Mundo</b></p>')); // <p>Hola <b >Mundo</b></p>

Distancia de Edición

Dadas dos cadenas word1 y word2, devuelve el número mínimo de operaciones (insertar, eliminar o sustituir) necesarias para convertir word1 en word2.

Explicación: Este es un problema clásico de programación dinámica (distancia de Levenshtein). Crea un array 2D dp donde dp[i][j] es la distancia de edición entre los primeros i caracteres de word1 y los primeros j caracteres de word2. La relación de recurrencia implica considerar el costo de inserción, eliminación y sustitución.

function minDistance(word1, word2) { const m = word1.length, n = word2.length; const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0)); for (let i = 0; i <= m; i++) dp[i][0] = i; for (let j = 0; j <= n; j++) dp[0][j] = j; for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { const cost = word1[i - 1] === word2[j - 1] ? 0 : 1; dp[i][j] = Math.min( dp[i - 1][j] + 1, // Eliminación dp[i][j - 1] + 1, // Inserción dp[i - 1][j - 1] + cost // Sustitución/Coincidencia ); } } return dp[m][n]; } console.log(minDistance('caballo', 'rosa')); // 3 console.log(minDistance('intencion', 'ejecucion')); // 5

Subsecuencia Creciente Más Larga (LIS)

Dado un array de enteros nums, devuelve la longitud de la subsecuencia estrictamente creciente más larga.

Explicación: Usa programación dinámica. Sea dp[i] la longitud de la LIS que termina en el índice i. Para calcular dp[i], encuentra todos los j < i tales que nums[j] < nums[i], y toma dp[i] = 1 + max(dp[j]). La respuesta final es el valor máximo en el array dp.

function lengthOfLIS(nums) { if (nums.length === 0) return 0; const n = nums.length; const dp = new Array(n).fill(1); let maxLength = 1; for (let i = 1; i < n; i++) { for (let j = 0; j < i; j++) { if (nums[i] > nums[j]) { dp[i] = Math.max(dp[i], dp[j] + 1); } } maxLength = Math.max(maxLength, dp[i]); } return maxLength; } console.log(lengthOfLIS([10, 9, 2, 5, 3, 7, 101, 18])); // 4 (2, 3, 7, 101)

Problema de las N-Reinas

El problema de las N-Reinas es el problema de colocar N reinas de ajedrez en un tablero de N×N de modo que ninguna de las reinas se amenace entre sí. Dado N, devuelve una solución válida (o todas).

Explicación: Usa retroceso. Coloca las reinas fila por fila. Para cada fila, intenta colocar una reina en cada columna. Antes de colocar, verifica si la posición propuesta es segura (no es atacada por reinas en filas anteriores). Si es segura, colócala y recurre para la siguiente fila. Si la recursión falla, retrocede (quita la reina) y prueba la siguiente columna.

function solveNQueens(n) { const results = []; const board = Array(n).fill('.').map(() => Array(n).fill('.')); function isSafe(row, col) { for (let i = 0; i < row; i++) if (board[i][col] === 'Q') return false; // Verificar columna for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) if (board[i][j] === 'Q') return false; // Verificar diagonal arriba-izquierda for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) if (board[i][j] === 'Q') return false; // Verificar diagonal arriba-derecha return true; } function backtrack(row) { if (row === n) { results.push(board.map(r => r.join(''))); return; } for (let col = 0; col < n; col++) { if (isSafe(row, col)) { board[row][col] = 'Q'; backtrack(row + 1); board[row][col] = '.'; // Retroceder } } } backtrack(0); return results; } console.log(solveNQueens(4)); // [ [ '.Q..', '...Q', 'Q...', '..Q.' ], [ '..Q.', 'Q...', '...Q', '.Q..' ] ]

Usar `WeakMap` para Datos Privados

Demuestra cómo usar WeakMap para almacenar datos privados para instancias de clase.

Explicación: WeakMap te permite asociar datos con un objeto de una manera que no impide la recolección de basura si el objeto ya no es referenciado. Esto es útil para crear miembros 'privados' en clases de JavaScript antes de que los campos privados nativos estuvieran ampliamente disponibles o para casos de uso específicos.

const privateData = new WeakMap(); class Person { constructor(name, age) { privateData.set(this, { name: name, age: age }); } getName() { return privateData.get(this).name; } getAge() { return privateData.get(this).age; } celebrateBirthday() { privateData.get(this).age++; } } const person = new Person('Alicia', 30); console.log(person.getName()); // Alicia person.celebrateBirthday(); console.log(person.getAge()); // 31 // console.log(person.name); // undefined - 'name' no es una propiedad pública // console.log(privateData.get(person)); // Accesible, pero no directamente a través de 'person.'

Implementar `Promise.allSettled`

Implementa una función que se comporte como Promise.allSettled.

Explicación: Promise.allSettled toma un array de promesas y devuelve una única promesa. Esta promesa se cumple cuando todas las promesas de entrada se han resuelto (ya sea cumplidas o rechazadas). El valor de cumplimiento es un array de objetos, cada uno describiendo el resultado de una promesa ({status: 'fulfilled', value: ...} o {status: 'rejected', reason: ...}).

function myPromiseAllSettled(promises) { return new Promise((resolve) => { const numPromises = promises.length; const results = []; let settledCount = 0; if (numPromises === 0) { resolve([]); return; } promises.forEach((promise, index) => { Promise.resolve(promise) .then(value => { results[index] = { status: 'fulfilled', value: value }; }) .catch(reason => { results[index] = { status: 'rejected', reason: reason }; }) .finally(() => { settledCount++; if (settledCount === numPromises) { resolve(results); } }); }); }); } // Ejemplo: myPromiseAllSettled([Promise.resolve(1), Promise.reject('err')]) // -> [{status: 'fulfilled', value: 1}, {status: 'rejected', reason: 'err'}]

Encontrar la Mediana de Dos Arrays Ordenados

Dados dos arrays ordenados nums1 y nums2 de tamaño m y n respectivamente, devuelve la mediana de los dos arrays ordenados.

Explicación: Este es un problema difícil que a menudo se resuelve con un enfoque de búsqueda binaria en el array más pequeño. El objetivo es dividir ambos arrays de tal manera que todos los elementos en el lado izquierdo sean menores o iguales a todos los elementos en el lado derecho, y el número de elementos en el lado izquierdo sea igual (o uno más) al derecho. La mediana se puede calcular a partir de los elementos límite.

function findMedianSortedArrays(nums1, nums2) { if (nums1.length > nums2.length) [nums1, nums2] = [nums2, nums1]; // Asegurarse de que nums1 sea el más pequeño const m = nums1.length, n = nums2.length; let low = 0, high = m; while (low <= high) { const partitionX = Math.floor((low + high) / 2); const partitionY = Math.floor((m + n + 1) / 2) - partitionX; const maxX = (partitionX === 0) ? -Infinity : nums1[partitionX - 1]; const minX = (partitionX === m) ? Infinity : nums1[partitionX]; const maxY = (partitionY === 0) ? -Infinity : nums2[partitionY - 1]; const minY = (partitionY === n) ? Infinity : nums2[partitionY]; if (maxX <= minY && maxY <= minX) { if ((m + n) % 2 === 0) { return (Math.max(maxX, maxY) + Math.min(minX, minY)) / 2; } else { return Math.max(maxX, maxY); } } else if (maxX > minY) { high = partitionX - 1; } else { low = partitionX + 1; } } throw new Error('Los arrays de entrada no están ordenados'); } console.log(findMedianSortedArrays([1, 3], [2])); // 2.0 console.log(findMedianSortedArrays([1, 2], [3, 4])); // 2.5

Resolutor de Sudoku

Escribe un programa para resolver un rompecabezas de Sudoku rellenando las celdas vacías.

Explicación: Usa retroceso. Encuentra una celda vacía. Intenta rellenarla con números del 1 al 9. Para cada número, verifica si es válido (no viola las reglas del Sudoku). Si es válido, llama recursivamente al resolutor. Si la recursión devuelve verdadero, se ha encontrado una solución. Si no, retrocede (reinicia la celda) y prueba el siguiente número.

function solveSudoku(board) { function isValid(row, col, numStr) { for (let i = 0; i < 9; i++) { if (board[row][i] === numStr) return false; // Comprobar fila if (board[i][col] === numStr) return false; // Comprobar columna const boxRow = 3 * Math.floor(row / 3) + Math.floor(i / 3); const boxCol = 3 * Math.floor(col / 3) + i % 3; if (board[boxRow][boxCol] === numStr) return false; // Comprobar cuadro } return true; } function backtrack() { for (let r = 0; r < 9; r++) { for (let c = 0; c < 9; c++) { if (board[r][c] === '.') { for (let n = 1; n <= 9; n++) { const numStr = String(n); if (isValid(r, c, numStr)) { board[r][c] = numStr; if (backtrack()) return true; board[r][c] = '.'; // Retroceder } } return false; // No se encontró un número válido } } } return true; // Tablero resuelto } backtrack(); return board; } // El ejemplo requiere un tablero de 9x9 con '.' para las celdas vacías.

Implementar un Patrón de Middleware Básico

Implementa un patrón de middleware simple que se ve a menudo en los frameworks web.

Explicación: Las funciones de middleware procesan una solicitud antes de que llegue al manejador final. Cada middleware puede modificar la solicitud/respuesta o pasar el control al siguiente middleware. Crea una clase u objeto que gestione una lista de funciones de middleware y las ejecute en secuencia.

class App { constructor() { this.middlewares = []; } use(fn) { this.middlewares.push(fn); } handle(request) { let index = 0; const next = () => { if (index < this.middlewares.length) { const middleware = this.middlewares[index++]; middleware(request, next); } }; next(); } } const app = new App(); app.use((req, next) => { console.log('1: Registrando...'); req.logged = true; next(); }); app.use((req, next) => { console.log('2: Autenticando...'); req.authed = true; next(); }); app.use((req, next) => { console.log('3: Manejador Final:', req); }); app.handle({ data: 'alguna solicitud' }); // 1: Registrando... // 2: Autenticando... // 3: Manejador Final: { data: 'alguna solicitud', logged: true, authed: true }

Detectar Ciclo en Grafo Dirigido

Dado un grafo dirigido, escribe una función para determinar si contiene un ciclo.

Explicación: Usa Búsqueda en Profundidad (DFS). Mantén dos conjuntos: visiting (nodos actualmente en la pila de recursión) y visited (nodos que han sido explorados completamente). Si encuentras un nodo en el conjunto visiting durante DFS, has encontrado una arista hacia atrás, lo que significa que hay un ciclo.

function hasCycleDirected(graph) { const visiting = new Set(); const visited = new Set(); const nodes = Object.keys(graph); function dfs(node) { visiting.add(node); for (const neighbor of graph[node]) { if (visiting.has(neighbor)) return true; // Ciclo detectado if (!visited.has(neighbor)) { if (dfs(neighbor)) return true; } } visiting.delete(node); visited.add(node); return false; } for (const node of nodes) { if (!visited.has(node)) { if (dfs(node)) return true; } } return false; } const cyclicGraph = { A: ['B'], B: ['C'], C: ['A'] }; const acyclicGraph = { A: ['B', 'C'], B: ['D'], C: ['E'], D: [], E: [] }; console.log(hasCycleDirected(cyclicGraph)); // true console.log(hasCycleDirected(acyclicGraph)); // false

Implementar el comportamiento de `Object.freeze`

Explica Object.freeze e implementa una versión (superficial).

Explicación: Object.freeze previene la adición de nuevas propiedades, la eliminación de propiedades existentes y el cambio de propiedades existentes o su enumerabilidad, configurabilidad o capacidad de escritura. Hace que un objeto sea inmutable (superficialmente). Puedes implementarlo usando Object.preventExtensions y Object.defineProperty para hacer que las propiedades existentes no sean escribibles ni configurables.

function myFreeze(obj) { Object.preventExtensions(obj); // Prevenir la adición de nuevas propiedades Object.keys(obj).forEach(key => { Object.defineProperty(obj, key, { writable: false, configurable: false }); }); return obj; } const obj = { a: 1, b: 2 }; myFreeze(obj); // obj.c = 3; // Falla silenciosamente (o lanza un error en modo estricto) // delete obj.a; // Falla silenciosamente (o lanza un error en modo estricto) // obj.a = 10; // Falla silenciosamente (o lanza un error en modo estricto) console.log(obj); // { a: 1, b: 2 }

Usar `requestAnimationFrame` para Animaciones Suaves

Explica requestAnimationFrame y muestra cómo usarlo para un bucle de animación simple.

Explicación: requestAnimationFrame (rAF) le dice al navegador que deseas realizar una animación y solicita que el navegador llame a una función específica para actualizar una animación antes del siguiente repintado. Es más eficiente y suave que usar setTimeout o setInterval para animaciones, ya que se sincroniza con la frecuencia de actualización del navegador y se pausa cuando la pestaña no está visible.

// HTML: <div id='box' style='width: 50px; height: 50px; background: red; position: absolute; left: 0px;'></div> const box = document.getElementById('box'); let position = 0; let startTime = null; function animate(currentTime) { if (!startTime) startTime = currentTime; const elapsedTime = currentTime - startTime; // Mover 100px en 2 segundos (50px/seg) position = (elapsedTime / 2000) * 100; if (position < 200) { // Seguir animando hasta que alcance los 200px box.style.left = position + 'px'; requestAnimationFrame(animate); } else { box.style.left = '200px'; } } // Iniciar la animación // requestAnimationFrame(animate); // Descomentar para ejecutar en el navegador console.log('Usa requestAnimationFrame para animaciones del navegador.');

Implementar `Array.prototype.some`

Implementa tu propia versión de Array.prototype.some.

Explicación: some prueba si al menos un elemento en el array pasa la prueba implementada por la función proporcionada. Devuelve true si encuentra un elemento para el cual la función de callback devuelve true; de lo contrario, devuelve false.

function mySome(arr, callback, thisArg) { for (let i = 0; i < arr.length; i++) { if (i in arr) { // Manejar arrays dispersos if (callback.call(thisArg, arr[i], i, arr)) { return true; } } } return false; } const arr = [1, 2, 3, 4, 5]; const hasEven = mySome(arr, x => x % 2 === 0); console.log(hasEven); // true

Implementar `Array.prototype.every`

Implementa tu propia versión de Array.prototype.every.

Explicación: every prueba si todos los elementos en el array pasan la prueba implementada por la función proporcionada. Devuelve true si todos los elementos pasan (o si el array está vacío), y false si algún elemento falla.

function myEvery(arr, callback, thisArg) { for (let i = 0; i < arr.length; i++) { if (i in arr) { if (!callback.call(thisArg, arr[i], i, arr)) { return false; } } } return true; } const arr1 = [1, 30, 39, 29, 10, 13]; const isBelow40 = myEvery(arr1, x => x < 40); console.log(isBelow40); // true const arr2 = [1, 30, 39, 29, 10, 45]; const isBelow40_2 = myEvery(arr2, x => x < 40); console.log(isBelow40_2); // false

Implementar `Array.prototype.findIndex`

Implementa tu propia versión de Array.prototype.findIndex.

Explicación: findIndex devuelve el índice del primer elemento en el array que satisface la función de prueba proporcionada. De lo contrario, devuelve -1, indicando que ningún elemento pasó la prueba.

function myFindIndex(arr, callback, thisArg) { for (let i = 0; i < arr.length; i++) { if (i in arr) { if (callback.call(thisArg, arr[i], i, arr)) { return i; } } } return -1; } const arr = [5, 12, 8, 130, 44]; const isLargeNumber = (element) => element > 13; console.log(myFindIndex(arr, isLargeNumber)); // 3

¿Qué son los Web Workers?

Explica qué son los Web Workers y su caso de uso principal.

Explicación: Los Web Workers son un medio para que el contenido web ejecute scripts en hilos en segundo plano. El caso de uso principal es descargar tareas de larga duración o computacionalmente intensivas del hilo principal (que maneja las actualizaciones de la interfaz de usuario y las interacciones del usuario), evitando así que la interfaz de usuario se vuelva insensible o 'se congele'. Los Workers se comunican con el hilo principal a través de un paso de mensajes (postMessage y onmessage).

// main.js /* if (window.Worker) { const myWorker = new Worker('worker.js'); myWorker.postMessage([5, 3]); // Enviar datos al worker myWorker.onmessage = function(e) { console.log('Resultado del worker:', e.data); } } else { console.log('Tu navegador no soporta Web Workers.'); } */ // worker.js /* self.onmessage = function(e) { console.log('Mensaje recibido del script principal'); const result = e.data[0] * e.data[1]; console.log('Publicando el resultado de vuelta al script principal'); self.postMessage(result); } */ console.log('Los Web Workers ejecutan scripts en hilos en segundo plano.');

Decodificar Formas

Un mensaje que contiene letras de la A-Z se puede codificar en números usando el mapeo 'A' -> 1, 'B' -> 2, ..., 'Z' -> 26. Dada una cadena s que contiene solo dígitos, devuelve el número de formas de decodificarla.

Explicación: Usa programación dinámica. Sea dp[i] el número de formas de decodificar s[0...i-1]. dp[i] depende de dp[i-1] (si s[i-1] es un código válido de 1 dígito) y dp[i-2] (si s[i-2...i-1] es un código válido de 2 dígitos).

function numDecodings(s) { if (!s || s[0] === '0') return 0; const n = s.length; const dp = new Array(n + 1).fill(0); dp[0] = 1; dp[1] = 1; for (let i = 2; i <= n; i++) { const oneDigit = parseInt(s.substring(i - 1, i)); const twoDigits = parseInt(s.substring(i - 2, i)); if (oneDigit >= 1) { dp[i] += dp[i - 1]; } if (twoDigits >= 10 && twoDigits <= 26) { dp[i] += dp[i - 2]; } } return dp[n]; } console.log(numDecodings('12')); // 2 ('AB' o 'L') console.log(numDecodings('226')); // 3 ('BBF', 'BZ', 'VF') console.log(numDecodings('06')); // 0

Suma Bit a Bit (sin `+`)

Escribe una función para sumar dos enteros sin usar el operador +.

Explicación: Usa operadores bit a bit. La suma se puede calcular como a ^ b (suma sin acarreo), y el acarreo se puede calcular como (a & b) << 1. Repite este proceso hasta que el acarreo se convierta en 0.

function getSum(a, b) { while (b !== 0) { const carry = (a & b) << 1; // Calcular acarreo a = a ^ b; // Calcular suma sin acarreo b = carry; // El acarreo se convierte en el nuevo 'b' } return a; } console.log(getSum(3, 5)); // 8 console.log(getSum(-2, 3)); // 1

Verificar Palíndromo (Número)

Dado un entero x, devuelve true si x es un palíndromo, y false en caso contrario.

Explicación: Un número negativo no es un palíndromo. Convierte el número a una cadena y verifica si la cadena es un palíndromo (se lee igual hacia adelante y hacia atrás). Alternativamente, invierte el número matemáticamente y compara.

function isPalindromeNumber(x) { if (x < 0) return false; // Enfoque de cadena // return String(x) === String(x).split('').reverse().join(''); // Enfoque matemático let original = x; let reversed = 0; while (x > 0) { reversed = reversed * 10 + (x % 10); x = Math.floor(x / 10); } return original === reversed; } console.log(isPalindromeNumber(121)); // true console.log(isPalindromeNumber(-121)); // false console.log(isPalindromeNumber(10)); // false

Patrón Factory

Implementa el patrón de diseño Factory en JavaScript.

Explicación: El patrón Factory proporciona una interfaz para crear objetos en una superclase, pero permite a las subclases alterar el tipo de objetos que se crearán. Es útil para crear objetos sin especificar la clase exacta.

class Car { constructor(options) { this.type = 'Coche'; this.doors = options.doors || 4; } } class Truck { constructor(options) { this.type = 'Camión'; this.bedSize = options.bedSize || 'largo'; } } class VehicleFactory { createVehicle(options) { if (options.vehicleType === 'car') { return new Car(options); } else if (options.vehicleType === 'truck') { return new Truck(options); } else { throw new Error('Tipo de vehículo desconocido'); } } } const factory = new VehicleFactory(); const car = factory.createVehicle({ vehicleType: 'car', doors: 2 }); const truck = factory.createVehicle({ vehicleType: 'truck', bedSize: 'short' }); console.log(car); // Car { type: 'Coche', doors: 2 } console.log(truck); // Truck { type: 'Camión', bedSize: 'short' }

Explicar `Symbol` y un Caso de Uso

Explica qué es Symbol en JavaScript y proporciona un caso de uso, como la creación de propiedades 'privadas' o claves de objeto únicas.

Explicación: Symbol es un tipo de dato primitivo introducido en ES6. Sus instancias son únicas e inmutables. A menudo se utilizan como claves para propiedades de objetos para evitar colisiones de nombres entre diferentes librerías o para crear propiedades pseudo-privadas (aunque no son verdaderamente privadas, no son accesibles a través de la iteración típica o Object.keys).

const _privateName = Symbol('name'); const _privateMethod = Symbol('greet'); class Person { constructor(name) { this[_privateName] = name; } [_privateMethod]() { console.log(`Hola, mi nombre es ${this[_privateName]}`); } introduce() { this[_privateMethod](); } } const p = new Person('Bob'); p.introduce(); // Hola, mi nombre es Bob console.log(Object.keys(p)); // [] - Los Symbols no están incluidos console.log(p[_privateName]); // Bob (Accesible si tienes el Symbol)

Convertir `arguments` a un Array Real

¿Cómo puedes convertir el objeto arguments (disponible en funciones no flecha) en un array real de JavaScript?

Explicación: El objeto arguments parece un array pero no lo es; carece de métodos como map, filter, etc. Puedes convertirlo usando:

  1. Array.from(arguments) (ES6)
  2. [...arguments] (Sintaxis de propagación ES6)
  3. Array.prototype.slice.call(arguments) (Forma antigua)
function sumAll() { // 1. Array.from const args1 = Array.from(arguments); console.log('Usando Array.from:', args1.reduce((a, b) => a + b, 0)); // 2. Sintaxis de propagación const args2 = [...arguments]; console.log('Usando propagación:', args2.reduce((a, b) => a + b, 0)); // 3. Slice const args3 = Array.prototype.slice.call(arguments); console.log('Usando Slice:', args3.reduce((a, b) => a + b, 0)); } sumAll(1, 2, 3, 4); // Usando Array.from: 10 // Usando propagación: 10 // Usando Slice: 10