Task 1: Enhancing a Complex Data Processing Function with Function Overloading and Templates
Suppose that initially, we have a complex data processing function designed to operate on a list of maps, applying a transformation that converts all string values within the maps to uppercase. Here's the initial version in C++:
#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>
void process_data(const std::vector<std::unordered_map<std::string, std::string>>& items) {
std::vector<std::unordered_map<std::string, std::string>> processed_items;
for (const auto& item : items) {
std::unordered_map<std::string, std::string> processed_item;
for (const auto& pair : item) {
std::string upper_value = pair.second;
std::transform(upper_value.begin(), upper_value.end(), upper_value.begin(), ::toupper);
processed_item[pair.first] = upper_value;
}
processed_items.push_back(processed_item);
}
for (size_t i = 0; i < std::min(processed_items.size(), static_cast<size_t>(3)); ++i) {
std::cout << "Processed Item: ";
for (const auto& pair : processed_items[i]) {
std::cout << pair.first << ": " << pair.second << " ";
}
std::cout << "\n";
}
}
To enhance this function, we'll introduce an overloaded function to allow custom transformation and filtering capabilities while maintaining backward compatibility.
Function templates in C++ allow you to write generic, reusable code that can handle different data types. By using templates, you define a blueprint for a function without specifying the exact data types the function will operate on. This allows the compiler to generate a version of the function with the correct data types when the function is called.
In the code snippet below, function templates are used to define a generic version of the process_data function. This templated function accepts custom transformation and filtering logic through lambda expressions, allowing for flexible processing while maintaining the function's default behavior to ensure backward compatibility.
template <typename Condition, typename Transform>
void process_data(
const std::vector<std::unordered_map<std::string, std::string>>& items,
Condition condition,
Transform transform
) {
std::vector<std::unordered_map<std::string, std::string>> processed_items;
for (const auto& item : items) {
if (condition(item)) {
auto transformed_item = transform(item);
processed_items.push_back(transformed_item);
}
}
for (size_t i = 0; i < std::min(processed_items.size(), static_cast<size_t>(3)); ++i) {
std::cout << "Processed Item: ";
for (const auto& pair : processed_items[i]) {
std::cout << pair.first << ": " << pair.second << " ";
}
std::cout << "\n";
}
}
// Default behavior - convert string values to uppercase
process_data([{ {"name", "apple"}, {"quantity", "10"} }, { {"name", "orange"}, {"quantity", "5"} }]);
// Custom filter - select items with a quantity greater than 5
process_data(
[{ {"name", "apple"}, {"quantity", "10"} }, { {"name", "orange"}, {"quantity", "5"} }],
[](const auto& item) { return std::stoi(item.at("quantity")) > 5; },
[](const auto& item) { return item; }
);
// Custom transformation - convert names to uppercase and multiply the quantity by 2
process_data(
[{ {"name", "apple"}, {"quantity", "10"} }, { {"name", "orange"}, {"quantity", "5"} }],
[](const auto&) { return true; },
[](const auto& item) {
std::unordered_map<std::string, std::string> transformed_item;
for (const auto& pair : item) {
if (pair.first == "name") {
std::string upper_value = pair.second;
std::transform(upper_value.begin(), upper_value.end(), upper_value.begin(), ::toupper);
transformed_item[pair.first] = upper_value;
} else {
transformed_item[pair.first] = std::to_string(std::stoi(pair.second) * 2);
}
}
return transformed_item;
}
);
The evolved version uses function templates and lambda expressions to allow for custom transformations and filtering, ensuring the process remains backward compatible with existing code paths by maintaining default functionality.