Mapeo inteligente con flatMap en Javascript
Podemos ver a Array.flatMap como el hermano inteligente de Array.flat. Si no sabes lo que es Array.flat, por acá te dejo un link donde lo explico a detalle:
El método Array.flatMap básicamente se encarga de llevar a cabo 2 simples tareas:
- Transformar cada elemento de nuestro arreglo subyacente.
- “Aplanar” o reducir en máximo 1 nivel el resultado final.
En este sentido, es el equivalente a utilizar de forma combinada los métodos Array.map y Array.flat.
Esto se entiende mejor con un ejemplo:
1
2
3
4
5
const posts = [
{ id: 1, title: 'When the Yogurt Took Over', tags: ['Sci-fi', 'Fantasy'], userId: 666 },
{ id: 2, title: 'The Mountain', tags: ['Romantic', 'Love Stories'], userId: 666 },
{ id: 3, title: 'Against all Odds', tags: ['Survival', 'War Stories'], userId: 666 },
];
Nuestro objetivo es obtener todos los tags de las publicaciones del usuario 666 con la siguiente estructura:
1
[ 'Sci-fi', 'Fantasy', 'Romantic', 'Love Stories', 'Survival', 'War Stories' ]
Veamos, como primer paso se me ocurre echar mano de mi viejo conocido Array.map para extraer solamente la información que necesito:
1
2
3
4
5
6
7
8
9
const tags = posts
.map((post) => post.tags);
console.log(tags);
// [
// ['Sci-fi', 'Fantasy'],
// ['Romantic', 'Love Stories'],
// ['Survival', 'War Stories']
// ]
Perfecto, con esto tengo todos los tags de las publicaciones, sin embargo, la estructura que obtengo no es la ideal.
Puedo apreciar que resultado es un arreglo de 2 dimensiones, ¿que tal si echamos mano de nuestro recién conocido amigo Array.flat para reducir esta estructura en 1 nivel? 🤔:
1
2
3
4
5
6
7
8
9
10
11
12
13
const tags = posts
.map((post) => post.tags)
.flat();
console.log(tags);
// [
// 'Sci-fi',
// 'Fantasy',
// 'Romantic',
// 'Love Stories',
// 'Survival',
// 'War Stories'
// ]
Voalá, justo lo que quiero.
La pregunta ahora es ¿puedo llevar a cabo todo lo anterior en una sola acción? y la respuesta es Array.flatMap:
1
2
3
4
5
6
7
8
9
10
11
12
const tags = posts
.flatMap((post) => post.tags);
console.log(tags);
// [
// 'Sci-fi',
// 'Fantasy',
// 'Romantic',
// 'Love Stories',
// 'Survival',
// 'War Stories'
// ]
Pan comido ¡eh! 😎.
Venga, ya me mostraste al conejo 🐇 veamos hasta dónde llega su madriguera, ¡Muéstrame un ejemplo más práctico!
Con esta información:
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
const availabilityZones = [
{ id: 1, code: 'a', region: 'us-west-1' },
{ id: 2, code: 'a', region: 'eu-central-1' },
{ id: 3, code: 'b', region: 'us-west-1' },
{ id: 4, code: 'a', region: 'sa-east-1' },
{ id: 5, code: 'a', region: 'us-east-1' },
{ id: 6, code: 'b', region: 'eu-central-1' },
{ id: 7, code: 'c', region: 'us-west-1' }
];
const datacenters = [
{ id: 100121, address: '1100 Harris Hill LN Tarboro', azId: 1 },
{ id: 100122, address: '3602 Maldon Way Hight Point', azId: 1 },
{ id: 100123, address: '1233 Rusell RD Dento', azId: 1 },
{ id: 133322, address: '6052 Turnerheim Sindlingen, 85 Farbenstraße', azId: 2 },
{ id: 100055, address: '6032 DFB-Campus, 274 Kennedyallee', azId: 2 },
{ id: 100023, address: '2800 Sam Wilson RD Charlotte' , azId: 3 },
{ id: 100024, address: '1145 Balfour Quarry RD Salisbury' , azId: 3 },
{ id: 112993, address: '1283 Rua Vigario João Álvares', azId: 4 },
{ id: 112992, address: '4330 Rua Ricardo Gumbleton Daunt', azId: 4 },
{ id: 100323, address: '9305 Merrifield AVE Fairfax Salem', azId: 5 },
{ id: 100343, address: '8012 Broad ST Portsmouth Indian Valley', azId: 5 },
{ id: 121049, address: '6043 Zum Kaagärtner Vereinshaus, 15 Am Ginnheimer', azId: 6 },
{ id: 130049, address: '6055 Symphatie, 9 Altenhainer Straße', azId: 6 },
{ id: 100432, address: '3917 Shelby AVE Kitty Hawk', azId: 7 },
{ id: 100423, address: '1704 Featherwood Court Charlotte', azId: 7 }
];
Nos piden crear un artefacto que tenga la capacidad de filtrar todos los datacenters de cualquier zona de disponibilidad de acuerdo a 1 o más regiones.
Ejemplo:
- Filtra todos los datacenters de la región
us-west-1. - Filtra todos los datacenters de la región
us-west-1yeu-central-1. - Etc.
Para resolver este problema se me ocurre crear una clase que se encargue de llevar a cabo las tareas de recibir los filtros y entregar los resultados.
Algo como esto:
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
class DatacenterFilter {
constructor(availabilityZones, datacenters) {
this.filters = [];
this.availabilityZones = availabilityZones;
this.datacenters = datacenters;
}
static create(availabilityZones, datacenters) {
return new DatacenterFilter(availabilityZones, datacenters);
}
add(filter) {
this.filters.push(filter);
}
getMatchingResults() {
const filterDatacentersByAZ = (az) =>
this.datacenters.filter((dc) => az.id === dc.azId);
const filterAZsByRegion = (region) =>
this.availabilityZones.filter((az) => region === az.region);
const filterDatacentersByRegion = (filter) =>
filterAZsByRegion(filter).flatMap(filterDatacentersByAZ);
return this.filters.flatMap(filterDatacentersByRegion);
}
}
Por el momento no te abrumes con todos los detalles de implementación, pero presta atención al método getMatchingResults(), en especial a la partes donde hacemos uso del método Array.flatMap, allí es donde se esconde el señor conejo 🐇:
La función filterDatacentersByRegion() básicamente funciona como un disparador de las funciones filterAZsByRegion() y filterDatacentersByAZ():
Aislemos por un momento la ejecución de filterAZsByRegion() pasándole como argumento la región us-west-1:
Con esta información podemos obtener los datacenters asociados a estas 3 zonas de disponibilidad, de paso también cambiemos ese Array.flatMap por Array.map solo para observar el resultado:
Mmmm 🤔 no es la estructura que busco, regresemos a nuestro viejo Array.flatMap a su lugar y observemos el resultado:
Mucho mejor 👌.
Ahora que conocemos los detalles filterDatacentersByRegion(), sólo queda la pregunta ¿quién se encarga de ejecutarla? 🤨
Ese es trabajo es de esta pequeña línea:
¿Puedes adivinar por qué?
Es correcto, la función filterDatacentersByRegion() es ejecutada por cada filtro que hayamos agregado. Básicamente algo así:
Como ejercicio intenta responder a la siguiente pregunta ¿por qué hemos utilizado el método Array.flatMap en esta línea?, te daré una pista, cambia ese Array.flatMap por un Array.map y observa el resultado.
De cualquier manera, te dejaré por acá el código del ejemplo para que saques tus conclusiones:
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
const availabilityZones = [
{ id: 1, code: 'a', region: 'us-west-1' },
{ id: 2, code: 'a', region: 'eu-central-1' },
{ id: 3, code: 'b', region: 'us-west-1' },
{ id: 4, code: 'a', region: 'sa-east-1' },
{ id: 5, code: 'a', region: 'us-east-1' },
{ id: 6, code: 'b', region: 'eu-central-1' },
{ id: 7, code: 'c', region: 'us-west-1' }
];
const datacenters = [
{ id: 100121, address: '1100 Harris Hill LN Tarboro', azId: 1 },
{ id: 100122, address: '3602 Maldon Way Hight Point', azId: 1 },
{ id: 100123, address: '1233 Rusell RD Dento', azId: 1 },
{ id: 133322, address: '6052 Turnerheim Sindlingen, 85 Farbenstraße', azId: 2 },
{ id: 100055, address: '6032 DFB-Campus, 274 Kennedyallee', azId: 2 },
{ id: 100023, address: '2800 Sam Wilson RD Charlotte' , azId: 3 },
{ id: 100024, address: '1145 Balfour Quarry RD Salisbury' , azId: 3 },
{ id: 112993, address: '1283 Rua Vigario João Álvares', azId: 4 },
{ id: 112992, address: '4330 Rua Ricardo Gumbleton Daunt', azId: 4 },
{ id: 100323, address: '9305 Merrifield AVE Fairfax Salem', azId: 5 },
{ id: 100343, address: '8012 Broad ST Portsmouth Indian Valley', azId: 5 },
{ id: 121049, address: '6043 Zum Kaagärtner Vereinshaus, 15 Am Ginnheimer', azId: 6 },
{ id: 130049, address: '6055 Symphatie, 9 Altenhainer Straße', azId: 6 },
{ id: 100432, address: '3917 Shelby AVE Kitty Hawk', azId: 7 },
{ id: 100423, address: '1704 Featherwood Court Charlotte', azId: 7 }
];
class DatacenterFilter {
constructor(availabilityZones, datacenters) {
this.filters = [];
this.availabilityZones = availabilityZones;
this.datacenters = datacenters;
}
static create(availabilityZones, datacenters) {
return new DatacenterFilter(availabilityZones, datacenters);
}
add(filter) {
this.filters.push(filter);
}
getMatchingResults() {
const filterDatacentersByAZ = (az) => this.datacenters.filter((dc) => az.id === dc.azId);
const filterAZsByRegion = (region) => this.availabilityZones.filter((az) => region === az.region);
const filterDatacentersByRegion = (filter) => filterAZsByRegion(filter).flatMap(filterDatacentersByAZ);
return this.filters.flatMap(filterDatacentersByRegion);
}
}
const filter = DatacenterFilter.create(availabilityZones, datacenters);
filter.add('us-west-1');
filter.add('sa-east-1');
const result = filter.getMatchingResults();
console.log(result);
Sin más agradezco tu tiempo y me despido, ¡Happy Coding! 🐇







