forked from marijnh/Eloquent-JavaScript
-
Notifications
You must be signed in to change notification settings - Fork 41
Expand file tree
/
Copy path04_data.txt
More file actions
1448 lines (1189 loc) · 54.6 KB
/
04_data.txt
File metadata and controls
1448 lines (1189 loc) · 54.6 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
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
:chap_num: 4
:prev_link: 03_functions
:next_link: 05_higher_order
:load_files: ["code/jacques_journal.js", "code/chapter/04_data.js"]
:zip: node/html
= Estructuras de Datos: Objetos y Arreglos =
[chapterquote="true"]
[quote, Charles Babbage, Passages from the Life of a Philosopher (1864)]
____
En dos ocasiones me han preguntado “Disculpe, Sr. Babbage, si introduce los
números incorrectos en la máquina, ¿van a salir las respuestas correctas?”
[...] No puedo terminar de comprender el tipo de confusión de ideas que
podrían provocar esta pregunta.
____
(((Babbage+++,+++ Charles)))(((objeto)))(((estructura de datos)))Números, Booleanos
y cadenas son los ladrillos de los que están hechas las estructuras de ((datos)).
Pero no podrás construir mucha casa de un solo ladrillo. Los _objetos_ nos
permiten agrupar valores, incluyendo otros objetos, permitiéndonos construir
estructuras más complejas.
Los programas que hemos construido hasta ahora han sido seriamente limitados
debido al hecho de que estaban operando únicamente en tipos de datos simples. Este
capítulo agregará a tu caja de herramientas un entendimiento básico de las
estructuras de datos. Al finalizarlo, sabrás lo suficiente para empezar a escribir
algunos programas de utilidad.
El capítulo trabajará a lo largo de un ejemplo de programación más o menos
realista, introduciendo conceptos conforme apliquen al problema en cuestión.
El código de ejemplo muchas veces construirá sobre funciones y variables que
fueron presentadas previamente en el texto.
ifdef::book_target[]
(((sandbox)))El sandbox de programación en línea para el libro
(http://eloquentjavascript.net/code[_eloquentjavascript.net/code_])
proporciona una manera de correr el código en el contexto de un capítulo en
específico. Si decides trabajar en los ejemplos en otro entorno, asegúrate de
descargar primero el código completo de este capítulo desde la página del
sandbox.
endif::book_target[]
== El hombre ardilla ==
(((hombre ardilla, ejemplo)))(((licántropo)))De vez en cuando, comúnmente
entre las ocho y las diez de la noche, ((Jacques)) se transforma en
un pequeño y peludo roedor con una frondosa cola.
Por un lado, Jacques esta bastante contento de no tener la clásica licantropía.
Convertirse en una ardilla suele causar menos problemas que convertirse en un
lobo. En vez de tener que preocuparse por comerse accidentalmente a un vecino
(_eso_ sería penoso), le preocupa el ser deborado por el gato del vecino.
Después de un par de ocasiones donde se despertó, en una
delgada rama en la cima de un roble, desnudo y desorientado, se ha asegurado
de cerrar puertas y ventanas de su cuarto por las noches y poner algunas
bellotas en el suelo para mantenerse ocupado.
image::img/weresquirrel.png[alt="The weresquirrel"]
Eso resuelve los problemas del gato y el roble. Pero Jacques aún sufre de su
enfermedad. Las ocurrencias irregulares de la transformación le hacen
sospechar que pudieran ser detonadas por algo. Por algún tiempo, creyó que
sucedía sólo en los dias que había tocado árboles. Así que dejó de tocar árboles
de manera definitiva e incluso evitó acercarse a ellos. Pero el problema
presistió.
(((diario)))Cambiando a una perspectiva más científica, Jacques intenta
empezar un registro diario de todo lo que hizo ese día y si tuvo una
transformación. Con estos datos espera limitar las condiciones que disparan las
transformaciones.
Lo primero que hace es diseñar una estructura de datos para almacenar esta
información.
== Conjuntos de datos ==
(((estructuras de datos)))Para trabajar con un pedazo de datos digitales, primero
tendremos que encontrar una forma de representarlo en la ((memoria)) de nuestra
máquina. Digamos, como un pequeño ejemplo, que queremos representar una
((colección)) de números: 2, 3, 5, 7 y 11.
(((cadena)))Podríamos ponernos creativos usando cadenas; después de todo, las
cadenas pueden ser de cualquier longitud, así que podemos poner mucha
información en ellas; y usar `"2 3 5 7 11"` como nuestra representación. Pero
esto es incómodo. De alguna forma tendrías que extraer los dígitos y convertirlos
de vuelta a números para acceder a ellos.
(((array, creación)))((([] (array))))Afortunadamente, Javascript proporciona un
tipo de dato específico para almacenar secuencias de valores. Se le llama
_arreglo_ (array) y se escribe como una lista de valores entre ((corchetes)), separados
por comas.
[source,javascript]
----
var listaDeNumeros = [2, 3, 5, 7, 11];
console.log(listaDeNumeros[1]);
// → 3
console.log(listaDeNumeros[1 - 1]);
// → 2
----
((([] (subscript))))(((array,indexado)))La notación para obtener los elementos
dentro de un arreglo también utiliza ((corchetes)). Un par de corchetes
inmediatamente después de una expresión, con otra expresión dentro de los
corchetes, buscará el elemento en la expresión de la izquierda que corresponda
al _((índice))_ dado por la expresión en corchetes.
[[array_indexing]]
El primer índice de un arreglo es cero, no uno. Así que el primer elemento puede
leerse usando `listaDeNumeros[0]`. Si no tienes antecedentes en programación,
acostumbrarte a esta convención puede tomarte algún tiempo. Pero el
((conteo con base cero)) tiene una larga tradición en tecnología y mientras la
convención se siga de manera consistente (que se ha hecho en Javascript),
funciona bien.
[[properties]]
== Propiedades ==
(((objeto Math)))(((función Math.max)))(((propiedad length, para
cadenas)))(((object,propiedad)))(((carácter punto)))Hemos visto algunas expresiones
sospechosas como `myString.length` (para obtener la longitud de una cadena) y
`Math.max` (la función máximo) en ejemplos pasados. Estas son expresiones que
acceden una _((propiedad))_ de algún valor. En el primer caso, accedemos a la
propiedad `length` de el valor en `myString`. En el segundo, accedemos a la
propiedad llamada `max` en el objeto `Math` (que es una colección de valores y
funciones relacionadas con las matemáticas).
(((propiedad)))(((null)))(((undefined)))Casi todos los valores de JavaScript tienen
propiedades. Las excepciones son `null` y `undefined`. Si intentas acceder una
propiedad de alguno de estos valores inválidos recibirás un error.
// test: no
[source,javascript]
----
null.length;
// → TypeError: Cannot read property 'length' of null
----
indexsee:[carácter punto]
((([] (subscript))))(((carácter punto)))(((corchetes)))(((propiedad
calculada)))Las dos maneras comunes de acceder a propiedades
en Javascript son con un punto y con corchetes. Ambas `valor.x` y `valor[x]`
acceden a una ((propiedad)) en ++valor++, pero no necesariamente la misma propiedad.
La diferencia radica en cómo se interpreta `x`. Cuando usamos un punto, la parte
después del punto debe ser un nombre de variable válido y nombra de manera
directa a la propiedad. Cuando usamos corchetes, la expresión dentro de los
corchetes es _evaluada_ para obtener el nombre de la propiedad. Mientras que
`valor.x` busca la propiedad de `valor` llamada “x”, `valor[x]` intenta evaluar
la expresión `x` y usa el resultado como el nombre de la propiedad.
Así que si sabes que la propiedad que te interesa se llama “length”, usas
`valor.length`. Si deseas extraer la propiedad nombrada por el valor almacenado
en la variable `i`, usas `valor[i]`. Y debido a que el nombre de las propiedades
puede ser cualquier cadena, si quieres accesar una propiedad llamada “2” o
“Fulano”, debes utilizar corchetes:`valor[2]` or `valor["Fulano de Tal"]`. Así lo
harías incluso si conoces el nombre preciso de la propiedad de antemano, ya que
ni “2” ni “Fulano de Tal” son nombres válidos de variables y por lo tanto no puede
accederse a traves de la notación con punto.
(((arreglo)))(((propiedad length,para arreglo)))(((arreglo,longitud
de)))Los elementos en un arreglo se almacenan en propiedades. Debido a que los
nombres de estas propiedades son números y usualmente necesitamos obtener su
nombre de una variable, tenemos que usar la sintaxis de corchetes para accesarlos.
La propiedad `length` (longitud) de un arreglo nos dice cuantos elementos contiene. Este
nombre de propiedad es un nombre de variable válido, y conocemos su nombre por
anticipado, así que para encontrar la longitud de un arreglo, comúnmente
escribiremos `arreglo.length` ya que es más fácil de escribir que `arreglo["length"]`.
[[methods]]
== Métodos ==
(((función,como propiedad)))(((método)))(((cadena)))Ambos objetos, las cadenas y
los arreglos contienen, adicionalmente a la propiedad `length` (longitud), varias
propiedades que refieren a valores que son funciones.
[source,javascript]
----
var doh = "Doh";
console.log(typeof doh.toUpperCase);
// → function
console.log(doh.toUpperCase());
// → DOH
----
(((conversión de mayúsculas y minúsculas)))(((método toUpperCase)))(((método
toLowerCase)))Todas las cadenas tienen una propiedad `toUpperCase`. Cuando es invocada,
regresará una copia de la cadena en la que todas las letras han sido convertidas
a mayúsculas. También existe `toLowerCase`. Puedes adivinar que es lo que hace.
(((this)))Es interesante que, aunque la función `toUpperCase` no
recibe ningún argumento, delaguna forma accede a la cadena `"Doh"`, el
valor en que se llamó la función. Esto funciona como se describe en
el link:06_object.html#obj_methods[Capítulo 6].
Las propiedades que contienen funciones genralmente son llamadas _métodos_
del valor al que pertenecen. Así, "++toUpperCase++ es un método de una cadena".
[[array_methods]]
(((colección)))(((array)))(((cadena)))(((método
push)))(((método
pop)))(((método join)))Este ejemplo demuestra
algunos métodos que los objetos arrays tienen:
[source,javascript]
----
var mack = [];
mack.push("Mack");
mack.push("the", "Knife");
console.log(mack);
// → ["Mack", "the", "Knife"]
console.log(mack.join(" "));
// → Mack the Knife
console.log(mack.pop());
// → Knife
console.log(mack);
// → ["Mack", "the"]
----
El método `push` puede ser usado para añadir valores al final de un
array. El método `pop` hace lo contrario: elimina el valor al final
del array y lo regresa. Un array de cadenas puede ser transformado a
una sola cadena con el método `join`. El argumento que se da a `join`
determina el texto que se usa entre los elementos del array.
== Objetos ==
(((diario)))(((ejemplo del hombre ardilla)))(((array)))(((registro)))De
regreso con el hombre ardilla. Un conjunto de entradas diaras de registro
pueden ser representadas como un array. Pero las entradas no consisten
de sólo un número o una cadena, cada entrada necesita guarda una lista de
actividades y un valor Booleano que indica si Jacques se convirtió en
ardilla. Idealmente, nos gustaría agrupar estos valores juntos en un solo
valor y poner estos valores agrupados en un array de entradas del registro.
(((sintaxis)))(((objeto)))(((propiedad)))(((llaves)))((({}
(objeto))))Los valores del tipo _objeto_ son colecciones arbitrarias
de propiedades, y podemos añadir o remover estas propiedades a placer.
Una manera de crear un objeto es usando la notación de llaves.
[source,javascript]
----
var dia1 = {
ardilla: false,
eventos: ["trabajo", "toqué un arbol", "pizza", "correr",
"televisión"]
};
console.log(dia1.ardilla);
// → false
console.log(dia1.lobo);
// → undefined
dia1.lobo = false;
console.log(dia1.lobo);
// → false
----
(((entrecomillado,de propiedades de objetos)))(((carácter dos puntos)))Dentro
de las llaves, podemos dar una lista de propiedades separadas por comas.
Cada propiedad está escritra como un nombre seguido por dos puntos, seguidos
pos una expresión que le asigna el valor a esa propiedad. Los espacios
y saltos de línea no son afectan. Cunado un objeto tiene múltiples líneas,
indentarlas como en el ejemplo previo mejora la legibilidad. Las propiedades
que tengan nombres que no sean nombres de variable válidos o números válidos
tienen que estar entrecomilladas.
[source,javascript]
----
var descripciones = {
trabajo: "Fui al trabajo",
"toqué un arbol": "Toqué el arbol del vecino"
};
----
Esto significa que las ((llaves)) tienen _dos_ significados en JavaScript.
Al principio de una sentencia, empiezan un bloque de sentencia. En cualquier
otra posición, describen un objeto. Afortunadamente, casi nunca es útil
empezar una sentencia con un objeto declarado con llaves, y en programas típicos,
no hay ambigüedad entre esos dos usos.
(((undefined)))Al leer una propiedad que no existe se produce el
valor `undefined`, es lo que pasa la primera vez que intentamos leer
la propiedad `lobo` en el ejemplo previo.
(((propiedad,asignación)))(((mutabilidad)))(((= operador)))Es posible asignar
un valor a una expresión de propiedad con el operador `=`. Esto
reemplazará el valor de la propiedad si ya existía o creará una nueva propiedad
en el objeto si no.
(((tentáculo (analogía))))(((propiedad,modelo de)))Para regresar brevemente a nuestro
modelo de tentáculos para asignaciones de ((variable)), la asignación de
propiedades es parecida. Estas _agarran_ valores, pero otras variables y
propiedades pudieran estar agarrando estos mismos valores. Puedes pensar
que los objetos son pulpos con un número indefinido de tentáculos, cada uno
de los cuales tiene un nombre escrito en él.
image::img/octopus-object.jpg[alt="Representación de un objeto de un artista."]
(((operador delete)))(((propiedad,borrado)))El operador `delete` le corta un
tentáculo a este pulpo. Es un operador unario que, cuando es aplicado a una
expresión de acceso a una propiedad, eliminará la propiedad nombrada del objeto.
Esto no es algo que se haga comúnmente, pero es posible.
[source,javascript]
----
var unObjeto = {izq: 1, der: 2};
console.log(unObjeto.izq);
// → 1
delete unObjeto.izq;
console.log(unObjeto.izq);
// → undefined
console.log("izq" in unObjeto);
// → false
console.log("der" in unObjeto);
// → true
----
(((operador in)))(((propiedad, haciendo pruebas para)))(((objeto)))El operador binario
`in`, cuando se aplica a una cadena y un objeto, devuelve un valor Booleano
que indica si el objeto tiene esa propiedad. La diferencia entre asignarle
`undefined` a una propiedad y borrarla de verdad es que, en el primer caso,
el objeto aún _tiene_ la propiedad (simplemente no tiene un valor muy interesante),
mientras que en el segundo caso, la propiedad no está presente e `in` devolverá
`false`.
(((array)))(((colección)))Los arrays son solamente un tipo de objeto
especializado en almacenar secuencias de cosas. Si evaluas `typeof [1, 2]`,
se prodcue `"object"`. Puedes verlos como pulpos largos y planos con
todos sus tentáculos en una fila limpia, etiquetados con números.
image::img/octopus-array.jpg[alt="Artist's representation of an array"]
(((diario)))(((ejemplo del hombre ardilla)))Así que podemos representar el
diario de Jacques coomo un array de objetos.
[source,javascript]
----
var diario = [
{eventos: ["trabajo", "tocar un arbol", "pizza",
"correr", "televisión"],
ardilla: false},
{eventos: ["trabajo", "helado", "coliflor",
"lasagna", "tocar un arbol", "lavarse los dientes"],
ardilla: false},
{eventos: ["fin de seamana", "bicicleta", "descanso",
"cacahuates", "cerveza"],
ardilla: true},
/* y continúa... */
];
----
== Mutabilidad ==
Nos meteremos en la programación de verdad pronto. Pero primero,
hay una última pieza de teoría que entender.
(((mutabilidad)))(((efecto secundario)))(((número)))(((cadena)))(((Booleano)))(((objeto)))Hemos
visto que los valores objeto pueden ser modificados. Los tipos de
valores de los que platicamos en capítulos anteriores, como números,
cadenas, Booleanos son todos __inmutables__; es imposible cambiar un
valor existente de esos tipos. Puedes combinarlos y derivar nuevos valores de
ellos, pero cuando tomas un valor cadena específico, ese valor siempre
permanecerá igual. El texto que está adentro no puede ser cambiado. Si
tienes una referencia a una cadena que contiene `"cat"`, no se posible
para el código cambiar un caráctes en _esa_ cadena para hacerlo decir `"rat"`.
Con los objeto, por otro lado, el contenido de un valor _puede_ ser
modificado al cambiar sus propiedades.
(((objeto,identidad)))(((identidad)))(((memoria)))Cuando tenemos dos
números,, 120 y 120, podemos considerar que son precisamente el mismo número
independientemente si se refieren o no a los mismos bits físicos. Pero
con los objetos, hay una diferencia entre tener la dos referencias al mismo objeto y tener dos objetos que contienen las mismas propiedades.
Considera el siguiente código:
[source,javascript]
----
var objeto1 = {valor: 10};
var objeto2 = objeto1;
var objeto3 = {valor: 10};
console.log(objeto1 == objeto2);
// → true
console.log(objeto1 == objeto3);
// → false
objeto1.valor = 15;
console.log(objeto2.valor);
// → 15
console.log(objeto3.valor);
// → 10
----
(((tentáculo (analogía))))(((variable,modelo de)))Las variables
`objeto1` y `objeto2` agarran el _mismo_ objeto, ésta es la razón
de que al cambiar `objeto1` también se cambia el valor del `objeto2`.
La variable `objeto3` apunta a un objeto diferente, que inicialmente
contiene las mismas propiedades que el `objeto1` pero vive una vida
separada.
(((operador ==)))(((comparación,de objetos)))(((comparación
profunda)))El operador `==` de JavaScript, cuando compara objetos
regresará `true` sólo si los dos objetos son precisamente el mismo
valor. Comparar diferentes objetos regresará `false`, incluso si
tienen contenidos idénticos. No existe comparación "profunda" construida en
JavaScript, la cual se basa en el contenido de los objetos, pero puedes
escribirla tú mismo (que será uno de los
link:04_data.html#exercise_deep_compare[ejercicios] al final de este capítulo).
== El registro del licántropo ==
(((ejemplo del hombre ardilla)))(((licantropía)))(((función agregarEntrada)))Así,
Jacques inicia su intérprete de JavaScript y configura el entorno
que necesita para llevar su ((diario)).
// include_code
[source,javascript]
----
var diario = [];
function agregarEntrada(eventos, meVolviArdilla) {
diario.push({
eventos: eventos,
ardilla: meVolviArdilla
});
}
----
Y después, cada noche a las 10, o algunas veces la mañana siguiente,
después de haber bajado de la parte superior de su librero, registra
el día.
[source,javascript]
----
agregarEntrada(["trabajo", "toqué un árbol", "pizza", "correr",
"televisión"], false);
agregarEntrada(["trabajo", "helado", "coliflor", "lasagna",
"toqué un árbol", "me lavé los dientes"], false);
agregarEntrada(["fin de semana", "bicicleta", "descanso", "cacachuates",
"cerveza"], true);
----
Una vez que tiene suficientes datos, quiere calcular la ((correlación))
entre su ardillamiento y cada uno de los eventos e, idealmente, aprender
algo útil de esas correlaciones.
(((correlación)))La _Correlación_ es una medida de ((dependencia)) entre
((variable))s (“variables” en el sentido de la estadística, no en el de
JavaScript). Es expresada usualmente como un coeficiente que va del -1 al
1. Una correlación de 0 significa que las variables no están relacionadas, mientras
que una correlación de uno indica que están perfectamente relacionadas: si conoces una,
entonces conoces la otra. El uno negativo también significa que están perfectamente
relacionadas pero que son opuestas, cuando una es verdadera, la otra es falsa.
(((coeficiente phi))) Para variables binarias (Booleanas), el coeficiente _phi_
(_ϕ_) da una buena medida de correlación y es relativamente fácil de calcular.
Para calcular _ϕ_ necesitamos una ((tabla)) _n_ que contine el número de veces
que varias combinaciones de variables fueron observadas. Por ejemplo, podríamos
tomar el evento de comer ((pizza)) y ponerlo en la tabla de la siguiente manera:
image::img/pizza-squirrel.svg[alt="Comer pizza vs convertirse en ardilla.",width="7cm"]
_ϕ_ puede ser calculado usando la siguiente fórmula, en donde _n_ se refiere a la
tabla:
ifdef::html_target[]
++++
<div>
<style scoped="scoped">sub { font-size: 60%; }</style>
<table style="border-collapse: collapse; margin-left: 1em;"><tr>
<td style="vertical-align: middle"><em>ϕ</em> =</td>
<td style="padding-left: .5em">
<div style="border-bottom: 1px solid black; padding: 0 7px;">n<sub>11</sub>n<sub>00</sub> - n<sub>10</sub>n<sub>01</sub></div>
<div style="padding: 0 7px;">√<span style="border-top: 1px solid black; position: relative; top: 2px;">
<span style="position: relative; top: -4px">n<sub>1•</sub>n<sub>0•</sub>n<sub>•1</sub>n<sub>•0</sub></span>
</span></div>
</td>
</tr></table>
</div>
++++
endif::html_target[]
ifdef::tex_target[]
pass:[\begin{equation}\varphi = \frac{n_{11}n_{00}-n_{10}n_{01}}{\sqrt{n_{1\bullet}n_{0\bullet}n_{\bullet1}n_{\bullet0}}}\end{equation}]
endif::tex_target[]
La notación (!html _n_~01~!)(!tex pass:[$n_{01}$]!) indica el
número de medidas en el que la primer vairiable (ardillificación) es falsa (0)
y la segunda variable (pizza) es verdadera (1). En este ejemplo, (!html _n_~01~!)(!tex pass:[$n_{01}$]!) es 9.
El valor (!html _n_~1•~!)(!tex pass:[$n_{1\bullet}$]!) se refiere a la suma
de todas las medidas en donde la primera variable es verdadera, que es 5 en la tabla de
ejemplo. De la misma manera, (!html _n_~•0~!)(!tex pass:[$n_{\bullet0}$]!)
se refiere a la suma de medidas en donde la segunda variable es falsa.
(((correlación)))(((coeficiente phi))) Así que para la tabla de la pizza,
la parte de arriba de la línea de división (el dividendo) sería 1×76 - 4×9 = 40,
y la parte de debajo de la línea (el divisor) sería la raíz cuadrada de
5×85×10×80, o (!html √340000!)(!tex pass:[$\sqrt{340000}$]!). Esto da como resultado
_ϕ_ ≈ 0.069, que es muy pequeño. Comer ((pizza)) no parece tener influencia en las transformaciones.
== Calculando correlaciones ==
(((array,como tabla)))(((anidado, de arrays)))Podemos representar una
((tabla)) de dos por dos en JavaScript con un array de 4 elementos
(`[76, 9, 4, 1]`). Podríamos también usar otras representaciones, como
un array que contenga dos arrays de dos elementos cada uno (`[[76, 9], [4, 1]]`) o como
un objeto con nombre de propiedades como `"11"` y `"01"`, pero el array
plano es simple y hace que las expresiones de acceso a la tabla sean
convenientemente cortas. Interpretaremos los índices del array como
un ((número binario)) de dos ((bit))s, en donde el dígito más a la izquierda
(el más siginificativo) se refiere a la variable de la ardilla y el más a
la derecha (menos significativo) se refiere a la variable del evento. Por ejemplo,
el número binario `10` se refiere al caso en que Jacques se convirtió en
ardilla, pero el evento (digamos, "pizza"), no ocurrió. Esto pasó 4 veces. Y como
el binario `10` es 2 en notación decimal, guardaremos ese número en el índice 2
del array.
(((coeficiente phi)))(((función phi)))Esta es la función que calcula el coeficiente
_ϕ_ del ese array:
// test: clip
// include_code strip_log
[source,javascript]
----
function phi(tabla) {
return (tabla[3] * tabla[0] - tabla[2] * tabla[1]) /
Math.sqrt((tabla[2] + tabla[3]) *
(tabla[0] + tabla[1]) *
(tabla[1] + tabla[3]) *
(tabla[0] + tabla[2]));
}
console.log(phi([76, 9, 4, 1]));
// → 0.068599434
----
(((raíz cuadrada)))(((funcieon Math.sqrt)))Esto es simplemente una
traducción directa de la fómula de la _ϕ_ a JavaScript. `Math.sqrt`
es la función raíz cuadrada, provista por el objeto `Math` en un entorno
de JavaScript estándar. Tenemos que sumar los dos campos de la tabla
para obtener campos como (!html n~1•~!)(!tex pass:[$n_{1\bullet}$]!)
porque la suma de las filas o columnas no están guardadas directamente en
nuestra estructura de datos.
(((set de datos DIARIO)))Jacques mantuvo su diario por tres meses. El
((set de datos)) resultante está disponible en el área de código para
este capítulo(!book (http://eloquentjavascript.net/code#4[_eloquentjavascript.net/code#4_])!),
en donde está guardada la variable `DIARIO`, y como descarga en
http://eloquentjavascript.net/code/jacques_journal.js[file].
(((función tablaPara)))(((función tieneEvento)))Para extraer una ((tabla)) de 2 x 2
po un evento específico de este diario, tenemos que iterar sobre todas las
entradas y contar cuántas veces el evento ocurre en relación con las
transformaciones a ardilla.
// include_code strip_log
[source,javascript]
----
function tieneEvento(evento, entrada) {
return entrada.eventos.indexOf(evento) != -1;
}
function tablaPara(evento, diario) {
var tabla = [0, 0, 0, 0];
for (var i = 0; i < diario.length; i++) {
var entrada = diario[i], index = 0;
if (tieneEvento(evento, entrada)) index += 1;
if (entrada.ardilla) index += 2;
tabla[index] += 1;
}
return tabla;
}
console.log(tabalaPara("pizza", DIARIO));
// → [76, 9, 4, 1]
----
(((array, búsqueda)))(((método indexOf))) La función `tieneEvento` verifica
si una entrada tiene una entrada dada. Los arrays tienen un método
`indexOf` que intenta encontrar un valor dado (en este caso, el nombre del evento)
en el array y regresan el índice en el cuál fue encontrado o -1 si no se encontró.
Así que si la llamada a `indexOf` no regresa -1, entonces sabemos que el evento
fue encontrado en la entrada.
(((array,indexado)))El cuerpo del loop en `tablaPara` encuentra en qué celda de la
tabla cae cada entrada del diario mediante verificar si la entrada contiene
el evento que está buscando y si el evento ocurre junto con un incidente de ardilla.
Entonces, el loop agrega uno al número en el array que corresponde con esta
categoría en la tabla.
Ahora tenemos las herramientas que necesitamos para calcular las ((correlaciones))
individuales. El único paso que queda es encontrar la correlación para
cada tipo de evento registrado y ver si algo sobresale. Pero, ¿cómo debereiamos guardar
esas correlaciones una vez que las calculamos?
== Objetos como mapas ==
(((ejemplo del hombre ardilla)))(((array)))Una camino posible es guardar
todas las ((correlaciones)) en un array, usando objetos con propiedades `nombre` y `valor`.
Pero eso hace que buscar la correlación por un evnto dado sea un poco difícil: tienes que
iterar sobre el array completo para buscar el objeto con el `nombre` correcto. Podemos
poner este código en una función, pero seguiríamos escribiendo más código, y la computadora
estaría haciendo más trabajo del necesario.
[[object_map]]
(((objeto)))(((corchetes)))(((object,como mapa)))(((operador in)))Una mejor forma es usar propiedades de
un objeto nombradas como los tipos de eventos. Podemos usar la notación de acceso de corchetes (paréntesis cuadrados)
para crear y leer las propiedades y el operador `in` para verificar si una propiedad existe.
[source,javascript]
----
var mapa = {};
function giardarPhi(evento, phi) {
mapa[evento] = phi;
}
giardarPhi("pizza", 0.069);
giardarPhi("toqué un árbol", -0.081);
console.log("pizza" in mapa);
// → true
console.log(mapa["touched tree"]);
// → -0.081
----
(((estructura de datos)))Un _((mapa))_ es una manera de ir de valores en
un dominio (en este caso nombres de eventos) a valores correspondientes
en otro dominio (en este caso coeficientes _ϕ_).
Existen algunos problemas potenciales al usar objetos como este, que discutiremos
en el link:06_object.html#prototypes[Capítulo 6], pero por ahora, no nos preocupemos de
eso.
(((loop for/in)))(((loop for)))(((object,iterando))) ¿Y si queremos hallar
todos los eventos para los que tenemos guardados un coeficiente? Las propiedades
no forman una serie predecible, como lo harían en un array, así que no
podemos usar un `for` normal. JavaScript tiene un construcción iterativa
específicamente para funcionar sobre las propiedades de un objeto. Luce un poco como
un loop `for` normal pero se distingue por el uso de la palabra `in`.
[source,javascript]
----
for (var evento in mapa)
console.log("La correlación para '" + evento +
"' es " + mapa[evento]);
// → La correlación para 'pizza' is 0.069
// → La correlación para 'toqué un arbol' es -0.081
----
[[análisis]]
== El análisis final ==
(((diario)))(((ejemplo del hombre ardilla)))(((función
reunirCorrelaciones)))Para encontrar todos los tipos de eventos
que están presentes en el set de datos, simplemente procesamos cada entrada
en turno y después iteramos en los eventos de esa entrada. Mantenemos un objeto
`phis` que tiene los coeficientes de correlación para todos los tipos de eventos que
hemos visto hasta ahora. Cuando encontramos un tipo de evento que no está en el
objeto `phis` todavía, calculamos la correlación y la añadimos al objeto.
// test: clip
// include_code strip_log
[source,javascript]
----
function gatherCorrelations(diario) {
var phis = {};
for (var entry = 0; entry < diario.length; entry++) {
var events = diario[entry].events;
for (var i = 0; i < events.length; i++) {
var event = events[i];
if (!(event in phis))
phis[event] = phi(tableFor(event, diario));
}
}
return phis;
}
var correlations = gatherCorrelations(JOURNAL);
console.log(correlations.pizza);
// → 0.068599434
----
(((correlation)))Let's see what came out.
// test: no
[source,javascript]
----
for (var event in correlations)
console.log(event + ": " + correlations[event]);
// → carrot: 0.0140970969
// → exercise: 0.0685994341
// → weekend: 0.1371988681
// → bread: -0.0757554019
// → pudding: -0.0648203724
// and so on...
----
(((for/in loop)))La mayoría de las correlaciones están cerca de cero.
Comer zanahorias, pan o pudding aparentemente no desencadenan la
licantropía de la ardilla. Sin embargo, parece que sí ocurre más
frecuentemente los fines de semana de alguna manera. Filtremos los
resultados para mostrar sólo las correlaciones mayores que 0.1 o
menores que -0.1.
// start_code
// test: no
[source,javascript]
----
for (var event in correlations) {
var correlation = correlations[event];
if (correlation > 0.1 || correlation < -0.1)
console.log(event + ": " + correlation);
}
// → weekend: 0.1371988681
// → brushed teeth: -0.3805211953
// → candy: 0.1296407447
// → work: -0.1371988681
// → spaghetti: 0.2425356250
// → reading: 0.1106828054
// → peanuts: 0.5902679812
----
A-ha! There are two factors whose ((correlation)) is clearly stronger
than the others. Eating ((peanuts)) has a strong positive effect on
the chance of turning into a squirrel, whereas brushing his teeth has
a significant negative effect.
Interesting. Let's try something.
// include_code strip_log
[source,javascript]
----
for (var i = 0; i < JOURNAL.length; i++) {
var entry = JOURNAL[i];
if (hasEvent("peanuts", entry) &&
!hasEvent("brushed teeth", entry))
entry.events.push("peanut teeth");
}
console.log(phi(tableFor("peanut teeth", JOURNAL)));
// → 1
----
Well, that's unmistakable! The phenomenon occurs precisely when
Jacques eats ((peanuts)) and fails to brush his teeth. If only he
weren't such a slob about dental hygiene, he'd have never even noticed
his affliction.
Knowing this, Jacques simply stops eating peanuts altogether and finds
that this completely puts an end to his transformations.
(((weresquirrel example)))All is well with Jacques for a while. But a
few years later, he loses his ((job)) and is eventually forced to take
employment with a ((circus)), where he performs as _The Incredible
Squirrelman_ by stuffing his mouth with peanut butter before every
show. One day, fed up with this pitiful existence, Jacques fails to
change back into his human form, hops through a crack in the circus
tent, and vanishes into the forest. He is never seen again.
== Further arrayology ==
(((array,methods)))(((method)))Before finishing up this chapter,
I want to introduce you to a few more object-related concepts. We'll
start by introducing some generally useful array methods.
(((push method)))(((pop method)))(((shift method)))(((unshift
method)))We saw `push` and `pop`, which add and remove elements at the
end of an array, link:04_data.html#array_methods[earlier] in this
chapter. The corresponding methods for adding and removing things at
the start of an array are called `unshift` and `shift`.
[source,javascript]
----
var todoList = [];
function rememberTo(task) {
todoList.push(task);
}
function whatIsNext() {
return todoList.shift();
}
function urgentlyRememberTo(task) {
todoList.unshift(task);
}
----
(((task management example)))The previous program manages lists of
tasks. You add tasks to the end of the list by calling
`rememberTo("eat")`, and when you're ready to do something, you call
`whatIsNext()` to get (and remove) the front item from the list. The
`urgentlyRememberTo` function also adds a task but adds it to the
front instead of the back of the list.
(((array,searching)))(((indexOf method)))(((lastIndexOf
method)))The `indexOf` method has a sibling called `lastIndexOf`,
which starts searching for the given element at the end of the array
instead of the front.
[source,javascript]
----
console.log([1, 2, 3, 2, 1].indexOf(2));
// → 1
console.log([1, 2, 3, 2, 1].lastIndexOf(2));
// → 3
----
Both `indexOf` and `lastIndexOf` take an optional second argument that
indicates where to start searching from.
(((slice method)))(((array,indexing)))Another fundamental method
is `slice`, which takes a start index and an end index and returns an
array that has only the elements between those indices. The start
index is inclusive, the end index exclusive.
[source,javascript]
----
console.log([0, 1, 2, 3, 4].slice(2, 4));
// → [2, 3]
console.log([0, 1, 2, 3, 4].slice(2));
// → [2, 3, 4]
----
(((string,indexing)))When the end index is not given, `slice`
will take all of the elements after the start index. Strings also have
a `slice` method, which has a similar effect.
(((concatenation)))(((concat method)))The `concat` method can be used
to glue arrays together, similar to what the `+` operator does for
strings. The following example shows both `concat` and `slice` in
action. It takes an array and an index, and it returns a new array
that is a copy of the original array with the element at the given
index removed.
[source,javascript]
----
function remove(array, index) {
return array.slice(0, index)
.concat(array.slice(index + 1));
}
console.log(remove(["a", "b", "c", "d", "e"], 2));
// → ["a", "b", "d", "e"]
----
== Strings and their properties ==
(((string,properties)))We can read properties like `length` and
`toUpperCase` from string values. But if you try to add a new
property, it doesn't stick.
[source,javascript]
----
var myString = "Fido";
myString.myProperty = "value";
console.log(myString.myProperty);
// → undefined
----
Values of type string, number, and Boolean are not objects, and though
the language doesn't complain if you try to set new properties on
them, it doesn't actually store those properties. The values are
immutable and cannot be changed.
(((string,methods)))(((slice method)))(((indexOf
method)))(((string,searching)))But these types do have some built-in
properties. Every string value has a number of methods. The most
useful ones are probably `slice` and `indexOf`, which resemble the
array methods of the same name.
[source,javascript]
----
console.log("coconuts".slice(4, 7));
// → nut
console.log("coconut".indexOf("u"));
// → 5
----
One difference is that a string's `indexOf` can take a string
containing more than one character, whereas the corresponding array
method looks only for a single element.
[source,javascript]
----
console.log("one two three".indexOf("ee"));
// → 11
----
(((whitespace)))(((trim method)))The `trim` method removes whitespace
(spaces, newlines, tabs, and similar characters) from the start and
end of a string.
[source,javascript]
----
console.log(" okay \n ".trim());
// → okay
----
(((length property,for string)))(((charAt
method)))(((string,indexing)))We have already seen the string type's
`length` property. Accessing the individual characters in a string can
be done with the `charAt` method but also by simply reading numeric
properties, like you'd do for an array.
[source,javascript]
----
var string = "abc";
console.log(string.length);
// → 3
console.log(string.charAt(0));
// → a
console.log(string[1]);
// → b
----
[[arguments_object]]
== The arguments object ==
(((arguments object)))(((length
property)))(((parameter)))(((optional argument)))(((array-like
object)))Whenever a function is called, a special variable named
`arguments` is added to the environment in which the function body
runs. This variable refers to an object that holds all of the
arguments passed to the function. Remember that in JavaScript you are
allowed to pass more (or fewer) arguments to a function than the
number of parameters the function itself declares.
[source,javascript]
----
function noArguments() {}
noArguments(1, 2, 3); // This is okay
function threeArguments(a, b, c) {}
threeArguments(); // And so is this
----
(((length property)))The `arguments` object has a `length` property
that tells us the number of arguments that were really passed to the
function. It also has a property for each argument, named 0, 1, 2, and
so on.
indexsee:[pseudo array,array-like object]
(((array,methods)))If that sounds a lot like an array to you,
you're right, it _is_ a lot like an array. But this object,
unfortunately, does not have any array methods (like `slice` or
`indexOf`), so it is a little harder to use than a real array.
[source,javascript]
----
function argumentCounter() {
console.log("You gave me", arguments.length, "arguments.");
}
argumentCounter("Straw man", "Tautology", "Ad hominem");
// → You gave me 3 arguments.
----
(((journal)))(((console.log)))(((variadic function)))Some functions
can take any number of arguments, like `console.log`. These typically
loop over the values in their `arguments` object. They can be used to
create very pleasant interfaces. For example, remember how we created
the entries to Jacques’ journal.
[source,javascript]
----
addEntry(["work", "touched tree", "pizza", "correr",
"television"], false);
----
Since he is going to be calling this function a lot, we could create
an alternative that is easier to call.
[source,javascript]
----
function addEntry(squirrel) {
var entry = {events: [], squirrel: squirrel};
for (var i = 1; i < arguments.length; i++)
entry.events.push(arguments[i]);
journal.push(entry);
}
addEntry(true, "work", "touched tree", "pizza",
"running", "television");
----
(((arguments object,indexing)))This version reads its first argument
(`squirrel`) in the normal way and then goes over the rest of the
arguments (the loop starts at index 1, skipping the first) to gather
them into an array.
== The Math object ==
(((Math object)))(((Math.min function)))(((Math.max