Skip to content

Commit

Permalink
feat: Improve Acts::zip to take arbitrary number of ranges (#2236)
Browse files Browse the repository at this point in the history
This allows finally something like this for an arbitray amount of ranges / containers:
```c++
for(const auto &[a, b, c] : Acts::zip(ra, rb, rc)) {
  // do something
}
```

Also adds unit test for this.
  • Loading branch information
benjaminhuth committed Jun 23, 2023
1 parent 71e4f2d commit 9c50e11
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 21 deletions.
45 changes: 24 additions & 21 deletions Core/include/Acts/Utilities/Zip.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,36 @@

namespace Acts {

/// Function that allows to zip two ranges to be used in a range-based for loop.
/// This is a very basic implementation for two ranges, but could relatively
/// easily be extended with variadic templates.
/// @tparam RA The first range type
/// @tparam RB The second range type
/// @param ra The first range
/// @param rb The second range
/// Function that allows to zip some ranges to be used in a range-based for
/// loop. When wanting to mutate the entries, the result must be captured by
/// value:
///
/// for(auto [a, b, c] : zip(ra, rb, rc)) { a+=2; }
///
/// @tparam R The ranges type pack
/// @param r The ranges parameter pack
/// @note the behaviour is undefined if the ranges do not have equal range
template <typename RA, typename RB>
auto zip(RA &&ra, RB &&rb) {
using ItA = decltype(ra.begin());
using ItB = decltype(rb.begin());

template <typename... R>
auto zip(R &&...r) {
struct It {
ItA a;
ItB b;
std::tuple<decltype(r.begin())...> iterators;
static_assert(std::tuple_size_v<decltype(iterators)> > 0);

using reference = std::tuple<decltype(*std::declval<ItA>()),
decltype(*std::declval<ItB>())>;
using reference = std::tuple<decltype(*r.begin())...>;

auto operator++() {
++a;
++b;
std::apply([](auto &...args) { (++args, ...); }, iterators);
return *this;
}

auto operator!=(const It &other) const { return a != other.a; }
auto operator!=(const It &other) const {
return std::get<0>(iterators) != std::get<0>(other.iterators);
}

reference operator*() { return {*a, *b}; }
reference operator*() {
return std::apply([](auto &...args) { return reference{*args...}; },
iterators);
}
};

struct Zip {
Expand All @@ -50,7 +51,9 @@ auto zip(RA &&ra, RB &&rb) {
auto end() { return e; }
};

return Zip{It{ra.begin(), rb.begin()}, It{ra.end(), rb.end()}};
auto begin = std::make_tuple(r.begin()...);
auto end = std::make_tuple(r.end()...);
return Zip{It{begin}, It{end}};
}

} // namespace Acts
2 changes: 2 additions & 0 deletions Tests/UnitTests/Core/Utilities/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ endif()
add_unittest(Any AnyTests.cpp)
add_unittest(AnyDebug AnyTests.cpp)
target_compile_definitions(ActsUnitTestAnyDebug PRIVATE _ACTS_ANY_ENABLE_VERBOSE _ACTS_ANY_ENABLE_DEBUG _ACTS_ANY_ENABLE_TRACK_ALLOCATIONS)

add_unittest(Zip ZipTests.cpp)
48 changes: 48 additions & 0 deletions Tests/UnitTests/Core/Utilities/ZipTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// This file is part of the Acts project.
//
// Copyright (C) 2023 CERN for the benefit of the Acts project
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <boost/test/unit_test.hpp>

#include <Acts/Utilities/Zip.hpp>

#include <array>
#include <string>
#include <vector>

const std::vector<int> vec = {1, 2, 3, 4};
const std::array<double, 4> arr = {2.0, 4.0, 6.0, 8.0};
const std::string str = "abcd";

BOOST_AUTO_TEST_CASE(test_access) {
int i = 0;
for (const auto &[a, b, c] : Acts::zip(vec, arr, str)) {
BOOST_CHECK(a == vec[i]);
BOOST_CHECK(b == arr[i]);
BOOST_CHECK(c == str[i]);
++i;
}
}

BOOST_AUTO_TEST_CASE(test_mutation) {
std::vector<int> vec2 = vec;
std::array<double, 4> arr2 = arr;
std::string str2 = str;

for (auto [a, b, c] : Acts::zip(vec2, arr2, str2)) {
a *= 2;
b *= 2;
c = 'e';
}

for (int i = 0; i < 4; ++i) {
BOOST_CHECK(vec2[i] == 2 * vec[i]);
BOOST_CHECK(arr2[i] == 2 * arr[i]);
}

BOOST_CHECK(str2 == "eeee");
}

0 comments on commit 9c50e11

Please sign in to comment.