Chapter 5Funciones de Orden Superior
Tzu-li y Tzu-ssu estaban jactándose del tamaño de sus ultimos programas. ‘Doscientas mil líneas’, dijo Tzu-li, ‘sin contar los comentarios!’ Tzu-ssu respondió, ‘Pssh, el mío tiene casi un millón de líneas ya.’ El Maestro Yuan-Ma dijo, ‘Mi mejor programa tiene quinientas líneas.’ Al escuchar esto, Tzu-li y Tzu-ssu fueron iluminados.
Hay dos formas de construir un diseño de software: Una forma es hacerlo tan simple de manera que no hayan deficiencias obvias, y la otra es hacerlo tan complicado de manera que obviamente no hayan deficiencias.
Un programa grande es un programa costoso, y no solo por el tiempo que se necesita para construirlo. El tamaño casi siempre involucra complejidad, y la complejidad confunde a los programadores. A su vez, los programadores confundidos, introducen errores en los programas. Un programa grande entonces proporciona de mucho espacio para que estos bugs se oculten, haciéndolos difíciles de encontrar.
Volvamos rapidamente a los dos últimos programas de ejemplo en la introducción. El primero es auto-contenido y solo tiene seis líneas de largo:
let total = 0, cuenta = 1; while (cuenta <= 10) { total += cuenta; cuenta += 1; } console.log(total);
El segundo depende de dos funciones externas y tiene una línea de longitud:
console.log(suma(rango(1, 10)));
Cuál es más probable que contenga un bug?
Si contamos el tamaño de las definiciones de suma
y rango
, el segundo programa también es grande—incluso puede que sea más grande que el primero. Pero aún así, argumentaria que es más probable que sea correcto.
Es más probable que sea correcto porque la solución se expresa en un vocabulario que corresponde al problema que se está resolviendo. Sumar un rango de números no se trata acerca de ciclos y contadores. Se trata acerca de rangos y sumas.
Las definiciones de este vocabulario (las funciones suma
y rango
) seguirán involucrando ciclos, contadores y otros detalles incidentales. Pero ya que expresan conceptos más simples que el programa como un conjunto, son más fáciles de realizar correctamente.
Abstracción
En el contexto de la programación, estos tipos de vocabularios suelen ser llamados abstracciones. Las abstracciones esconden detalles y nos dan la capacidad de hablar acerca de los problemas a un nivel superior (o más abstracto).
Como una analogía, compara estas dos recetas de sopa de guisantes:
Coloque 1 taza de guisantes secos por persona en un recipiente. Agregue agua hasta que los guisantes esten bien cubiertos. Deje los guisantes en agua durante al menos 12 horas. Saque los guisantes del agua y pongalos en una cacerola para cocinar. Agregue 4 tazas de agua por persona. Cubra la sartén y mantenga los guisantes hirviendo a fuego lento durante dos horas. Tome media cebolla por persona. Cortela en piezas con un cuchillo. Agréguela a los guisantes. Tome un tallo de apio por persona. Cortelo en pedazos con un cuchillo. Agréguelo a los guisantes. Tome una zanahoria por persona. Cortela en pedazos. Con un cuchillo! Agregarla a los guisantes. Cocine por 10 minutos más.
Por persona: 1 taza de guisantes secos, media cebolla picada, un tallo de apio y una zanahoria.
Remoje los guisantes durante 12 horas. Cocine a fuego lento durante 2 horas en 4 tazas de agua (por persona). Picar y agregar verduras. Cocine por 10 minutos más.
La segunda es más corta y fácil de interpretar. Pero necesitas entender algunas palabras más relacionadas a la cocina—remojar, cocinar a fuego lento, picar, y, supongo, verduras.
Cuando programamos, no podemos confiar en que todas las palabras que necesitaremos estaran esperando por nosotros en el diccionario. Por lo tanto, puedes caer en el patrón de la primera receta—resolviendo los pasos precisos que debe realizar la computadora, uno por uno, ciego a los conceptos de orden superior que estos expresan.
En la programación, es una habilidad útil, darse cuenta cuando estás trabajando en un nivel de abstracción demasiado bajo.
Abstrayendo la repetición
Las funciones simples, como las hemos visto hasta ahora, son una buena forma de construir abstracciones. Pero a veces se quedan cortas.
Es común que un programa haga algo una determinada cantidad de veces. Puedes escribir un ciclo for
para eso, de esta manera:
for (let i = 0; i < 10; i++) { console.log(i); }
Podemos abstraer “hacer algo N veces” como una función? Bueno, es fácil escribir una función que llame a console.log
N cantidad de veces.
function repetirLog(n) { for (let i = 0; i < n; i++) { console.log(i); } }
Pero, y si queremos hacer algo más que loggear los números? Ya que “hacer algo” se puede representar como una función y que las funciones solo son valores, podemos pasar nuestra acción como un valor de función.
function repetir(n, accion) { for (let i = 0; i < n; i++) { accion(i); } } repetir(3, console.log); // → 0 // → 1 // → 2
No es necesario que le pases una función predefinida a repetir
. A menudo, desearas crear un valor de función al momento en su lugar.
let etiquetas = []; repetir(5, i => { etiquetas.push(`Unidad ${i + 1}`); }); console.log(etiquetas); // → ["Unidad 1", "Unidad 2", "Unidad 3", "Unidad 4", "Unidad 5"]
Esto está estructurado un poco como un ciclo for
—primero describe el tipo de ciclo, y luego provee un cuerpo. Sin embargo, el cuerpo ahora está escrito como un valor de función, que está envuelto en el paréntesis de la llamada a repetir
. Por eso es que tiene que cerrarse con el corchete de cierre y paréntesis de cierre. En casos como este ejemplo, donde el cuerpo es una expresión pequeña y única, podrias tambien omitir las llaves y escribir el ciclo en una sola línea.
Funciones de orden superior
Las funciones que operan en otras funciones, ya sea tomándolas como argumentos o retornandolas, se denominan funciones de orden superior. Como ya hemos visto que las funciones son valores regulares, no existe nada particularmente notable sobre el hecho de que tales funciones existen. El término proviene de las matemáticas, donde la distinción entre funciones y otros valores se toma más en serio.
Las funciones de orden superior nos permiten abstraer sobre acciones, no solo sobre valores. Estas vienen en varias formas. Por ejemplo, puedes tener funciones que crean nuevas funciones.
function mayorQue(n) { return m => m > n; } let mayorQue10 = mayorQue(10); console.log(mayorQue10(11)); // → true
Y puedes tener funciones que cambien otras funciones.
function ruidosa(funcion) { return (argumentos) => { console.log("llamando con", argumentos); let resultado = funcion(argumentos); console.log("llamada con", argumentos, ", retorno", resultado); return resultado; }; } ruidosa(Math.min)(3, 2, 1); // → llamando con [3, 2, 1] // → llamada con [3, 2, 1] , retorno 1
Incluso puedes escribir funciones que proporcionen nuevos tipos de flujo de control.
function aMenosQue(prueba, entonces) { if (!prueba) entonces(); } repetir(3, n => { aMenosQue(n % 2 == 1, () => { console.log(n, "es par"); }); }); // → 0 es par // → 2 es par
Hay un método de array incorporado, forEach
que proporciona algo como un ciclo for
/of
como una función de orden superior.
["A", "B"].forEach(letra => console.log(letra)); // → A // → B
Conjunto de datos de códigos
Un área donde brillan las funciones de orden superior es en el procesamiento de datos. Para procesar datos, necesitaremos algunos datos reales. Este capítulo usara un conjunto de datos acerca de códigos—sistema de escrituras como Latin, Cirílico, o Arábico.
Recuerdas Unicode del Capítulo 1, el sistema que asigna un número a cada carácter en el lenguaje escrito. La mayoría de estos carácteres están asociados a un código específico. El estandar contiene 140 codigos diferentes—81 de los cuales todavía están en uso hoy, y 59 que son históricos.
Aunque solo puedo leer con fluidez los caracteres en Latin, aprecio el hecho de que las personas estan escribiendo textos en al menos 80 diferentes sistemas de escritura, muchos de los cuales ni siquiera reconocería. Por ejemplo, aquí está una muestra de escritura a mano en Tamil.
El conjunto de datos de ejemplo contiene algunos piezas de información acerca de los 140 codigos definidos en Unicode. Este esta disponible en la caja de arena para este capítulo como la vinculación SCRIPTS
. La vinculación contiene un array de objetos, cada uno de los cuales describe un codigo.
{ name: "Coptic", ranges: [[994, 1008], [11392, 11508], [11513, 11520]], direction: "ltr", year: -200, living: false, link: "https://en.wikipedia.org/wiki/Coptic_alphabet" }
Tal objeto te dice el nombre del codigo, los rangos de Unicode asignados a él, la dirección en la que está escrito, la tiempo de origen (aproximado), si todavía está en uso, y un enlace a más información. La dirección en la que esta escrito puede ser "ltr"
(left-to-right) para izquierda a derecha, "rtl"
(right-to-left) para derecha a izquierda (la forma en que se escriben los textos en árabe y en hebreo), o "ttb"
(top-to-bottom) para de arriba a abajo (como con la escritura de Mongolia).
La propiedad ranges
contiene un array de rangos de caracteres Unicode, cada uno de los cuales es un array de dos elementos que contiene límites inferior y superior. Se asignan los códigos de caracteres dentro de estos rangos al codigo. El limite más bajo es inclusivo (el código 994 es un carácter Copto) y el límite superior es no-inclusivo (el código 1008 no lo es).
Filtrando arrays
Para encontrar los codigos en el conjunto de datos que todavía están en uso, la siguiente función podría ser útil. Filtra hacia afuera los elementos en un array que no pasen una prueba:
function filtrar(array, prueba) { let pasaron = []; for (let elemento of array) { if (prueba(elemento)) { pasaron.push(elemento); } } return pasaron; } console.log(filtrar(SCRIPTS, codigo => codigo.living)); // → [{name: "Adlam", …}, …]
La función usa el argumento llamado prueba
, un valor de función, para llenar una “brecha” en el cálculo—el proceso de decidir qué elementos recolectar.
Observa cómo la función filtrar
, en lugar de eliminar elementos del array existente, crea un nuevo array solo con los elementos que pasan la prueba. Esta función es pura. No modifica el array que se le es dado.
Al igual que forEach
, filtrar
es un método de array estándar, este esta incorporado como filter
. El ejemplo definió la función solo para mostrar lo que hace internamente. A partir de ahora, la usaremos así en su lugar:
console.log(SCRIPTS.filter(codigo => codigo.direction == "ttb")); // → [{name: "Mongolian", …}, …]
Transformando con map
Digamos que tenemos un array de objetos que representan codigos, producidos al filtrar el array SCRIPTS
de alguna manera. Pero queremos un array de nombres, que es más fácil de inspeccionar
El método map
(“mapear”) transforma un array al aplicar una función a todos sus elementos y construir un nuevo array a partir de los valores retornados. El nuevo array tendrá la misma longitud que el array de entrada, pero su contenido ha sido mapeado a una nueva forma en base a la función.
function map(array, transformar) { let mapeados = []; for (let elemento of array) { mapeados.push(transformar(elemento)); } return mapeados; } let codigosDerechaAIzquierda = SCRIPTS.filter(codigo => codigo.direction == "rtl"); console.log(map(codigosDerechaAIzquierda, codigo => codigo.name)); // → ["Adlam", "Arabic", "Imperial Aramaic", …]
Al igual que forEach
y filter
, map
es un método de array estándar.
Resumiendo con reduce
Otra cosa común que hacer con arrays es calcular un valor único a partir de ellos. Nuestro ejemplo recurrente, sumar una colección de números, es una instancia de esto. Otro ejemplo sería encontrar el codigo con la mayor cantidad de caracteres.
La operación de orden superior que representa este patrón se llama reduce (“reducir”)—a veces también llamada fold (“doblar”). Esta construye un valor al repetidamente tomar un solo elemento del array y combinándolo con el valor actual. Al sumar números, comenzarías con el número cero y, para cada elemento, agregas eso a la suma.
Los parámetros para reduce
son, además del array, una función de combinación y un valor de inicio. Esta función es un poco menos sencilla que filter
y map
, así que mira atentamente:
function reduce(array, combinar, inicio) { let actual = inicio; for (let elemento of array) { actual = combinar(actual, elemento); } return actual; } console.log(reduce([1, 2, 3, 4], (a, b) => a + b, 0)); // → 10
El método de array estándar reduce
, que por supuesto corresponde a esta función tiene una mayor comodidad. Si tu array contiene al menos un elemento, tienes permitido omitir el argumento inicio
. El método tomará el primer elemento del array como su valor de inicio y comienza a reducir a partir del segundo elemento.
console.log([1, 2, 3, 4].reduce((a, b) => a + b)); // → 10
Para usar reduce
(dos veces) para encontrar el codigo con la mayor cantidad de caracteres, podemos escribir algo como esto:
function cuentaDeCaracteres(codigo) { return codigo.ranges.reduce((cuenta, [desde, hasta]) => { return cuenta + (hasta - desde); }, 0); } console.log(SCRIPTS.reduce((a, b) => { return cuentaDeCaracteres(a) < cuentaDeCaracteres(b) ? b : a; })); // → {name: "Han", …}
La función cuentaDeCaracteres
reduce los rangos asignados a un codigo sumando sus tamaños. Ten en cuenta el uso de la desestructuración en el parámetro lista de la función reductora. La segunda llamada a reduce
luego usa esto para encontrar el codigo más grande al comparar repetidamente dos scripts y retornando el más grande.
El codigo Han tiene más de 89,000 caracteres asignados en el Estándar Unicode, por lo que es, por mucho, el mayor sistema de escritura en el conjunto de datos. Han es un codigo (a veces) usado para texto chino, japonés y coreano. Esos idiomas comparten muchos caracteres, aunque tienden a escribirlos de manera diferente. El consorcio Unicode (con sede en EE.UU.) decidió tratarlos como un único sistema de escritura para ahorrar códigos de caracteres. Esto se llama unificación Han y aún enoja bastante a algunas personas.
Composabilidad
Considera cómo habríamos escrito el ejemplo anterior (encontrar el código más grande) sin funciones de orden superior. El código no es mucho peor.
let mayor = null; for (let codigo of SCRIPTS) { if (mayor == null || cuentaDeCaracteres(mayor) < cuentaDeCaracteres(codigo)) { mayor = codigo; } } console.log(mayor); // → {name: "Han", …}
Hay algunos vinculaciones más, y el programa tiene cuatro líneas más. Pero todavía es bastante legible.
Las funciones de orden superior comienzan a brillar cuando necesitas componer operaciones. Como ejemplo, vamos a escribir código que encuentre el año de origen promedio para los codigos vivos y muertos en el conjunto de datos.
function promedio(array) { return array.reduce((a, b) => a + b) / array.length; } console.log(Math.round(promedio( SCRIPTS.filter(codigo => codigo.living).map(codigo => codigo.year)))); // → 1185 console.log(Math.round(promedio( SCRIPTS.filter(codigo => !codigo.living).map(codigo => codigo.year)))); // → 209
Entonces, los codigos muertos en Unicode son, en promedio, más antiguos que los vivos. Esta no es una estadística terriblemente significativa o sorprendente. Pero espero que aceptes que el código utilizado para calcularlo no es difícil de leer. Puedes verlo como una tubería: comenzamos con todos los codigos, filtramos los vivos (o muertos), tomamos los años de aquellos, los promediamos, y redondeamos el resultado.
Definitivamente también podrías haber escribir este codigo como un gran ciclo.
let total = 0, cuenta = 0; for (let codigo of SCRIPTS) { if (codigo.living) { total += codigo.year; cuenta += 1; } } console.log(Math.round(total / cuenta)); // → 1185
Pero es más difícil de ver qué se está calculando y cómo. Y ya que los resultados intermedios no se representan como valores coherentes, sería mucho más trabajo extraer algo así como promedio
en una función aparte.
En términos de lo que la computadora realmente está haciendo, estos dos enfoques también son bastante diferentes. El primero creará nuevos arrays al ejecutar filter
y map
, mientras que el segundo solo computa algunos números, haciendo menos trabajo. Por lo general, puedes permitirte el enfoque legible, pero si estás procesando arrays enormes, y haciendolo muchas veces, el estilo menos abstracto podría ser mejor debido a la velocidad extra.
Strings y códigos de caracteres
Un uso del conjunto de datos sería averiguar qué código esta usando una pieza de texto. Veamos un programa que hace esto.
Recuerda que cada codigo tiene un array de rangos para los códigos de caracteres asociados a el. Entonces, dado un código de carácter, podríamos usar una función como esta para encontrar el codigo correspondiente (si lo hay):
function codigoCaracter(codigo_caracter) { for (let codigo of SCRIPTS) { if (codigo.ranges.some(([desde, hasta]) => { return codigo_caracter >= desde && codigo_caracter < hasta; })) { return codigo; } } return null; } console.log(codigoCaracter(121)); // → {name: "Latin", …}
El método some
(“alguno”) es otra función de orden superior. Toma una función de prueba y te dice si esa función retorna verdadero para cualquiera de los elementos en el array.
Pero cómo obtenemos los códigos de los caracteres en un string?
En el Capítulo 1 mencioné que los strings de JavaScript estan codificados como una secuencia de números de 16 bits. Estos se llaman unidades de código. Inicialmente se suponía que un código de carácter Unicode encajara dentro de esa unidad (lo que da un poco más de 65,000 caracteres). Cuando quedó claro que esto no seria suficiente, muchas las personas se resistieron a la necesidad de usar más memoria por carácter. Para apaciguar estas preocupaciones, UTF-16, el formato utilizado por los strings de JavaScript, fue inventado. Este describe la mayoría de los caracteres mas comunes usando una sola unidad de código de 16 bits, pero usa un par de dos de esas unidades para otros caracteres.
Al dia de hoy UTF-16 generalmente se considera como una mala idea. Parece casi intencionalmente diseñado para invitar a errores. Es fácil escribir programas que pretenden que las unidades de código y caracteres son la misma cosa. Y si tu lenguaje no usa caracteres de dos unidades, esto parecerá funcionar simplemente bien. Pero tan pronto como alguien intente usar dicho programa con algunos menos comunes caracteres chinos, este se rompe. Afortunadamente, con la llegada del emoji, todo el mundo ha empezado a usar caracteres de dos unidades, y la carga de lidiar con tales problemas esta bastante mejor distribuida.
Desafortunadamente, las operaciones obvias con strings de JavaScript, como obtener su longitud a través de la propiedad length
y acceder a su contenido usando corchetes, trata solo con unidades de código.
// Dos caracteres emoji, caballo y zapato let caballoZapato = "🐴👟"; console.log(caballoZapato.length); // → 4 console.log(caballoZapato[0]); // → ((Medio-carácter inválido)) console.log(caballoZapato.charCodeAt(0)); // → 55357 (Código del medio-carácter) console.log(caballoZapato.codePointAt(0)); // → 128052 (Código real para emoji de caballo)
El método charCodeAt
de JavaScript te da una unidad de código, no un código de carácter completo. El método codePointAt
, añadido despues, si da un carácter completo de Unicode. Entonces podríamos usarlo para obtener caracteres de un string. Pero el argumento pasado a codePointAt
sigue siendo un índice en la secuencia de unidades de código. Entonces, para hacer un ciclo a traves de todos los caracteres en un string, todavía tendríamos que lidiar con la cuestión de si un carácter ocupa una o dos unidades de código.
En el capítulo anterior, mencioné que el ciclo for
/of
también se puede usar en strings. Como codePointAt
, este tipo de ciclo se introdujo en un momento en que las personas eran muy conscientes de los problemas con UTF-16. Cuando lo usas para hacer un ciclo a traves de un string, te da caracteres reales, no unidades de código.
let dragonRosa = "🐉🌹"; for (let caracter of dragonRosa) { console.log(caracter); } // → 🐉 // → 🌹
Si tienes un caracter (que será un string de unidades de uno o dos códigos), puedes usar codePointAt(0)
para obtener su código.
Reconociendo texto
Tenemos una función codigoCaracter
y una forma de correctamente hacer un ciclo a traves de caracteres. El siguiente paso sería contar los caracteres que pertenecen a cada codigo. La siguiente abstracción de conteo será útil para eso:
function contarPor(elementos, nombreGrupo) { let cuentas = []; for (let elemento of elementos) { let nombre = nombreGrupo(elemento); let conocido = cuentas.findIndex(c => c.nombre == nombre); if (conocido == -1) { cuentas.push({nombre, cuenta: 1}); } else { cuentas[conocido].cuenta++; } } return cuentas; } console.log(contarPor([1, 2, 3, 4, 5], n => n > 2)); // → [{nombre: false, cuenta: 2}, {nombre: true, cuenta: 3}]
La función contarPor
espera una colección (cualquier cosa con la que podamos hacer un ciclo for
/of
) y una función que calcula un nombre de grupo para un elemento dado. Retorna un array de objetos, cada uno de los cuales nombra un grupo y te dice la cantidad de elementos que se encontraron en ese grupo.
Utiliza otro método de array—findIndex
(“encontrar index”). Este método es algo así como indexOf
, pero en lugar de buscar un valor específico, este encuentra el primer valor para el cual la función dada retorna verdadero. Como indexOf
, retorna -1 cuando no se encuentra dicho elemento.
Usando contarPor
, podemos escribir la función que nos dice qué codigos se usan en una pieza de texto.
function codigosTexto(texto) { let codigos = contarPor(texto, caracter => { let codigo = codigoCaracter(caracter.codePointAt(0)); return codigo ? codigo.name : "ninguno"; }).filter(({name}) => name != "ninguno"); let total = codigos.reduce((n, {count}) => n + count, 0); if (total == 0) return "No se encontraron codigos"; return codigos.map(({name, count}) => { return `${Math.round(count * 100 / total)}% ${name}`; }).join(", "); } console.log(codigosTexto('英国的狗说"woof", 俄罗斯的狗说"тяв"')); // → 61% Han, 22% Latin, 17% Cyrillic
La función primero cuenta los caracteres por nombre, usando codigoCaracter
para asignarles un nombre, y recurre al string "ninguno"
para caracteres que no son parte de ningún codigo. La llamada filter
deja afuera las entrada para "ninguno"
del array resultante, ya que no estamos interesados en esos caracteres.
Para poder calcular porcentajes, primero necesitamos la cantidad total de caracteres que pertenecen a un codigo, lo que podemos calcular con reduce
. Si no se encuentran tales caracteres, la función retorna un string específico. De lo contrario, transforma las entradas de conteo en strings legibles con map
y luego las combina con join
.
Resumen
Ser capaz de pasar valores de función a otras funciones es un aspecto profundamente útil de JavaScript. Nos permite escribir funciones que modelen calculos con “brechas” en ellas. El código que llama a estas funciones pueden llenar estas brechas al proporcionar valores de función.
Los arrays proporcionan varios métodos útiles de orden superior. Puedes usar forEach
para recorrer los elementos en un array. El método filter
retorna un nuevo array que contiene solo los elementos que pasan una función de predicado. Transformar un array al poner cada elemento a través de una función se hace con map
. Puedes usar reduce
para combinar todos los elementos en una array a un solo valor. El método some
prueba si algun elemento coincide con una función de predicado determinada. Y findIndex
encuentra la posición del primer elemento que coincide con un predicado.
Ejercicios
Aplanamiento
Use el método reduce
en combinación con el método concat
para “aplanar” un array de arrays en un único array que tenga todos los elementos de los arrays originales.
let arrays = [[1, 2, 3], [4, 5], [6]]; // Tu código aquí. // → [1, 2, 3, 4, 5, 6]
Tu propio ciclo
Escriba una función de orden superior llamada ciclo
que proporcione algo así como una declaración de ciclo for
. Esta toma un valor, una función de prueba, una función de actualización y un cuerpo de función. En cada iteración, primero ejecuta la función de prueba en el valor actual del ciclo y se detiene si esta retorna falso. Luego llama al cuerpo de función, dándole el valor actual. Y finalmente, llama a la función de actualización para crear un nuevo valor y comienza desde el principio.
Cuando definas la función, puedes usar un ciclo regular para hacer los ciclos reales.
// Tu código aquí. loop(3, n => n > 0, n => n - 1, console.log); // → 3 // → 2 // → 1
Cada
De forma análoga al método some
, los arrays también tienen un método every
(“cada”). Este retorna true cuando la función dada devuelve verdadero para cada elemento en el array. En cierto modo, some
es una versión del operador ||
que actúa en arrays, y every
es como el operador &&
.
Implementa every
como una función que tome un array y una función predicado como parámetros. Escribe dos versiones, una usando un ciclo y una usando el método some
.
function cada(array, test) { // Tu código aquí. } console.log(cada([1, 3, 5], n => n < 10)); // → true console.log(cada([2, 4, 16], n => n < 10)); // → false console.log(cada([], n => n < 10)); // → true
Al igual que el operador &&
, el método every
puede dejar de evaluar más elementos tan pronto como haya encontrado uno que no coincida. Entonces la versión basada en un ciclo puede saltar fuera del ciclo—con break
o return
—tan pronto como se encuentre con un elemento para el cual la función predicado retorne falso. Si el ciclo corre hasta su final sin encontrar tal elemento, sabemos que todos los elementos coinciden y debemos retornar verdadero.
Para construir cada
usando some
, podemos aplicar las leyes De
Morgan, que establecen que a && b
es igual a !(!a ||! b)
. Esto puede ser generalizado a arrays, donde todos los elementos del array coinciden si no hay elemento en el array que no coincida.
Dirección de Escritura Dominante
Escriba una función que calcule la dirección de escritura dominante en un string de texto. Recuerde que cada objeto de codigo tiene una propiedad direction
que puede ser "ltr"
(de izquierda a derecha), "rtl"
(de derecha a izquierda), o "ttb"
(arriba a abajo).
La dirección dominante es la dirección de la mayoría de los caracteres que tienen un código asociado a ellos. Las funciones codigoCaracter
y contarPor
definidas anteriormente en el capítulo probablemente seran útiles aquí.
function direccionDominante(texto) { // Tu código aquí. } console.log(direccionDominante("Hola!")); // → ltr console.log(direccionDominante("Hey, مساء الخير")); // → rtl
Tu solución puede parecerse mucho a la primera mitad del ejemplo codigosTexto
. De nuevo debes contar los caracteres por el criterio basado en codigoCaracter
, y luego filtrar hacia afuera la parte del resultado que se refiere a caracteres sin interés (que no tengan codigos).
Encontrar la dirección con la mayor cantidad de caracteres se puede hacer con reduce
. Si no está claro cómo, refiérate al ejemplo anterior en el capítulo, donde se usa reduce
para encontrar el código con la mayoría de los caracteres.