Filter, Map y Reduce

Los tres sirven para procesar arreglos:

FILTER → selecciona elementos
MAP → transforma elementos
REDUCE → combina elementos en un resultado

FILTER
[1 2 3 4 5 6]

[2 4 6]

MAP
[1 2 3 4 5]

[2 4 6 8 10]

REDUCE
[1 2 3 4 5]

15

En C no existen como funciones estándar llamadas exactamente filter, map y reduce. Nosotros podemos construirlas.

Y para hacerlas generales, usamos punteros a funciones.


FILTER

filter recorre un arreglo y selecciona solamente los elementos que cumplen una condición.

Por ejemplo:

10  3  8  7  4  11

Queremos los pares:

10  8  4

La condición sería:

intesPar(intnumero)
{
return numero%2==0;
}

Filter usando punteros

Podemos hacer:

void filter(int*vector,intcantidad,int*resultado,int*cantidadResultado,int (*condicion)(int))
{
int*p=vector;
int*r=resultado;
*cantidadResultado=0;
while (p<vector+cantidad)
{if (condicion(*p))
{*r=*p;
r++;
(*cantidadResultado)++;
}
p++;
}
}

Fijate en esta parte:

if (condicion(*p))

p es un puntero.

*p

es el valor actual.

Y:

condicion(*p)

ejecuta la función que pasamos.


Ejemplo completo de FILTER

#include <stdio.h>

int esPar(int numero)
{
return numero % 2 == 0;
}

void filter(
int *vector,
int cantidad,
int *resultado,
int *cantidadResultado,
int (*condicion)(int)
)
{
int *p = vector;
int *r = resultado;

*cantidadResultado = 0;

while (p < vector + cantidad)
{
if (condicion(*p))
{
*r = *p;
r++;
(*cantidadResultado)++;
}

p++;
}
}

void mostrar(int *vector, int cantidad)
{
int *p = vector;

while (p < vector + cantidad)
{
printf("%d ", *p);
p++;
}

printf("\n");
}

int main()
{
int vector[] = {10, 3, 8, 7, 4, 11};
int resultado[6];
int cantidadResultado;

filter(
vector,
6,
resultado,
&cantidadResultado,
esPar
);

mostrar(resultado, cantidadResultado);

return 0;
}

Resultado:

10 8 4

MAP

Ahora vamos con map.

¿Qué hace?

map transforma cada elemento.

Por ejemplo:

1  2  3  4  5

Queremos multiplicar cada elemento por 2:

2  4  6  8  10

Tenemos una función:

int duplicar(int numero)
{
return numero*2;
}

MAP usando punteros

void map(
int *vector,
int cantidad,
int (*transformacion)(int)
)
{
int *p = vector;

while (p < vector + cantidad)
{
*p = transformacion(*p);
p++;
}
}

Esta línea es fundamental:

*p=transformacion(*p);

Significa:

Tomo el valor apuntado por p, lo transformo y vuelvo a guardarlo en el mismo lugar.


Ejemplo completo de MAP

#include <stdio.h>

int duplicar(int numero)
{
return numero * 2;
}

void map(
int *vector,
int cantidad,
int (*transformacion)(int)
)
{
int *p = vector;

while (p < vector + cantidad)
{
*p = transformacion(*p);
p++;
}
}

void mostrar(int *vector, int cantidad)
{
int *p = vector;

while (p < vector + cantidad)
{
printf("%d ", *p);
p++;
}

printf("\n");
}

int main()
{
int vector[] = {1, 2, 3, 4, 5};

map(vector, 5, duplicar);

mostrar(vector, 5);

return 0;
}

Resultado:

2 4 6 8 10

REDUCE

Ahora viene reduce.

¿Qué hace?

Reduce toma muchos elementos y genera un solo resultado.

Por ejemplo:

1 2 3 4 5

Queremos sumar:

1 + 2 + 3 + 4 + 5

Resultado:

15

Reduce usando punteros

Podemos definir una función que indique cómo combinar dos valores:

int sumar(inta, intb){returna+b;}

Y nuestro reduce:

int reduce(
int *vector,
int cantidad,
int inicial,
int (*operacion)(int, int)
)
{
int resultado = inicial;

int *p = vector;

while (p < vector + cantidad)
{
resultado = operacion(resultado, *p);
p++;
}

return resultado;
}

La parte importante es:

resultado=operacion(resultado, *p);

Ejemplo completo

#include <stdio.h>

int sumar(int a, int b)
{
return a + b;
}

int reduce(
int *vector,
int cantidad,
int inicial,
int (*operacion)(int, int)
)
{
int resultado = inicial;

int *p = vector;

while (p < vector + cantidad)
{
resultado = operacion(resultado, *p);
p++;
}

return resultado;
}

int main()
{
int vector[] = {1, 2, 3, 4, 5};

int resultado = reduce(
vector,
5,
0,
sumar
);

printf("Resultado: %d\n", resultado);

return 0;
}

Resultado:

Resultado: 15

¿Por qué pasamos 0?

Tenemos:

reduce(vector, 5, 0, sumar);

El 0 es el valor inicial.

Internamente pasa esto:

resultado = 0
resultado = 0 + 1 → 1
resultado = 1 + 2 → 3
resultado = 3 + 3 → 6
resultado = 6 + 4 → 10
resultado = 10 + 5 → 15

Por eso devuelve:

15

Podemos hacer otros Reduce

Lo interesante es que no tenemos que hacer solamente sumas.

Podemos hacer:

int multiplicar(int a, int b)
{
return a*b;
}

Entonces:

int resultado=reduce(vector, 5, 1, multiplicar);

Con:

1 2 3 4 5

obtenemos:

1 × 2 × 3 × 4 × 5 = 120

FILTER + MAP + REDUCE juntos

Ahora viene la parte más interesante.

Supongamos que tenemos:

1 2 3 4 5 6 7 8

Queremos:

  1. Quedarnos con los pares.
  2. Multiplicarlos por 10.
  3. Sumarlos.

Paso 1 — FILTER

1 2 3 4 5 6 7 8      

2 4 6 8

Paso 2 — MAP

2 4 6 8 

20 40 60 80

Paso 3 — REDUCE

20 + 40 + 60 + 80

Resultado:

200

11. Programa completo

#include <stdio.h>

int esPar(int numero)
{
return numero % 2 == 0;
}

int multiplicarPorDiez(int numero)
{
return numero * 10;
}

int sumar(int a, int b)
{
return a + b;
}

void filter(
int *vector,
int cantidad,
int *resultado,
int *cantidadResultado,
int (*condicion)(int)
)
{
int *p = vector;
int *r = resultado;

*cantidadResultado = 0;

while (p < vector + cantidad)
{
if (condicion(*p))
{
*r = *p;

r++;

(*cantidadResultado)++;
}

p++;
}
}

void map(
int *vector,
int cantidad,
int (*transformacion)(int)
)
{
int *p = vector;

while (p < vector + cantidad)
{
*p = transformacion(*p);

p++;
}
}

int reduce(
int *vector,
int cantidad,
int inicial,
int (*operacion)(int, int)
)
{
int resultado = inicial;

int *p = vector;

while (p < vector + cantidad)
{
resultado = operacion(resultado, *p);

p++;
}

return resultado;
}

void mostrar(int *vector, int cantidad)
{
int *p = vector;

while (p < vector + cantidad)
{
printf("%d ", *p);
p++;
}

printf("\n");
}

int main()
{
int vector[] = {1, 2, 3, 4, 5, 6, 7, 8};

int filtrados[8];
int cantidadFiltrados;

/*
FILTER
Nos quedamos solamente
con los números pares.
*/

filter(
vector,
8,
filtrados,
&cantidadFiltrados,
esPar
);

printf("Despues de FILTER:\n");
mostrar(filtrados, cantidadFiltrados);

/*
MAP
Multiplicamos cada elemento
por 10.
*/

map(
filtrados,
cantidadFiltrados,
multiplicarPorDiez
);

printf("Despues de MAP:\n");
mostrar(filtrados, cantidadFiltrados);

/*
REDUCE
Sumamos todos los elementos.
*/

int resultado = reduce(
filtrados,
cantidadFiltrados,
0,
sumar
);

printf("Despues de REDUCE:\n");
printf("%d\n", resultado);

return 0;
}

Resultado:

Despues de FILTER:2 4 6 8
Despues de MAP:20 40 60 80
Despues de REDUCE:200

Lo más importante: punteros a funciones

Cuando ves:

int (*condicion)(int)

significa:

condicion es un puntero a una función que recibe un int y devuelve un int.

Cuando ves:

int (*transformacion)(int)

significa:

transformacion apunta a una función que recibe un int y devuelve un int.

Y:

int (*operacion)(int, int)

significa:

operacion apunta a una función que recibe dos int y devuelve un int.


Cómo saber qué poner

Esta tabla te puede salvar en un ejercicio:

CasoPuntero a función
Preguntar si cumple condiciónint (*condicion)(int)
Transformar un elementoint (*transformacion)(int)
Combinar dos elementosint (*operacion)(int, int)

Por ejemplo:

FILTER

intesPar(int x)

Entonces:

int (*condicion)(int)

MAP

intduplicar(int x)

Entonces:

int (*transformacion)(int)

REDUCE

intsumar(int a, int b)

Entonces:

int (*operacion)(int, int)

Resumen

Si te preguntan «¿qué es Filter, Map y Reduce?», pensalo así:

FILTER Muchos → algunos
MAP Muchos → muchos transformados
REDUCE Muchos → uno

Y con punteros:

FILTER →int (*condicion)(int)
MAP →int (*transformacion)(int)
REDUCE →int (*operacion)(int, int)

La clave es que C permite pasar funciones como parámetros mediante punteros a funciones. Eso hace posible implementar estos tres patrones de manera general, en lugar de escribir una función distinta para cada condición, transformación o operación.