Está en la página 1de 9

Introducción a los algoritmos de biblioteca estándar

os nuevos programadores suelen dedicar mucho tiempo a escribir bucles personalizados para realizar
tareas relativamente sencillas, como clasificar, contar o buscar matrices. Estos bucles pueden ser
problemáticos, tanto en términos de lo fácil que es cometer un error como en términos de mantenimiento
general, ya que los bucles pueden ser difíciles de entender.

Debido a que buscar, contar y ordenar son operaciones comunes, la biblioteca estándar de C++ viene con
un montón de funciones para hacer estas cosas en solo unas pocas líneas de código. Además, estas
funciones de biblioteca estándar vienen probadas previamente, son eficientes, funcionan en una variedad
de diferentes tipos de contenedores y muchas admiten la paralelización (la capacidad de dedicar múltiples
subprocesos de CPU a la misma tarea para completarla más rápido).

La funcionalidad provista en la biblioteca de algoritmos generalmente se divide en una de tres categorías:

 Inspectores : se utilizan para ver (pero no modificar) datos en un contenedor. Los ejemplos


incluyen buscar y contar.
 Mutadores : se utilizan para modificar datos en un contenedor. Los ejemplos incluyen clasificar
y barajar.
 Facilitadores : se utilizan para generar un resultado basado en los valores de los miembros de
datos. Los ejemplos incluyen objetos que multiplican valores u objetos que determinan en qué
orden se deben clasificar los pares de elementos.
Estos algoritmos viven en la biblioteca de algoritmos . En esta lección, exploraremos algunos de los
algoritmos más comunes, pero hay muchos más, y lo alentamos a que lea la referencia vinculada para ver
todo lo que está disponible.
Nota: Todos estos utilizan iteradores, por lo que si no está familiarizado con los iteradores básicos, consulte
la lección 11.18: Introducción a los iteradores .
Usando std::find para encontrar un elemento por valor
std::findbusca la primera aparición de un valor en un contenedor. std::findtoma 3 parámetros: un
iterador para el elemento inicial de la secuencia, un iterador para el elemento final de la secuencia y un
valor para buscar. Devuelve un iterador que apunta al elemento (si se encuentra) o al final del contenedor
(si no se encuentra el elemento).
Por ejemplo:

#include <algorithm>
#include <array>
#include <iostream>

int main()
{
std::array arr{ 13, 90, 99, 5, 40, 80 };

std::cout << "Enter a value to search for and replace with: ";
int search{};
int replace{};
std::cin >> search >> replace;

// Input validation omitted

// std::find returns an iterator pointing to the found element (or the end of the container)
// we'll store it in a variable, using type inference to deduce the type of
// the iterator (since we don't care)
auto found{ std::find(arr.begin(), arr.end(), search) };
// Algorithms that don't find what they were looking for return the end iterator.
// We can access it by using the end() member function.
if (found == arr.end())
{
std::cout << "Could not find " << search << '\n';
}
else
{
// Override the found element.
*found = replace;
}

for (int i : arr)


{
std::cout << i << ' ';
}

std::cout << '\n';

return 0;
}

COPIAR
Ejemplo de ejecución cuando se encuentra el elemento

Ingrese un valor para buscar y reemplazar con: 5 234

13 90 99 234 40 80

Ejecución de muestra cuando no se encuentra el elemento

Ingrese un valor para buscar y reemplazar con: 0 234

No se pudo encontrar 0

13 90 99 5 40 80

Usando std::find_if para encontrar un elemento que coincida con alguna


condición

A veces queremos ver si hay un valor en un contenedor que coincida con alguna condición (por ejemplo,
una cadena que contiene una subcadena específica) en lugar de un valor exacto. En tales
casos, std::find_ifes perfecto.
La std::find_iffunción funciona de manera similar a std::find, pero en lugar de pasar un valor específico
para buscar, pasamos un objeto invocable, como un puntero de función (o una lambda, que veremos más
adelante). Para cada elemento que se itera, std::find_ifllamará a esta función (pasando el elemento
como argumento a la función), y la función puede regresar truesi se encuentra una coincidencia, o
de falselo contrario.
Aquí hay un ejemplo en el que usamos std::find_ifpara verificar si algún elemento contiene la subcadena
"nuez":
#include <algorithm>
#include <array>
#include <iostream>
#include <string_view>

// Our function will return true if the element matches


bool containsNut(std::string_view str)
{
// std::string_view::find returns std::string_view::npos if it doesn't find
// the substring. Otherwise it returns the index where the substring occurs
// in str.
return (str.find("nut") != std::string_view::npos);
}

int main()
{
std::array<std::string_view, 4> arr{ "apple", "banana", "walnut", "lemon" };

// Scan our array to see if any elements contain the "nut" substring
auto found{ std::find_if(arr.begin(), arr.end(), containsNut) };

if (found == arr.end())
{
std::cout << "No nuts\n";
}
else
{
std::cout << "Found " << *found << '\n';
}

return 0;
}

COPIAR
Producción

Nuez encontrada

Si tuviera que escribir el ejemplo anterior a mano, necesitaría al menos tres bucles (uno para recorrer la
matriz y dos para que coincida con la subcadena). ¡Las funciones estándar de la biblioteca nos permiten
hacer lo mismo en solo unas pocas líneas de código!

Usar std::count y std::count_if para contar cuántas ocurrencias hay


std::county std::count_ifbuscar todas las apariciones de un elemento o un elemento que cumpla una
condición.
En el siguiente ejemplo, contaremos cuántos elementos contienen la subcadena "tuerca":

#include <algorithm>
#include <array>
#include <iostream>
#include <string_view>

bool containsNut(std::string_view str)


{
return (str.find("nut") != std::string_view::npos);
}

int main()
{
std::array<std::string_view, 5> arr{ "apple", "banana", "walnut", "lemon", "peanut" };

auto nuts{ std::count_if(arr.begin(), arr.end(), containsNut) };

std::cout << "Counted " << nuts << " nut(s)\n";

return 0;
}

COPIAR
Producción

Contado 2 tuerca(s)

Usando std::sort para ordenar de forma personalizada


Anteriormente solíamos std::sortordenar una matriz en orden ascendente, pero std::sort puede hacer
más que eso. Hay una versión std::sortque toma una función como su tercer parámetro que nos permite
ordenar como queramos. La función toma dos parámetros para comparar y devuelve verdadero si el
primer argumento debe ordenarse antes que el segundo. Por defecto, std::sortordena los elementos en
orden ascendente.
Usemos std::sortpara ordenar una matriz en orden inverso usando una función de comparación
personalizada llamada greater:

#include <algorithm>
#include <array>
#include <iostream>

bool greater(int a, int b)


{
// Order @a before @b if @a is greater than @b.
return (a > b);
}

int main()
{
std::array arr{ 13, 90, 99, 5, 40, 80 };

// Pass greater to std::sort


std::sort(arr.begin(), arr.end(), greater);

for (int i : arr)


{
std::cout << i << ' ';
}

std::cout << '\n';

return 0;
}

COPIAR
Producción

99 90 80 40 13 5

Una vez más, en lugar de escribir nuestras propias funciones de bucle personalizadas, ¡podemos ordenar
nuestra matriz como queramos en solo unas pocas líneas de código!

Nuestra greaterfunción necesita 2 argumentos, pero no le estamos pasando ninguno, entonces, ¿de dónde
vienen? Cuando usamos una función sin paréntesis (), es solo un puntero de función, no una
llamada. Puede que recuerdes esto de cuando intentamos imprimir una función sin paréntesis e
imprimimos std::cout"1". std::sortusa este puntero y llama a la greaterfunción real con 2 elementos
cualesquiera de la matriz. No sabemos greatercon qué elementos se llamará, porque no está definido qué
algoritmo de clasificación std::sortse está usando debajo del capó. Hablaremos más sobre los punteros
de función en un capítulo posterior.
Consejo
Debido a que la clasificación en orden descendente es tan común, C++ std::greatertambién proporciona
un tipo personalizado (llamado ) para eso (que es parte del encabezado funcional ). En el ejemplo anterior,
podemos reemplazar:

std::sort(arr.begin(), arr.end(), greater); // call our custom greater function

COPIAR
con:

std::sort(arr.begin(), arr.end(), std::greater{}); // use the standard library greater comparison


// Before C++17, we had to specify the element type when we create std::greater
std::sort(arr.begin(), arr.end(), std::greater<int>{}); // use the standard library greater comparison

COPIAR
Tenga en cuenta que std::greater{}necesita las llaves porque no es una función invocable. Es un tipo, y
para usarlo, necesitamos instanciar un objeto de ese tipo. Las llaves crean una instancia de un objeto
anónimo de ese tipo (que luego se pasa como argumento a std::sort).

Para lectores avanzados


Para explicar más detalladamente cómo std::sortusa la función de comparación, tendremos que dar un
paso atrás a una versión modificada del ejemplo de ordenación por selección de la lección 11.4 -- Ordenar
una matriz usando la ordenación por selección .

#include <iostream>
#include <iterator>
#include <utility>

void sort(int* begin, int* end)


{
for (auto startElement{ begin }; startElement != end-1; ++startElement)
{
auto smallestElement{ startElement };
// std::next returns a pointer to the next element, just like (startElement + 1) would.
for (auto currentElement{ std::next(startElement) }; currentElement != end; ++currentElement)
{
if (*currentElement < *smallestElement)
{
smallestElement = currentElement;
}
}

std::swap(*startElement, *smallestElement);
}
}

int main()
{
int array[]{ 2, 1, 9, 4, 5 };

sort(std::begin(array), std::end(array));

for (auto i : array)


{
std::cout << i << ' ';
}

std::cout << '\n';

return 0;
}

COPIAR
Hasta ahora, esto no es nada nuevo y sortsiempre ordena los elementos de menor a mayor. Para agregar
una función de comparación, tenemos que usar un nuevo tipo, std::function<bool(int, int)>, para
almacenar una función que toma 2 parámetros int y devuelve un bool. Trata este tipo como magia por
ahora, lo explicaremos en el capítulo 12 .

void sort(int* begin, int* end, std::function<bool(int, int)> compare)

COPIAR
Ahora podemos pasar una función de comparación como greatera sort, pero ¿cómo se sortusa? Todo lo
que tenemos que hacer es reemplazar la línea

if (*currentElement < *smallestElement)

COPIAR
con

if (compare(*currentElement, *smallestElement))

COPIAR
Ahora la persona que llama sortpuede elegir cómo comparar dos elementos.

#include <functional> // std::function


#include <iostream>
#include <iterator>
#include <utility>

// sort accepts a comparison function


void sort(int* begin, int* end, std::function<bool(int, int)> compare)
{
for (auto startElement{ begin }; startElement != end-1; ++startElement)
{
auto smallestElement{ startElement };

for (auto currentElement{ std::next(startElement) }; currentElement != end; ++currentElement)


{
// the comparison function is used to check if the current element should be ordered
// before the currently "smallest" element.
if (compare(*currentElement, *smallestElement))
{
smallestElement = currentElement;
}
}

std::swap(*startElement, *smallestElement);
}
}

int main()
{
int array[]{ 2, 1, 9, 4, 5 };

// use std::greater to sort in descending order


// (We have to use the global namespace selector to prevent a collision
// between our sort function and std::sort.)
::sort(std::begin(array), std::end(array), std::greater{});

for (auto i : array)


{
std::cout << i << ' ';
}

std::cout << '\n';

return 0;
}

COPIAR

Usar std::for_each para hacer algo con todos los elementos de un


contenedor
std::for_eachtoma una lista como entrada y aplica una función personalizada a cada elemento. Esto es útil
cuando queremos realizar la misma operación para todos los elementos de una lista.
Aquí hay un ejemplo en el que usamos std::for_eachpara duplicar todos los números en una matriz:

#include <algorithm>
#include <array>
#include <iostream>

void doubleNumber(int& i)
{
i *= 2;
}

int main()
{
std::array arr{ 1, 2, 3, 4 };

std::for_each(arr.begin(), arr.end(), doubleNumber);

for (int i : arr)


{
std::cout << i << ' ';
}

std::cout << '\n';

return 0;
}

COPIAR
Producción

2468

Esto a menudo parece el algoritmo más innecesario para los nuevos desarrolladores, porque el código
equivalente con un ciclo for basado en rango es más corto y más fácil. Pero hay beneficios
para std::for_each. Comparemos std::for_eachcon un bucle for basado en rango.

std::ranges::for_each(arr, doubleNumber); // Since C++20, we don't have to use begin() and end().
// std::for_each(arr.begin(), arr.end(), doubleNumber); // Before C++20

for (auto& i : arr)


{
doubleNumber(i);
}

COPIAR
Con std::for_each, nuestras intenciones son claras. Llame doubleNumbercon cada elemento de arr. En el
ciclo for basado en rango, tenemos que agregar una nueva variable, i. Esto conduce a varios errores que
un programador podría cometer cuando está cansado o no presta atención. Por un lado, podría haber una
conversión implícita si no usamos auto. Podríamos olvidar el ampersand, y doubleNumberno afectaría la
matriz. Accidentalmente podríamos pasar una variable que no sea ia doubleNumber. Estos errores no
pueden ocurrir con std::for_each.
Además, std::for_eachpuede omitir elementos al principio o al final de un contenedor, por ejemplo, para
omitir el primer elemento de arr, std::nextse puede usar para avanzar al siguiente elemento.

std::for_each(std::next(arr.begin()), arr.end(), doubleNumber);


// Now arr is [1, 4, 6, 8]. The first element wasn't doubled.

COPIAR
Esto no es posible con un ciclo for basado en rango.

Al igual que muchos algoritmos, std::for_eachse puede paralelizar para lograr un procesamiento más
rápido, lo que lo hace más adecuado para grandes proyectos y big data que un bucle for basado en rango.
Orden de ejecución

Tenga en cuenta que la mayoría de los algoritmos en la biblioteca de algoritmos no garantizan un orden
particular de ejecución. Para tales algoritmos, tenga cuidado de asegurarse de que ninguna función que
pase no asuma un orden particular, ya que el orden de invocación puede no ser el mismo en todos los
compiladores.

Los siguientes algoritmos garantizan la ejecución


secuencial: std::for_each, std::copy, std::copy_backward, std::movey std::move_backward.
Mejores prácticas
A menos que se especifique lo contrario, no asuma que los algoritmos de la biblioteca estándar se
ejecutarán en una secuencia
particular. std::for_each, std::copy, std::copy_backward, std::movey std::move_backwardtienen garantías
secuenciales.

Rangos en C++20

Tener que pasar explícitamente arr.begin()y arr.end()a cada algoritmo es un poco molesto. Pero no


temas: C++20 agrega rangos , lo que nos permite simplemente pasar arr. Esto hará que nuestro código sea
aún más corto y más legible.
Conclusión

La biblioteca de algoritmos tiene un montón de funciones útiles que pueden hacer que su código sea más
simple y robusto. Solo cubrimos un pequeño subconjunto en esta lección, pero debido a que la mayoría de
estas funciones funcionan de manera muy similar, una vez que sepa cómo funcionan algunas, podrá
utilizar la mayoría de ellas.

También podría gustarte