-
Notifications
You must be signed in to change notification settings - Fork 76
Expand file tree
/
Copy path05_higher_order.html
More file actions
498 lines (342 loc) · 57.4 KB
/
Copy path05_higher_order.html
File metadata and controls
498 lines (342 loc) · 57.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
<!doctype html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Funciones de Orden Superior :: Eloquent JavaScript</title>
<link rel=stylesheet href="css/ejs.css"><script>
var page = {"type":"chapter","number":5,"load_files":["code/scripts.js","code/chapter/05_higher_order.js","code/intro.js"]}</script></head>
<article>
<nav><a href="04_data.html" title="previous chapter" aria-label="previous chapter">◂</a> <a href="index.html" title="cover" aria-label="cover">●</a> <a href="06_object.html" title="next chapter" aria-label="next chapter">▸</a> <button class=help title="help" aria-label="help"><strong>?</strong></button>
</nav>
<h1>Funciones de Orden Superior</h1>
<p><a class="p_ident" id="p-eV2Rh7S+DT" href="#p-eV2Rh7S+DT" tabindex="-1" role="presentation"></a><em>“Hay dos maneras de construir un diseño de software: una forma es hacerlo tan simple que obviamente no haya defectos, y la otra forma es hacerlo tan complicado que no haya defectos obvios.”</em></p>
<p><a class="p_ident" id="p-rComRYoME7" href="#p-rComRYoME7" tabindex="-1" role="presentation"></a>— C.A.R. Hoare, <em>Discurso de Recepción del Premio Turing de la ACM de 1980</em></p>
<p><a class="p_ident" id="p-UkjkuKBNDn" href="#p-UkjkuKBNDn" tabindex="-1" role="presentation"></a>Un programa grande es un programa costoso, y no solo por el tiempo que lleva construirlo. El tamaño casi siempre implica complejidad, y la complejidad confunde a los programadores. Los programadores confundidos, a su vez, introducen errores (<em>bugs</em>) en los programas. Un programa grande da mucho hueco para que estos errores se escondan, lo que los hace difíciles de encontrar.</p>
<p><a class="p_ident" id="p-Q033k+pXx6" href="#p-Q033k+pXx6" tabindex="-1" role="presentation"></a>Vamos a volver por un momento a los dos ejemplos de programas del final de la introducción. El primero es autocontenido y tiene seis líneas:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-NaiLvX4kR2" href="#c-NaiLvX4kR2" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">total</span> = <span class="tok-number">0</span>, <span class="tok-definition">contador</span> = <span class="tok-number">1</span>;
<span class="tok-keyword">while</span> (contador <= <span class="tok-number">10</span>) {
total += contador;
contador += <span class="tok-number">1</span>;
}
console.log(total);</pre>
<p><a class="p_ident" id="p-XpPbbvrjf7" href="#p-XpPbbvrjf7" tabindex="-1" role="presentation"></a>El segundo depende de dos funciones externas y tiene una línea:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-pxsnyeb5dE" href="#c-pxsnyeb5dE" tabindex="-1" role="presentation"></a>console.log(suma(rango(<span class="tok-number">1</span>, <span class="tok-number">10</span>)));</pre>
<p><a class="p_ident" id="p-mQkKtUTj8f" href="#p-mQkKtUTj8f" tabindex="-1" role="presentation"></a>¿Cuál es más probable que contenga un error?</p>
<p><a class="p_ident" id="p-kr8iBt6cDE" href="#p-kr8iBt6cDE" tabindex="-1" role="presentation"></a>Si contamos el tamaño de las definiciones de <code>suma</code> y <code>rango</code>, el segundo programa también es grande, incluso más que el primero. Pero, aún así, diría que es más probable que sea correcto.</p>
<p><a class="p_ident" id="p-DC41zE9LVe" href="#p-DC41zE9LVe" tabindex="-1" role="presentation"></a>Esto se debe a que la solución se expresa en un vocabulario que corresponde al problema que se está resolviendo. Sumar un intervalo de números no va considerar bucles y contadores. Va de intervalos y sumas.</p>
<p><a class="p_ident" id="p-ZVaOtVwzgJ" href="#p-ZVaOtVwzgJ" tabindex="-1" role="presentation"></a>Las definiciones de este vocabulario (las funciones <code>suma</code> y <code>rango</code>) no dejan de consistir en trabajar con bucles, contadores y otros detalles. Pero debido a que expresan conceptos más simples que el programa en su totalidad, son más fáciles de hacer correctamente.</p>
<h2><a class="h_ident" id="h-jeqhU5hoax" href="#h-jeqhU5hoax" tabindex="-1" role="presentation"></a>Abstracción</h2>
<p><a class="p_ident" id="p-b96WneVaDi" href="#p-b96WneVaDi" tabindex="-1" role="presentation"></a>En el contexto de la programación, este tipo de vocabularios se suelen llamar <em>abstracciones</em>. Las abstracciones nos brindan la capacidad de hablar sobre problemas a un nivel superior (o más abstracto), sin distraernos con detalles no interesantes.</p>
<p><a class="p_ident" id="p-vujVr5jCqZ" href="#p-vujVr5jCqZ" tabindex="-1" role="presentation"></a>Como analogía, compara estas dos recetas de sopa de guisantes. La primera es así:</p>
<blockquote>
<p><a class="p_ident" id="p-bHDeGVTCo+" href="#p-bHDeGVTCo+" tabindex="-1" role="presentation"></a>Pon 1 taza de guisantes secos por persona en un recipiente. Añade agua hasta que los guisantes estén bien cubiertos. Deja los guisantes en agua durante al menos 12 horas. Saca los guisantes del agua y ponlos en una olla. Agrega 4 tazas de agua por persona. Cubre la olla y deja los guisantes cociendo a fuego lento durante dos horas. Toma media cebolla por persona. Córtala en trozos con un cuchillo. Agrégala a los guisantes. Toma un tallo de apio por persona. Córtalo en trozos con un cuchillo. Agrégalo a los guisantes. Toma una zanahoria por persona. ¡Córtala en trozos! ¡Con un cuchillo! Agrégala a los guisantes. Cocina durante 10 minutos más.</p>
</blockquote>
<p><a class="p_ident" id="p-dL6mnA4R6A" href="#p-dL6mnA4R6A" tabindex="-1" role="presentation"></a>Y esta es la segunda receta:</p>
<blockquote>
<p><a class="p_ident" id="p-tZ51jSgceM" href="#p-tZ51jSgceM" tabindex="-1" role="presentation"></a>Por persona: 1 taza de guisantes partidos secos, 4 tazas de agua, media cebolla picada, un tallo de apio y una zanahoria.</p>
<p><a class="p_ident" id="p-pWLdoICo3H" href="#p-pWLdoICo3H" tabindex="-1" role="presentation"></a>Remoja los guisantes durante 12 horas. Cocina a fuego lento durante 2 horas. Pica y agrega las verduras. Cocina durante 10 minutos más.</p>
</blockquote>
<p><a class="p_ident" id="p-d9+qq62FnP" href="#p-d9+qq62FnP" tabindex="-1" role="presentation"></a>La segunda es más corta y fácil de interpretar. Pero necesitas entender algunas palabras más relacionadas con la cocina, como <em>remojar</em>, <em>cocinar a fuego lento</em>, <em>picar</em>, y, supongo, <em>verdura</em>.</p>
<p><a class="p_ident" id="p-qhHkdQl/il" href="#p-qhHkdQl/il" tabindex="-1" role="presentation"></a>Cuando se programa, no podemos depender de que todas las palabras que necesitamos estén ya escritas en el diccionario para nosotros. Por lo tanto, podríamos caer en el patrón de la primera receta: ejecutar los pasos precisos que la computadora tiene que realizar, uno por uno, sin atender a los conceptos de más alto nivel que expresan.</p>
<p><a class="p_ident" id="p-k17Wm3bRzR" href="#p-k17Wm3bRzR" tabindex="-1" role="presentation"></a>Una habilidad útil en programación es darse cuenta de cuándo se está trabajando a un muy bajo nivel de abstracción.</p>
<h2><a class="h_ident" id="h-3uF6LnSOqd" href="#h-3uF6LnSOqd" tabindex="-1" role="presentation"></a>Abstraer la repetición</h2>
<p><a class="p_ident" id="p-ycCWB39qtV" href="#p-ycCWB39qtV" tabindex="-1" role="presentation"></a>Funciones simples como las hemos visto hasta ahora son una buena manera de construir abstracciones. Pero a veces se quedan cortas.</p>
<p><a class="p_ident" id="p-S9ZNhpazRV" href="#p-S9ZNhpazRV" tabindex="-1" role="presentation"></a>Es común que un programa haga algo una cantidad determinada de veces. Puedes escribir un <code>for</code> para eso, así:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-5c+C2+9IG1" href="#c-5c+C2+9IG1" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">i</span> = <span class="tok-number">0</span>; i < <span class="tok-number">10</span>; i++) {
console.log(i);
}</pre>
<p><a class="p_ident" id="p-mYLOzu14uu" href="#p-mYLOzu14uu" tabindex="-1" role="presentation"></a>¿Podemos abstraer “hacer algo <em>N</em> veces” como una función? Bueno, es fácil escribir una función que llame a <code>console.log</code> <em>N</em> veces:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-01lxFf/pce" href="#c-01lxFf/pce" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">repetirLog</span>(<span class="tok-definition">n</span>) {
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">i</span> = <span class="tok-number">0</span>; i < n; i++) {
console.log(i);
}
}</pre>
<p><a class="p_ident" id="p-aHRJYV9dtZ" href="#p-aHRJYV9dtZ" tabindex="-1" role="presentation"></a>¿Y si queremos hacer algo que no sea solo pintar los los números? Dado que “hacer algo” se puede representar como una función y las funciones son solo valores, podemos pasar nuestra acción como un valor de función:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-PTLJYIA4FA" href="#c-PTLJYIA4FA" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">repetir</span>(<span class="tok-definition">n</span>, <span class="tok-definition">acción</span>) {
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">i</span> = <span class="tok-number">0</span>; i < n; i++) {
acción(i);
}
}
repetir(<span class="tok-number">3</span>, console.log);
<span class="tok-comment">// → 0</span>
<span class="tok-comment">// → 1</span>
<span class="tok-comment">// → 2</span></pre>
<p><a class="p_ident" id="p-MZU1WHGlo3" href="#p-MZU1WHGlo3" tabindex="-1" role="presentation"></a>No tenemos que pasar una función predefinida a <code>repetir</code>. A menudo, es más fácil crear un valor de función en el momento:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-61hbFnWHXS" href="#c-61hbFnWHXS" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">etiquetas</span> = [];
repetir(<span class="tok-number">5</span>, <span class="tok-definition">x</span> => {
etiquetas.push(<span class="tok-string2">`Unidad </span>${x + <span class="tok-number">1</span>}<span class="tok-string2">`</span>);
});
console.log(etiquetas);
<span class="tok-comment">// → ["Unidad 1", "Unidad 2", "Unidad 3", "Unidad 4", "Unidad 5"]</span></pre>
<div class="translator-note"><p><strong>N. del T.:</strong> Con respecto a la versión original del texto, se ha cambiado el nombre del parámetro en la función flecha de <code>i</code> a <code>x</code> para enfatizar la no necesidad de que el parámetro de dicha función se llame como el parámetro contador del bucle for de la implementación de la función <code>repetir</code>.</p>
</div>
<p><a class="p_ident" id="p-ZaxR2WEHob" href="#p-ZaxR2WEHob" tabindex="-1" role="presentation"></a>Esto está estructurado un poco como un bucle <code>for</code>: primero describe el tipo de bucle y luego proporciona un cuerpo. Sin embargo, el cuerpo ahora está escrito como un valor de función, que está envuelto entre los paréntesis de la llamada a <code>repetir</code>. Por eso tiene que cerrarse con el corchete de cierre <em>y</em> el paréntesis de cierre. En casos como este ejemplo donde el cuerpo es una sola expresión pequeña, también podrías omitir los corchetes y escribir el bucle en una sola línea.</p>
<h2><a class="h_ident" id="h-sBm/RoOmVE" href="#h-sBm/RoOmVE" tabindex="-1" role="presentation"></a>Funciones de orden superior</h2>
<p><a class="p_ident" id="p-mB+p2eklnH" href="#p-mB+p2eklnH" tabindex="-1" role="presentation"></a>Las funciones que operan sobre otras funciones, ya sea tomándolas como argumentos o devolviéndolas, se llaman <em>funciones de orden superior</em>. Dado que ya hemos visto que las funciones son valores como cualquier otro, no hay nada particularmente notable en el hecho de que existan tales funciones. El término proviene de las matemáticas, donde se toma más en serio la distinción entre funciones y otros valores.</p>
<p><a class="p_ident" id="p-sGlpxAbe6R" href="#p-sGlpxAbe6R" tabindex="-1" role="presentation"></a>Las funciones de orden superior nos permiten abstraer <em>acciones</em>, no solo valores. Las hay de muchas formas. Por ejemplo, podemos tener funciones que crean nuevas funciones:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-yl26zgd/Q+" href="#c-yl26zgd/Q+" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">mayorQue</span>(<span class="tok-definition">n</span>) {
<span class="tok-keyword">return</span> <span class="tok-definition">m</span> => m > n;
}
<span class="tok-keyword">let</span> <span class="tok-definition">mayorQue10</span> = mayorQue(<span class="tok-number">10</span>);
console.log(mayorQue10(<span class="tok-number">11</span>));
<span class="tok-comment">// → true</span></pre>
<p><a class="p_ident" id="p-ZzQufg6k7a" href="#p-ZzQufg6k7a" tabindex="-1" role="presentation"></a>También podemos tener funciones que modifican otras funciones:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-x5IWKK9iS2" href="#c-x5IWKK9iS2" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">ruidosa</span>(<span class="tok-definition">f</span>) {
<span class="tok-keyword">return</span> (...<span class="tok-definition">args</span>) => {
console.log(<span class="tok-string">"llamando con"</span>, args);
<span class="tok-keyword">let</span> <span class="tok-definition">resultado</span> = f(...args);
console.log(<span class="tok-string">"llamado con"</span>, args, <span class="tok-string">", devolvió"</span>, resultado);
<span class="tok-keyword">return</span> resultado;
};
}
ruidosa(Math.min)(<span class="tok-number">3</span>, <span class="tok-number">2</span>, <span class="tok-number">1</span>);
<span class="tok-comment">// → llamando con [3, 2, 1]</span>
<span class="tok-comment">// → llamado con [3, 2, 1] , devolvió 1</span></pre>
<p><a class="p_ident" id="p-XsceS57yr8" href="#p-XsceS57yr8" tabindex="-1" role="presentation"></a>Incluso podemos escribir funciones que proveen nuevos tipos de flujo de control:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-nuRXUnBX/r" href="#c-nuRXUnBX/r" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">aMenosQue</span>(<span class="tok-definition">prueba</span>, <span class="tok-definition">entonces</span>) {
<span class="tok-keyword">if</span> (!prueba) entonces();
}
repetir(<span class="tok-number">3</span>, <span class="tok-definition">n</span> => {
aMenosQue(n % <span class="tok-number">2</span> == <span class="tok-number">1</span>, () => {
console.log(n, <span class="tok-string">"es par"</span>);
});
});
<span class="tok-comment">// → 0 es par</span>
<span class="tok-comment">// → 2 es par</span></pre>
<p><a class="p_ident" id="p-Zjga2rFofq" href="#p-Zjga2rFofq" tabindex="-1" role="presentation"></a>Existe un método ya incorporado en los arrays, <code>forEach</code>, que proporciona algo similar a un bucle <code>for</code>/<code>of</code> como una función de orden superior:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-v9jL6NafRj" href="#c-v9jL6NafRj" tabindex="-1" role="presentation"></a>[<span class="tok-string">"A"</span>, <span class="tok-string">"B"</span>].forEach(<span class="tok-definition">l</span> => console.log(l));
<span class="tok-comment">// → A</span>
<span class="tok-comment">// → B</span></pre>
<h2 id="scripts"><a class="h_ident" id="h-OdHcd1haxZ" href="#h-OdHcd1haxZ" tabindex="-1" role="presentation"></a>Conjunto de datos de sistemas de escritura</h2>
<p><a class="p_ident" id="p-nLcvnbV7JQ" href="#p-nLcvnbV7JQ" tabindex="-1" role="presentation"></a>Un área donde las funciones de orden superior destacan es en el procesamiento de datos. Para procesar datos, vamos a necesitar algunos datos de ejemplo. Este capítulo utilizará un conjunto de datos sobre sistemas de escritura tales como el latín, cirílico o árabe.</p>
<p><a class="p_ident" id="p-QWRc3oEeB3" href="#p-QWRc3oEeB3" tabindex="-1" role="presentation"></a>¿Recuerdas Unicode del <a href="01_values.html#unicode">Capítulo 1</a>, el sistema que asigna un número a cada carácter en lenguaje escrito? La mayoría de estos caracteres están asociados con un sistema de escritura concreto. El estándar contiene 140 sistemas diferentes, de los cuales 81 aún se utilizan hoy en día y 59 son históricos.</p>
<p><a class="p_ident" id="p-Y+uBE+NY1X" href="#p-Y+uBE+NY1X" tabindex="-1" role="presentation"></a>Aunque solo puedo leer con fluidez caracteres latinos, aprecio el hecho de que las personas estén escribiendo textos en al menos otros 80 sistemas de escritura, muchos de los cuales ni siquiera reconocería. Por ejemplo, aquí tienes una muestra de escritura Tamil:</p><figure><img src="img/tamil.png" alt="Una línea de verso en escritura Tamil. Los caracteres son relativamente simples y separados ordenadamente, pero completamente diferentes de los caracteres latinos."></figure>
<p><a class="p_ident" id="p-ELQZberUtO" href="#p-ELQZberUtO" tabindex="-1" role="presentation"></a>El conjunto de datos de ejemplo contiene información sobre los 140 sistemas de escritura definidos en Unicode. Está disponible en el <a href="https://eloquentjavascript.net/code#5">sandbox de código</a> para este capítulo como la asociación de nombre <code>SCRIPTS</code>. La variable contiene un array de objetos, cada uno describiendo un sistema de escritura:</p>
<pre class="snippet" data-language="json" ><a class="c_ident" id="c-YkfuyBG2fl" href="#c-YkfuyBG2fl" tabindex="-1" role="presentation"></a>{
<span class="tok-definition">name</span>: <span class="tok-string">"Coptic"</span>,
<span class="tok-definition">ranges</span>: [[<span class="tok-number">994</span>, <span class="tok-number">1008</span>], [<span class="tok-number">11392</span>, <span class="tok-number">11508</span>], [<span class="tok-number">11513</span>, <span class="tok-number">11520</span>]],
<span class="tok-definition">direction</span>: <span class="tok-string">"ltr"</span>,
<span class="tok-definition">year</span>: -<span class="tok-number">200</span>,
<span class="tok-definition">living</span>: false,
<span class="tok-definition">link</span>: <span class="tok-string">"https://en.wikipedia.org/wiki/Coptic_alphabet"</span>
}</pre>
<p><a class="p_ident" id="p-ua08rL6DL0" href="#p-ua08rL6DL0" tabindex="-1" role="presentation"></a>Tal objeto nos informa sobre el nombre del sistema de lenguaje, los rangos Unicode asignados a él, la dirección en la que se escribe, el momento de origen (aproximado), si todavía se utiliza, y un enlace a más información. La dirección puede ser <code>"ltr"</code> para izquierda a derecha, <code>"rtl"</code> para derecha a izquierda (como se escribe el texto en árabe y hebreo) o <code>"ttb"</code> para arriba hacia abajo (como en la escritura mongola).</p>
<p><a class="p_ident" id="p-DoOVtob5DG" href="#p-DoOVtob5DG" tabindex="-1" role="presentation"></a>La propiedad <code>ranges</code> contiene un array de rangos de caracteres Unicode, cada uno de los cuales es un array de dos elementos que contiene un límite inferior y un límite superior. Todos los códigos de caracteres dentro de estos rangos se asignan al sistema de escritura en cuestión. El límite inferior 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).</p>
<h2><a class="h_ident" id="h-MtcnrMCox6" href="#h-MtcnrMCox6" tabindex="-1" role="presentation"></a>Filtrado de arrays</h2>
<p><a class="p_ident" id="p-lJs1/KLSN1" href="#p-lJs1/KLSN1" tabindex="-1" role="presentation"></a>Si queremos encontrar en el conjunto de datos qué sistemas de escritura todavía se utilizan, la siguiente función puede ser útil. Deja fuera los elementos de un array que no cumplen una cierta comprobación.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-lyHwMa/zZ6" href="#c-lyHwMa/zZ6" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">filtrar</span>(<span class="tok-definition">array</span>, <span class="tok-definition">comprobación</span>) {
<span class="tok-keyword">let</span> <span class="tok-definition">pasada</span> = [];
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">elemento</span> <span class="tok-keyword">of</span> array) {
<span class="tok-keyword">if</span> (comprobación(elemento)) {
pasada.push(elemento);
}
}
<span class="tok-keyword">return</span> pasada;
}
console.log(filtrar(SCRIPTS, <span class="tok-definition">sistema</span> => sistema.living));
<span class="tok-comment">// → [{name: "Adlam", …}, …]</span></pre>
<p><a class="p_ident" id="p-vqNV3Gw5Wz" href="#p-vqNV3Gw5Wz" tabindex="-1" role="presentation"></a>La función utiliza el argumento llamado <code>comprobación</code>, un valor de función, para llenar un “hueco” en el procedimiento de filtrado: el proceso de decidir qué elementos recopilar.</p>
<p><a class="p_ident" id="p-rDTRpRs2b7" href="#p-rDTRpRs2b7" tabindex="-1" role="presentation"></a>Observa cómo la función <code>filtrar</code>, en lugar de eliminar elementos de la matriz existente, construye una nueva matriz con solo los elementos que pasan la prueba. Esta función es <em>pura</em>. No modifica la matriz que se le pasa.</p>
<p><a class="p_ident" id="p-mflQRYwRQo" href="#p-mflQRYwRQo" tabindex="-1" role="presentation"></a>Al igual que con <code>forEach</code>, hay un método estándar para <code>filtrar</code> en los arrays, el método <code>filter</code>. En el ejemplo se define la función solo para mostrar qué hace internamente. De ahora en adelante, lo usaremos de esta manera en su lugar:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-x8e0PmGGB1" href="#c-x8e0PmGGB1" tabindex="-1" role="presentation"></a>console.log(SCRIPTS.filter(<span class="tok-definition">s</span> => s.direction == <span class="tok-string">"ttb"</span>));
<span class="tok-comment">// → [{name: "Mongolian", …}, …]</span></pre>
<h2 id="map"><a class="h_ident" id="h-S3rmMrmWka" href="#h-S3rmMrmWka" tabindex="-1" role="presentation"></a>Transformación con map</h2>
<p><a class="p_ident" id="p-ydEMpih2aw" href="#p-ydEMpih2aw" tabindex="-1" role="presentation"></a>Digamos que tenemos un array de objetos que representan sistemas de escritura, producido al filtrar el array <code>SCRIPTS</code> de alguna manera. En su lugar, queremos un array de nombres, que es más fácil de inspeccionar.</p>
<p><a class="p_ident" id="p-eEu8IlZwFm" href="#p-eEu8IlZwFm" tabindex="-1" role="presentation"></a>El método <code>map</code> transforma un array aplicando una función a todos sus elementos y construyendo un nuevo array a partir de los valores devueltos. El nuevo array tendrá la misma longitud que el de entrada, pero su contenido habrá sido <em>mapeado</em> a una nueva forma por la función:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-HNoE2R2koX" href="#c-HNoE2R2koX" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">mapear</span>(<span class="tok-definition">array</span>, <span class="tok-definition">transformación</span>) {
<span class="tok-keyword">let</span> <span class="tok-definition">mapeados</span> = [];
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">elemento</span> <span class="tok-keyword">of</span> array) {
mapeados.push(transformación(elemento));
}
<span class="tok-keyword">return</span> mapeados;
}
<span class="tok-keyword">let</span> <span class="tok-definition">rtlScripts</span> = SCRIPTS.filter(<span class="tok-definition">s</span> => s.direction == <span class="tok-string">"rtl"</span>);
console.log(mapear(rtlScripts, <span class="tok-definition">s</span> => s.name));
<span class="tok-comment">// → ["Adlam", "Arabic", "Imperial Aramaic", …]</span></pre>
<p><a class="p_ident" id="p-0CRJOsl5nV" href="#p-0CRJOsl5nV" tabindex="-1" role="presentation"></a>Al igual que <code>forEach</code> y <code>filter</code>, hay un método estándar para <code>mapear</code> en los arrays, el método <code>map</code>.</p>
<h2><a class="h_ident" id="h-ddK/7bb9/9" href="#h-ddK/7bb9/9" tabindex="-1" role="presentation"></a>Resumiendo con reduce</h2>
<p><a class="p_ident" id="p-0sSdjIXjkE" href="#p-0sSdjIXjkE" tabindex="-1" role="presentation"></a>Otra cosa común que hacer con arrays es calcular un único valor a partir de ellos. Nuestro ejemplo de siempre, sumar una colección de números, es una ejemplo de esto. Otro ejemplo es encontrar el sistema de escritura con más caracteres.</p>
<p><a class="p_ident" id="p-EiDL8H6ZNm" href="#p-EiDL8H6ZNm" tabindex="-1" role="presentation"></a>La operación de orden superior que representa esta idea se llama <em>reduce</em> (a veces también llamada <em>fold</em>). Construye un valor tomando repetidamente un único elemento del array y combinándolo con el valor actual. Al sumar números empezarías con el número cero y añadirías cada elemento a la suma.</p>
<p><a class="p_ident" id="p-ON8Z05D/At" href="#p-ON8Z05D/At" tabindex="-1" role="presentation"></a>Los parámetros de <code>reduce</code> son, además del array, una función de combinación y un valor inicial. Esta función es un poco menos directa que <code>filter</code> y <code>map</code>, así que observa detenidamente:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-MPEcux0ztP" href="#c-MPEcux0ztP" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">reducir</span>(<span class="tok-definition">array</span>, <span class="tok-definition">combinación</span>, <span class="tok-definition">principio</span>) {
<span class="tok-keyword">let</span> <span class="tok-definition">actual</span> = inicio;
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">elemento</span> <span class="tok-keyword">of</span> array) {
actual = combinación(actual, elemento);
}
<span class="tok-keyword">return</span> actual;
}
console.log(reducir([<span class="tok-number">1</span>, <span class="tok-number">2</span>, <span class="tok-number">3</span>, <span class="tok-number">4</span>], (<span class="tok-definition">a</span>, <span class="tok-definition">b</span>) => a + b, <span class="tok-number">0</span>));
<span class="tok-comment">// → 10</span></pre>
<p><a class="p_ident" id="p-tSi+CnrI8e" href="#p-tSi+CnrI8e" tabindex="-1" role="presentation"></a>El método estándar de arrays, <code>reduce</code> —que por supuesto corresponde a esta función— tiene una ventaja adicional. Si tu array contiene al menos un elemento, puedes omitir el argumento <code>start</code>. El método tomará el primer elemento del array como su valor inicial y comenzará a reducir en el segundo elemento.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Dxn7muuMkk" href="#c-Dxn7muuMkk" tabindex="-1" role="presentation"></a>console.log([<span class="tok-number">1</span>, <span class="tok-number">2</span>, <span class="tok-number">3</span>, <span class="tok-number">4</span>].reduce((<span class="tok-definition">a</span>, <span class="tok-definition">b</span>) => a + b));
<span class="tok-comment">// → 10</span></pre>
<p><a class="p_ident" id="p-aq58LUBIxu" href="#p-aq58LUBIxu" tabindex="-1" role="presentation"></a>Para usar <code>reduce</code> (dos veces) y encontrar el sistema de escritura con más caracteres, podemos escribir algo así:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-9muvALBVGr" href="#c-9muvALBVGr" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">contarCaracteres</span>(<span class="tok-definition">sistema</span>) {
<span class="tok-keyword">return</span> sistema.ranges.reduce((<span class="tok-definition">contador</span>, [<span class="tok-definition">desde</span>, <span class="tok-definition">hasta</span>]) => {
<span class="tok-keyword">return</span> contador + (hasta - desde);
}, <span class="tok-number">0</span>);
}
console.log(SCRIPTS.reduce((<span class="tok-definition">a</span>, <span class="tok-definition">b</span>) => {
<span class="tok-keyword">return</span> contarCaracteres(a) < contarCaracteres(b) ? b : a;
}));
<span class="tok-comment">// → {name: "Han", …}</span></pre>
<p><a class="p_ident" id="p-h4Jk417+fU" href="#p-h4Jk417+fU" tabindex="-1" role="presentation"></a>La función <code>contarCaracteres</code> reduce los rangos asignados a un sistema de escritura sumando sus tamaños. Observa el uso de la desestructuración en la lista de parámetros de la función reductora. La segunda llamada a <code>reduce</code> luego utiliza esto para encontrar el sistema de escritura más grande comparando repetidamente dos sistemas y devolviendo el más grande.</p>
<p><a class="p_ident" id="p-lRRvnFoqP7" href="#p-lRRvnFoqP7" tabindex="-1" role="presentation"></a>El sistema de escritura Han (es decir, el sistema de escritura chino actual) tiene más de 89000 caracteres asignados en el estándar Unicode, convirtiéndolo en el sistema de escritura más grande del conjunto de datos. El sistema Han es un sistema a veces utilizado para texto en chino, japonés y coreano. Estos 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 <em>unificación Han</em> y aún hay gente que no está muy contenta con ella.</p>
<h2><a class="h_ident" id="h-bzPoX82c4F" href="#h-bzPoX82c4F" tabindex="-1" role="presentation"></a>Composabilidad</h2>
<p><a class="p_ident" id="p-077XGN/TLg" href="#p-077XGN/TLg" tabindex="-1" role="presentation"></a>Considera cómo hubiéramos escrito el ejemplo anterior (encontrar el sistema más grande) sin funciones de orden superior. El código no es tan inferior al anterior.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-vfhIpzue2u" href="#c-vfhIpzue2u" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">másGrande</span> = <span class="tok-keyword">null</span>;
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">sistema</span> <span class="tok-keyword">of</span> SCRIPTS) {
<span class="tok-keyword">if</span> (másGrande == <span class="tok-keyword">null</span> ||
contarCaracteres(másGrande) < contarCaracteres(sistema)) {
másGrande = sistema;
}
}
console.log(másGrande);
<span class="tok-comment">// → {name: "Han", …}</span></pre>
<p><a class="p_ident" id="p-EtVmv7i/iI" href="#p-EtVmv7i/iI" tabindex="-1" role="presentation"></a>Hay algunas variables más y el programa tiene cuatro líneas más, pero sigue siendo muy legible.</p>
<p id="average_function"><a class="p_ident" id="p-/CzAZCG1Ww" href="#p-/CzAZCG1Ww" tabindex="-1" role="presentation"></a>Las abstracciones proporcionadas por estas funciones brillan realmente cuando necesitas <em>componer</em> operaciones. Como ejemplo, escribamos un código que encuentre el año promedio de origen para sistemas vivos y muertos en el conjunto de datos:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-DlxRx+dU7R" href="#c-DlxRx+dU7R" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">promedio</span>(<span class="tok-definition">array</span>) {
<span class="tok-keyword">return</span> array.reduce((<span class="tok-definition">a</span>, <span class="tok-definition">b</span>) => a + b) / array.length;
}
console.log(Math.round(promedio(
SCRIPTS.filter(<span class="tok-definition">s</span> => s.living).map(<span class="tok-definition">s</span> => s.year))));
<span class="tok-comment">// → 1165</span>
console.log(Math.round(promedio(
SCRIPTS.filter(<span class="tok-definition">s</span> => !s.living).map(<span class="tok-definition">s</span> => s.year))));
<span class="tok-comment">// → 204</span></pre>
<p><a class="p_ident" id="p-HYrvIekBW0" href="#p-HYrvIekBW0" tabindex="-1" role="presentation"></a>Como puedes ver, los sistemas de escritura muertos en Unicode son, en promedio, más antiguos que los vivos. Esta no es una estadística muy significativa o sorprendente. Pero espero que estés de acuerdo en que el código utilizado para calcularlo no es difícil de leer. Puedes verlo como una cadena de procesos (pipeline): empezamos con todos los sistemas, filtramos los vivos (o muertos), tomamos los años de esos sistemas, calculamos el promedio y redondeamos el resultado.</p>
<p><a class="p_ident" id="p-KGXXlw/EvR" href="#p-KGXXlw/EvR" tabindex="-1" role="presentation"></a>Definitivamente también podrías escribir este cálculo como un único bucle grande:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-rI3MU9TnSK" href="#c-rI3MU9TnSK" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">total</span> = <span class="tok-number">0</span>, <span class="tok-definition">contador</span> = <span class="tok-number">0</span>;
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">sistema</span> <span class="tok-keyword">of</span> SCRIPTS) {
<span class="tok-keyword">if</span> (sistema.living) {
total += sistema.year;
contador += <span class="tok-number">1</span>;
}
}
console.log(Math.round(total / contador));
<span class="tok-comment">// → 1165</span></pre>
<p><a class="p_ident" id="p-z1zZ1R3oEN" href="#p-z1zZ1R3oEN" tabindex="-1" role="presentation"></a>Sin embargo, es más difícil ver qué se estaba calculando y cómo. Y como los resultados intermedios no se representan como valores coherentes, sería mucho más trabajo extraer algo como el <code>promedio</code> en una función separada.</p>
<p><a class="p_ident" id="p-PY6JQfUdBq" href="#p-PY6JQfUdBq" tabindex="-1" role="presentation"></a>En términos de lo que realmente está haciendo la computadora, estos dos enfoques también son bastante distintos. El primero construirá nuevos arrays al ejecutar <code>filter</code> y <code>map</code>, mientras que el segundo calcula solo algunos números, haciendo menos trabajo. Por lo general, puedes permitirte el enfoque legible, pero si estás procesando arrays enormes y haciéndolo muchas veces, un estilo menos abstracto podría valer la pena a cambio de velocidad adicional.</p>
<h2><a class="h_ident" id="h-XPdB1c6gdD" href="#h-XPdB1c6gdD" tabindex="-1" role="presentation"></a>Cadenas y códigos de caracteres</h2>
<p><a class="p_ident" id="p-MXjFCWbW9t" href="#p-MXjFCWbW9t" tabindex="-1" role="presentation"></a>Un uso interesante de este conjunto de datos sería averiguar qué sistema de escritura está utilizando un fragmento de texto. Veamos un programa que hace esto.</p>
<p><a class="p_ident" id="p-GatVMstQav" href="#p-GatVMstQav" tabindex="-1" role="presentation"></a>Recuerda que cada sistema de escritura tiene asociado un array de intervalos de códigos de caracteres. Dado un código de carácter, podríamos usar una función como esta para encontrar el sistema correspondiente (si lo hay):</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-R9pXaLnTza" href="#c-R9pXaLnTza" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">sistemaCaracteres</span>(<span class="tok-definition">código</span>) {
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">sistema</span> <span class="tok-keyword">of</span> SCRIPTS) {
<span class="tok-keyword">if</span> (sistema.ranges.some(([<span class="tok-definition">desde</span>, <span class="tok-definition">hasta</span>]) => {
<span class="tok-keyword">return</span> código >= desde && código < hasta;
})) {
<span class="tok-keyword">return</span> sistema;
}
}
<span class="tok-keyword">return</span> <span class="tok-keyword">null</span>;
}
console.log(sistemaCaracteres(<span class="tok-number">121</span>));
<span class="tok-comment">// → {name: "Latin", …}</span></pre>
<p><a class="p_ident" id="p-hJDL1Q3G7R" href="#p-hJDL1Q3G7R" tabindex="-1" role="presentation"></a>El método <code>some</code> es otra función de orden superior. Toma una función de comprobación y te dice si esa función devuelve true para alguno de los elementos en el array.</p>
<p id="code_units"><a class="p_ident" id="p-xwqTjYNDg/" href="#p-xwqTjYNDg/" tabindex="-1" role="presentation"></a>Pero, ¿cómo obtenemos los códigos de caracteres en una cadena?</p>
<p><a class="p_ident" id="p-QaWyxrrWcl" href="#p-QaWyxrrWcl" tabindex="-1" role="presentation"></a>En el <a href="01_values.html">Capítulo 1</a> mencioné que las cadenas de JavaScript están codificadas como una secuencia de números de 16 bits. Estos se llaman <em>unidades de código</em>. Al principio, se suponía que un código de carácter Unicode cabía dentro de tal unidad (lo que te da algo más de 65000 caracteres). Cuando quedó claro que eso no iba a ser suficiente, mucha gente se mostró reacia a la necesidad de usar más memoria por carácter. Para abordar estas preocupaciones, se inventó UTF-16, el formato que usan las cadenas de JavaScript. Describe la mayoría de los caracteres comunes usando una única unidad de código de 16 bits, pero usa un par de dos unidades de dicho tipo para otros.</p>
<p><a class="p_ident" id="p-giEN4jUd0U" href="#p-giEN4jUd0U" tabindex="-1" role="presentation"></a>UTF-16 generalmente se considera una mala idea hoy en día. Parece casi diseñado intencionalmente para provocar errores. Es fácil escribir programas que asuman que las unidades de código y los caracteres son lo mismo. Y si tu lenguaje no utiliza caracteres de dos unidades, eso parecerá funcionar perfectamente. Pero tan pronto como alguien intente usar dicho programa con algunos caracteres menos comunes como los chinos, fallará. Por suerte, con la llegada de los emoji, todo el mundo ha comenzado a usar caracteres de dos unidades, y tratar con tales problemas se está haciendo más llevadero.</p>
<p><a class="p_ident" id="p-cIimdGgzlf" href="#p-cIimdGgzlf" tabindex="-1" role="presentation"></a>Lamentablemente, las operaciones obvias en las cadenas de JavaScript, como obtener su longitud a través de la propiedad <code>length</code> y acceder a su contenido usando corchetes cuadrados, tratan solo con unidades de código.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-y07dTF1oUr" href="#c-y07dTF1oUr" tabindex="-1" role="presentation"></a><span class="tok-comment">// Dos caracteres emoji, caballo y zapato</span>
<span class="tok-keyword">let</span> <span class="tok-definition">caballoZapato</span> = <span class="tok-string">"🐴👟"</span>;
console.log(caballoZapato.length);
<span class="tok-comment">// → 4</span>
console.log(caballoZapato[<span class="tok-number">0</span>]);
<span class="tok-comment">// → (Mitad de carácter inválida)</span>
console.log(caballoZapato.charCodeAt(<span class="tok-number">0</span>));
<span class="tok-comment">// → 55357 (Código de la mitad de caracter)</span>
console.log(caballoZapato.codePointAt(<span class="tok-number">0</span>));
<span class="tok-comment">// → 128052 (Código real para el emoji de caballo)</span></pre>
<p><a class="p_ident" id="p-BS9MJRj/qW" href="#p-BS9MJRj/qW" tabindex="-1" role="presentation"></a>El método <code>charCodeAt</code> de JavaScript te da una unidad de código, no un código de carácter completo. El método <code>codePointAt</code>, añadido más tarde, sí da un carácter Unicode completo, por lo que podríamos usarlo para obtener caracteres de una cadena. Pero el argumento pasado a <code>codePointAt</code> sigue siendo un índice en la secuencia de unidades de código. Para recorrer todos los caracteres en una cadena, aún necesitaríamos abordar la cuestión de si un carácter ocupa una o dos unidades de código.</p>
<p><a class="p_ident" id="p-+k9Io/S3re" href="#p-+k9Io/S3re" tabindex="-1" role="presentation"></a>En el <a href="datos#bucle_for_of">capítulo anterior</a>, mencioné que un bucle <code>for</code>/<code>of</code> también se puede usar en cadenas. Al igual que <code>codePointAt</code>, este tipo de bucle se introdujo en un momento en que la gente era muy consciente de los problemas con UTF-16. Cuando lo usas para recorrer una cadena, te proporciona caracteres reales, no unidades de código:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-5IPW3Z/swv" href="#c-5IPW3Z/swv" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">rosaDragón</span> = <span class="tok-string">"🌹🐉"</span>;
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">carácter</span> <span class="tok-keyword">of</span> rosaDragón) {
console.log(caracter);
}
<span class="tok-comment">// → 🌹</span>
<span class="tok-comment">// → 🐉</span></pre>
<p><a class="p_ident" id="p-crnlwccTvb" href="#p-crnlwccTvb" tabindex="-1" role="presentation"></a>Si tienes un carácter (que será una cadena de una o dos unidades de código), puedes usar <code>codePointAt(0)</code> para obtener su código.</p>
<h2><a class="h_ident" id="h-6v14c/pbJh" href="#h-6v14c/pbJh" tabindex="-1" role="presentation"></a>Reconociendo texto</h2>
<p><a class="p_ident" id="p-US1jun7br4" href="#p-US1jun7br4" tabindex="-1" role="presentation"></a>Tenemos una función <code>sistemaCaracteres</code> y una forma de recorrer correctamente los caracteres. El próximo paso es contar los caracteres que pertenecen a cada sistema de escritura. La siguiente abstracción de recuento será útil para eso:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-TEPk63dZNo" href="#c-TEPk63dZNo" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">contarPor</span>(<span class="tok-definition">items</span>, <span class="tok-definition">nombreGrupo</span>) {
<span class="tok-keyword">let</span> <span class="tok-definition">recuentos</span> = [];
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">item</span> <span class="tok-keyword">of</span> items) {
<span class="tok-keyword">let</span> <span class="tok-definition">nombre</span> = nombreGrupo(item);
<span class="tok-keyword">let</span> <span class="tok-definition">conocido</span> = recuentos.find(<span class="tok-definition">c</span> => c.nombre == nombre);
<span class="tok-keyword">if</span> (!conocido) {
recuentos.push({<span class="tok-definition">nombre</span>, <span class="tok-definition">recuento</span>: <span class="tok-number">1</span>});
} <span class="tok-keyword">else</span> {
conocido.recuento++;
}
}
<span class="tok-keyword">return</span> recuentos;
}
console.log(contarPor([<span class="tok-number">1</span>, <span class="tok-number">2</span>, <span class="tok-number">3</span>, <span class="tok-number">4</span>, <span class="tok-number">5</span>], <span class="tok-definition">n</span> => n > <span class="tok-number">2</span>));
<span class="tok-comment">// → [{nombre: false, recuento: 2}, {nombre: true, recuento: 3}]</span></pre>
<p><a class="p_ident" id="p-BkGEda/6+p" href="#p-BkGEda/6+p" tabindex="-1" role="presentation"></a>La función <code>contarPor</code> espera una colección (cualquier cosa por la que podamos iterar con <code>for</code>/<code>of</code>) y una función que calcule un nombre de grupo para un elemento dado. Devuelve una matriz de objetos, cada uno de los cuales nombra un grupo y te dice el número de elementos que se encontraron en ese grupo.</p>
<p><a class="p_ident" id="p-3D6sEDfZSV" href="#p-3D6sEDfZSV" tabindex="-1" role="presentation"></a>Utiliza otro método de array, <code>find</code>, que recorre los elementos en el array y devuelve el primero para el cual una función devuelve true. Devuelve <code>undefined</code> cuando no se encuentra dicho elemento.</p>
<p><a class="p_ident" id="p-cOB6KMMgRg" href="#p-cOB6KMMgRg" tabindex="-1" role="presentation"></a>Usando <code>contarPor</code>, podemos escribir la función que nos dice qué sistemas de escritura se utilizan en un fragmento de texto:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-IBybQjGQ9Z" href="#c-IBybQjGQ9Z" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">sistemasTexto</span>(<span class="tok-definition">texto</span>) {
<span class="tok-keyword">let</span> <span class="tok-definition">sistemas</span> = contarPor(texto, <span class="tok-definition">carácter</span> => {
<span class="tok-keyword">let</span> <span class="tok-definition">sistema</span> = sistemaCaracteres(carácter.codePointAt(<span class="tok-number">0</span>));
<span class="tok-keyword">return</span> sistema ? sistema.name : <span class="tok-string">"ninguno"</span>;
}).filter(({nombre}) => nombre != <span class="tok-string">"ninguno"</span>);
<span class="tok-keyword">let</span> <span class="tok-definition">total</span> = sistemas.reduce((<span class="tok-definition">n</span>, {recuento}) => n + recuento, <span class="tok-number">0</span>);
<span class="tok-keyword">if</span> (total == <span class="tok-number">0</span>) <span class="tok-keyword">return</span> <span class="tok-string">"No se encontraron sistemas"</span>;
<span class="tok-keyword">return</span> sistemas.map(({nombre, recuento}) => {
<span class="tok-keyword">return</span> <span class="tok-string2">`</span>${Math.round(recuento * <span class="tok-number">100</span> / total)}<span class="tok-string2">% </span>${nombre}<span class="tok-string2">`</span>;
}).join(<span class="tok-string">", "</span>);
}
console.log(sistemasTexto(<span class="tok-string">'英国的狗说"woof", 俄罗斯的狗说"тяв"'</span>));
<span class="tok-comment">// → 61% Han, 22% Latin, 17% Cyrillic</span></pre>
<p><a class="p_ident" id="p-6ZNN+7KNVP" href="#p-6ZNN+7KNVP" tabindex="-1" role="presentation"></a>La función primero recoge los nombres de los sistemas de escritura de los caracteres en el texto usando <code>sistemaCaracteres</code> para asignarles un nombre y recurriendo a la cadena <code>"ninguno"</code> para los caracteres que no forman parte de ningún sistema. La llamada a <code>filter</code> elimina la entrada correspondiente a <code>"ninguno"</code> del array resultante, ya que no nos interesan esos caracteres.</p>
<p><a class="p_ident" id="p-z0FN1vM2Io" href="#p-z0FN1vM2Io" tabindex="-1" role="presentation"></a>Para poder calcular porcentajes, primero necesitamos el número total de caracteres que pertenecen a un sistema dado, lo cual podemos calcular con <code>reduce</code>. Si no se encuentran dichos caracteres, la función devuelve una cadena específica. De lo contrario, transforma las entradas de conteo en cadenas legibles con <code>map</code> y luego las combina con <code>join</code>.</p>
<h2><a class="h_ident" id="h-NUFOUyK+lw" href="#h-NUFOUyK+lw" tabindex="-1" role="presentation"></a>Resumen</h2>
<p><a class="p_ident" id="p-ua0YBHiMTm" href="#p-ua0YBHiMTm" tabindex="-1" role="presentation"></a>Poder pasar valores de funciones a otras funciones es un aspecto muy útil de JavaScript. Nos permite escribir funciones que modelan cálculos con “huecos a rellenar” en ellas. El código que llama a estas funciones puede llenar los huecos proporcionando valores de funciones.</p>
<p><a class="p_ident" id="p-AxW84COxvD" href="#p-AxW84COxvD" tabindex="-1" role="presentation"></a>Los arrays proporcionan diversos métodos de orden superior muy útiles. Puedes usar <code>forEach</code> para recorrer los elementos de un array. El método <code>filter</code> devuelve un nuevo array que contiene solo los elementos que pasan la función de predicado. Transformar un array poniendo cada elemento en una función se hace con <code>map</code>. Puedes usar <code>reduce</code> para combinar todos los elementos de un array en un único valor. El método <code>some</code> comprueba si algún elemento satisface una función de predicado dada, mientras que <code>find</code> encuentra el primer elemento que satisface un predicado.</p>
<h2><a class="h_ident" id="h-tkm7ntLto1" href="#h-tkm7ntLto1" tabindex="-1" role="presentation"></a>Ejercicios</h2>
<h3><a class="i_ident" id="i-NYYN2FxNVG" href="#i-NYYN2FxNVG" tabindex="-1" role="presentation"></a>Aplanamiento</h3>
<p><a class="p_ident" id="p-2JTbAIwElU" href="#p-2JTbAIwElU" tabindex="-1" role="presentation"></a>Utiliza el método <code>reduce</code> en combinación con el método <code>concat</code> para “aplanar” un array de arrays en un único array que contenga todos los elementos de los arrays originales.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-9vAbUnn21O" href="#c-9vAbUnn21O" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">arrays</span> = [[<span class="tok-number">1</span>, <span class="tok-number">2</span>, <span class="tok-number">3</span>], [<span class="tok-number">4</span>, <span class="tok-number">5</span>], [<span class="tok-number">6</span>]];
<span class="tok-comment">// Tu código aquí.</span>
<span class="tok-comment">// → [1, 2, 3, 4, 5, 6]</span></pre>
<h3><a class="i_ident" id="i-cBnCXoHx6D" href="#i-cBnCXoHx6D" tabindex="-1" role="presentation"></a>Tu propio bucle</h3>
<p><a class="p_ident" id="p-eTd6UCJrnn" href="#p-eTd6UCJrnn" tabindex="-1" role="presentation"></a>Escribe una función de orden superior <code>loop</code> que proporcione algo similar a una declaración de bucle <code>for</code>. Debería recibir un valor, una función de comprobación, una función de actualización y una función de cuerpo. En cada iteración, primero debe ejecutar la función de comprobación en el valor actual del bucle y detenerse si devuelve falso. Luego debe llamar a la función de cuerpo, pasándole el valor actual, y finalmente llamar a la función de actualización para crear un nuevo valor y empezar de nuevo desde el principio.</p>
<p><a class="p_ident" id="p-ZjRsh6UWeD" href="#p-ZjRsh6UWeD" tabindex="-1" role="presentation"></a>Al definir la función, puedes usar un bucle normal para hacer el bucle real.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Fv1rc97GEM" href="#c-Fv1rc97GEM" tabindex="-1" role="presentation"></a><span class="tok-comment">// Your code here.</span>
loop(<span class="tok-number">3</span>, <span class="tok-definition">n</span> => n > <span class="tok-number">0</span>, <span class="tok-definition">n</span> => n - <span class="tok-number">1</span>, console.log);
<span class="tok-comment">// → 3</span>
<span class="tok-comment">// → 2</span>
<span class="tok-comment">// → 1</span></pre>
<h3><a class="i_ident" id="i-SmbRSAd5GA" href="#i-SmbRSAd5GA" tabindex="-1" role="presentation"></a>Everything</h3>
<p><a class="p_ident" id="p-4Pg9vLwvKg" href="#p-4Pg9vLwvKg" tabindex="-1" role="presentation"></a>Los arrays también tienen un método <code>every</code> análogo al método <code>some</code>. Este método devuelve <code>true</code> cuando la función dada devuelve <code>true</code> para <em>todo</em> elemento en el array. En cierto modo, <code>some</code> es una versión del operador <code>||</code> que actúa en arrays, y <code>every</code> es como el operador <code>&&</code>.</p>
<p><a class="p_ident" id="p-pg6rAnmtg7" href="#p-pg6rAnmtg7" tabindex="-1" role="presentation"></a>Implementa <code>every</code> como una función que recibe un array y una función de predicado como parámetros. Escribe dos versiones, una usando un bucle y otra usando el método <code>some</code>.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-NludaTWDls" href="#c-NludaTWDls" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">every</span>(<span class="tok-definition">array</span>, <span class="tok-definition">test</span>) {
<span class="tok-comment">// Your code here.</span>
}
console.log(every([<span class="tok-number">1</span>, <span class="tok-number">3</span>, <span class="tok-number">5</span>], <span class="tok-definition">n</span> => n < <span class="tok-number">10</span>));
<span class="tok-comment">// → true</span>
console.log(every([<span class="tok-number">2</span>, <span class="tok-number">4</span>, <span class="tok-number">16</span>], <span class="tok-definition">n</span> => n < <span class="tok-number">10</span>));
<span class="tok-comment">// → false</span>
console.log(every([], <span class="tok-definition">n</span> => n < <span class="tok-number">10</span>));
<span class="tok-comment">// → true</span></pre>
<details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text">
<p><a class="p_ident" id="p-2enp9r8UmW" href="#p-2enp9r8UmW" tabindex="-1" role="presentation"></a>Al igual que el operador <code>&&</code>, el método <code>every</code> puede dejar de evaluar más elementos tan pronto como encuentre uno que no coincida. Por lo tanto, la versión basada en un bucle puede salir del bucle —con <code>break</code> o <code>return</code>— tan pronto como encuentre un elemento para el que la función de predicado devuelva false. Si el bucle se ejecuta hasta el final sin encontrar dicho elemento, sabemos que todos los elementos coincidieron y deberíamos devolver true.</p>
<p><a class="p_ident" id="p-Coa0zWQNO0" href="#p-Coa0zWQNO0" tabindex="-1" role="presentation"></a>Para construir <code>every</code> sobre <code>some</code>, podemos aplicar <em>leyes de De Morgan</em>, que establecen que <code>a && b</code> tiene el mismo valor que <code>!(!a || !b)</code>. Esto se puede generalizar a arrays, donde todos los elementos en el array coinciden si no hay ningún elemento en el array que no coincida.</p>
</div></details>
<h3><a class="i_ident" id="i-kUUDTvgJ6V" href="#i-kUUDTvgJ6V" tabindex="-1" role="presentation"></a>Dirección de escritura dominante</h3>
<p><a class="p_ident" id="p-wNkAaSiwZs" href="#p-wNkAaSiwZs" tabindex="-1" role="presentation"></a>Escribe una función que calcule la dirección de escritura dominante en una cadena de texto. Recuerda que cada objeto de sistema de escritura tiene una propiedad <code>direction</code> que puede ser <code>"ltr"</code> (de izquierda a derecha), <code>"rtl"</code> (de derecha a izquierda) o <code>"ttb"</code> (de arriba a abajo).</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-CNawUvyti3" href="#c-CNawUvyti3" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">dominantDirection</span>(<span class="tok-definition">text</span>) {
<span class="tok-comment">// Your code here.</span>
}
console.log(dominantDirection(<span class="tok-string">"Hello!"</span>));
<span class="tok-comment">// → ltr</span>
console.log(dominantDirection(<span class="tok-string">"Hey, مساء الخير"</span>));
<span class="tok-comment">// → rtl</span></pre>
<details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text">
<p><a class="p_ident" id="p-lyuHx3ewV/" href="#p-lyuHx3ewV/" tabindex="-1" role="presentation"></a>Tu solución podría parecerse mucho a la primera mitad del ejemplo de <code>sistemasTexto</code>. De nuevo, debes contar caracteres según un criterio basado en <code>sistemaCaracteres</code> y luego filtrar la parte del resultado que se refiere a caracteres no interesantes (sin sistema asociado).</p>
<p><a class="p_ident" id="p-FrVu7Vg3MC" href="#p-FrVu7Vg3MC" tabindex="-1" role="presentation"></a>Encontrar la dirección con el recuento de caracteres más alto es algo que se puede hacer con <code>reduce</code>. Si no está claro cómo hacerlo, consulta el ejemplo que vimos antes en el capítulo, donde se usó <code>reduce</code> para encontrar el script con más caracteres.</p>
</div></details><nav><a href="04_data.html" title="previous chapter" aria-label="previous chapter">◂</a> <a href="index.html" title="cover" aria-label="cover">●</a> <a href="06_object.html" title="next chapter" aria-label="next chapter">▸</a> <button class=help title="help" aria-label="help"><strong>?</strong></button>
</nav>
</article>
<script src="ejs.js"></script>