Combining Collections with Zip in C++23 for Environment friendly Information Processing

[ad_1]

On this article, we’ll have a look at a brilliant useful ranges view in C++23 – views::zip. In brief, it means that you can mix two or extra ranges and iterate by way of them concurrently. Let’s see how one can use it.

Fundamental

 

When you have two (or extra) separate containers and also you need to iterate by way of them “in parallel,” you possibly can write the next code:

std::vector a { 10, 20, 30, 40, 50 };
std::vector<std::string> b { "ten", "twenty", "thirty", "fourty" };

for (size_t i = 0; i < std::min(a.dimension(), b.dimension()); ++i)
    std::cout << std::format("{} -> {}n", a[i], b[i]);

We are able to even use iota to generate indices:

// vary model
for (auto i : std::views::iota(0uz, std::min(a.dimension(), b.dimension())))
    std::cout << std::format("{} -> {}n", a[i], b[i]);

As of C++23 you need to use the integer literal suffix for std::size_t is any mixture of z or Z with u or U (i.e. zu, zU, Zu, ZU, uz, uZ, Uz, or UZ).

Or extract some names reasonably than utilizing indices:

for (size_t i = 0; i < std::min(a.dimension(), b.dimension()); ++i) {
    const auto& num = a[i];
    const auto& identify = b[i];
    std::cout << std::format("{} -> {}n", num, identify);
}

Play with all examples @Compiler Explorer

As you possibly can see, we are able to simply iterate by way of these containers, however the code will not be elegant. What’s extra, you need to take note of particulars and ensure you use the min dimension from all collections.

In C++23, we’ve got a nicer answer: zips!

#embrace <format>
#embrace <iostream>
#embrace <ranges>
#embrace <vector>

int major() {
    std::vector a { 10, 20, 30, 40, 50 };
    std::vector<std::string> b { "one", "two", "three", "4" };
        
    for (const auto& [num, name] : std::views::zip(a, b))
        std::cout << std::format("{} -> {}n", num, identify);
}

See @Compiler Explorer

How cool is that? And you’ll already play with this in GCC 13 (in addition to partially in Clang 15, and MSVC 17.3).

Suppose you iterate over two ranges zip yields std::pair and std::tuple you probably have extra ranges. My code used structured binding to unpack these tuples into significant names.

This iteration may be particularly useful when your information is break up throughout many containers – like in SOA (Construction of Arrays) reasonably than AOS (Array of structs).

Observe: for having a container with simply indices, you possibly can take a look at views::enumerate (additionally C++23)

However how a couple of extra sensible instance?

Strolling over three containers

 

Let’s say you’ve gotten three containers:

  • dates – dates of the gross sales
  • merchandise – what varieties of merchandise had been bought
  • gross sales – earnings from every sorts
std::vector<std::string> dates = {
    "2023-03-01", "2023-03-01", "2023-03-02", "2023-03-03", "2023-03-03"
};
std::vector<std::string> merchandise = {
    "Sneakers", "T-shirts", "Pants", "Sneakers", "T-shirts"
};
std::vector gross sales = {
    50.0, 20.0, 30.0, 75.0, 40.0
};

We need to group these information and test what earnings we acquired on a given date, or how a lot we acquired promoting a given class of merchandise.

That’s to zip we are able to write the next code:

std::map<std::string, double> salesInDay;
std::map<std::string, double> salesPerProduct;
for (const auto & [d, p, s] : std::views::zip(dates, merchandise, gross sales)) {
    salesInDay[d] += s;
    salesPerProduct[p] += s;
}

for (const auto& [day, sale] : salesInDay)
    std::cout << std::format("in {} bought {}n", day, sale);

for (const auto& [prod, sale] : salesPerProduct)
    std::cout << std::format("bought {} in {} classn", sale, prod);

Run @Compiler Explorer

The Output:

in 2023-03-01 bought 70
in 2023-03-02 bought 30
in 2023-03-03 bought 115
bought 30 in Pants class
bought 125 in Sneakers class
bought 60 in T-shirts class

Zip rework

 

However there’s extra. What if we’d wish to compute the earnings primarily based on costs and prices:

int major() {
    std::vector costs = {100, 200, 150, 180, 130};
    std::vector prices = {10, 20, 50, 40, 100};     
    
    std::vector<int> earnings;    
    earnings.reserve(costs.dimension());
    for (const auto& [p, c] : std::views::zip(costs, prices))
        earnings.emplace_back(p - c); // <<
    
    std::cout << std::accumulate(earnings.start(), earnings.finish(), 0);
}

The code above makes use of zip, after which shops the computation within the earnings vector. However because of the associated view: zip_transform, we are able to write:

int major() {
    std::vector costs = {100, 200, 150, 180, 130};
    std::vector prices = {10, 20, 50, 40, 100};     
    
    std::vector<int> earnings;    
    earnings.reserve(costs.dimension());
    auto compute = [](const auto& p, const auto& c) { return p - c; };
    auto v = std::views::zip_transform(compute, costs, prices);
    for (const auto& in : v)
        earnings.emplace_back(in);
    
    std::cout << std::accumulate(earnings.start(), earnings.finish(), 0);
}

This time, we use a separate view that walks on parts of each containers however then applies the compute callable object.

Play @Compiler Explorer

Ready for ranges::to and creating containers

 

Proper now, you need to use range-based for loops to create different containers, however in hopefully a few weeks, you’ll be capable to write:

std::vector costs = {100, 200, 150, 180, 130};
std::vector prices = {10, 20, 50, 40, 100};    

auto compute = [](const auto& p, const auto& c) { return p - c; };
auto v = std::views::zip_transform(compute, costs, prices);
auto earnings = v | std::ranges::to<std::vector>();

Briefly, ranges::to means that you can pack numerous views and subranges right into a separate container You could course of later.

To unzip a map, test views::keys, views::values or extra generic views::parts. See at cppreference.com.

Abstract

 

On this quick article, we checked out a useful view known as std::views::zip. It means that you can “stroll” on parts of a number of containers directly.

Earlier than C++20/23, you may be conversant in enhance mix or ranges v3 that allowed for such iteration method. However for instance, enhance mix wasn’t ready for structured binding.

Should you marvel why it’s higher to make use of views::abc reasonably than ranges::abc_view see this associated article by Barry R.: Choose views::meow | Barry’s C++ Weblog

Again to you

Do you employ zip views in your code? Possibly you used some customized code or the enhance strategy?

Be a part of the dialogue under, or at reddit/cpp.

[ad_2]

Leave a Comment

Your email address will not be published. Required fields are marked *