diff --git a/CMakeLists.txt b/CMakeLists.txt
index 82d9ae9..cef10de 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -68,6 +68,7 @@ if(BUILD_TESTING AND ${KDALGORITHMS_BUILD_TEST})
src/kdalgorithms_bits/zip.h
src/kdalgorithms_bits/tuple_utils.h
src/kdalgorithms_bits/invoke.h
+ src/kdalgorithms_bits/cartesian_product.h
tests/tst_kdalgorithms.cpp
tests/tst_constraints.cpp
diff --git a/Documentation/algorithms.md b/Documentation/algorithms.md
index bc92ccb..dd7e0f8 100644
--- a/Documentation/algorithms.md
+++ b/Documentation/algorithms.md
@@ -41,6 +41,7 @@ Other
- partitioned
- multi_partitioned
- zip
+- product
@@ -953,3 +954,34 @@ auto result = kdalgorithms::zip(v1, v2);
```
See [boost::compine](https://www.boost.org/doc/libs/1_81_0/libs/range/doc/html/range/reference/utilities/combine.html) for similar algorithm in boost, and [std::ranges::views::zip](https://en.cppreference.com/w/cpp/ranges/zip_view) for the C++23 version.
+
+
+cartesian_product
+-----------------------------
+cartesian_product takes a number of containers and returns a cartesian product of the items.
+
+```
+const std::array x = {'A', 'B'};
+const std::vector y = {1, 2, 3};
+const std::list z = {"α", "β", "γ", "δ"};
+
+auto result = kdalgorithms::product(x, y, z);
+// result is:
+// std::vector>{
+// {'A', 1, "α"}, {'A', 1, "β"}, {'A', 1, "γ"}, {'A', 1, "δ"},
+// {'A', 2, "α"}, {'A', 2, "β"}, {'A', 2, "γ"}, {'A', 2, "δ"},
+// {'A', 3, "α"}, {'A', 3, "β"}, {'A', 3, "γ"}, {'A', 3, "δ"},
+// {'B', 1, "α"}, {'B', 1, "β"}, {'B', 1, "γ"}, {'B', 1, "δ"},
+// {'B', 2, "α"}, {'B', 2, "β"}, {'B', 2, "γ"}, {'B', 2, "δ"},
+// {'B', 3, "α"}, {'B', 3, "β"}, {'B', 3, "γ"}, {'B', 3, "δ"},
+// };
+```
+
+It is also possible to specify the return type:
+```
+auto result = kdalgorithms::cartesian_product( x, y, z );
+// result is:
+// std::deque>{ ... }
+```
+
+See [std::cartesian_product](https://en.cppreference.com/w/cpp/ranges/cartesian_product_view)
diff --git a/src/kdalgorithms.h b/src/kdalgorithms.h
index 697eb45..24ff7d3 100644
--- a/src/kdalgorithms.h
+++ b/src/kdalgorithms.h
@@ -10,6 +10,7 @@
#pragma once
+#include "kdalgorithms_bits/cartesian_product.h"
#include "kdalgorithms_bits/filter.h"
#include "kdalgorithms_bits/find_if.h"
#include "kdalgorithms_bits/generate.h"
diff --git a/src/kdalgorithms_bits/cartesian_product.h b/src/kdalgorithms_bits/cartesian_product.h
new file mode 100644
index 0000000..725730e
--- /dev/null
+++ b/src/kdalgorithms_bits/cartesian_product.h
@@ -0,0 +1,51 @@
+/****************************************************************************
+**
+** This file is part of KDAlgorithms
+**
+** SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company
+**
+** SPDX-License-Identifier: MIT
+**
+****************************************************************************/
+
+#pragma once
+
+#include "transform.h"
+#include "tuple_utils.h"
+#include
+#include
+
+namespace kdalgorithms {
+namespace detail {
+ template class ResultContainerClass, typename Container>
+ auto cartesian_product(Container &&arg)
+ {
+ return kdalgorithms::transformed(
+ std::forward(arg), [](auto &&elm) { return std::make_tuple(elm); });
+ }
+
+ template class ResultContainerClass, typename Container,
+ typename... REST>
+ auto cartesian_product(Container &&container, REST... args)
+ {
+ auto partialResult = cartesian_product(args...);
+
+ using TupleType =
+ detail::merge_tuple_types_t, ValueType>;
+ ResultContainerClass result;
+ for (auto &&item : container) {
+ for (auto &&partialItem : partialResult) {
+ result.push_back(std::tuple_cat(std::make_tuple(item), partialItem));
+ }
+ }
+ return result;
+ }
+} // namespace detail
+
+template class ResultContainerClass = std::vector, typename... ARGS>
+auto cartesian_product(ARGS... args)
+{
+ return detail::cartesian_product(std::forward(args)...);
+}
+
+} // namespace kdalgorithms
diff --git a/src/kdalgorithms_bits/transform.h b/src/kdalgorithms_bits/transform.h
index a3519d0..1f1cfb3 100644
--- a/src/kdalgorithms_bits/transform.h
+++ b/src/kdalgorithms_bits/transform.h
@@ -11,6 +11,7 @@
#pragma once
#include "insert_wrapper.h"
+#include "read_iterator_wrapper.h"
#include "reserve_helper.h"
#include "shared.h"
#include "to_function_object.h"
diff --git a/src/kdalgorithms_bits/tuple_utils.h b/src/kdalgorithms_bits/tuple_utils.h
index 41fb880..9ffebb3 100644
--- a/src/kdalgorithms_bits/tuple_utils.h
+++ b/src/kdalgorithms_bits/tuple_utils.h
@@ -40,5 +40,16 @@ namespace detail {
fn);
}
+ template
+ struct merge_tuple_types;
+
+ template
+ struct merge_tuple_types>
+ {
+ using type = std::tuple;
+ };
+
+ template
+ using merge_tuple_types_t = typename merge_tuple_types::type;
} // namespace detail
} // namespace kdalgorithms
diff --git a/tests/tst_kdalgorithms.cpp b/tests/tst_kdalgorithms.cpp
index 276a0ba..31a8a59 100644
--- a/tests/tst_kdalgorithms.cpp
+++ b/tests/tst_kdalgorithms.cpp
@@ -15,6 +15,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -182,6 +183,7 @@ private Q_SLOTS:
void multi_partitioned();
void multi_partitioned_with_function_taking_a_value();
void sub_range();
+ void product();
};
void TestAlgorithms::copy()
@@ -2929,6 +2931,68 @@ void TestAlgorithms::sub_range()
#endif
}
+void TestAlgorithms::product()
+{
+ { // Base case
+ std::vector v{1, 2, 3};
+ std::vector> expected{{1}, {2}, {3}};
+ auto result = kdalgorithms::cartesian_product(v);
+ QCOMPARE(result, expected);
+ }
+
+ { // Two lists
+ std::vector v1{1, 2};
+ std::vector v2{3, 4};
+ std::vector> expected{{1, 3}, {1, 4}, {2, 3}, {2, 4}};
+ auto result = kdalgorithms::cartesian_product(v1, v2);
+ QCOMPARE(result, expected);
+ }
+
+ { // Three lists
+ std::vector v1{1, 2};
+ std::vector v2{3, 4};
+ std::vector v3{5, 6, 7};
+ std::vector> expected{{1, 3, 5}, {1, 3, 6}, {1, 3, 7}, {1, 4, 5},
+ {1, 4, 6}, {1, 4, 7}, {2, 3, 5}, {2, 3, 6},
+ {2, 3, 7}, {2, 4, 5}, {2, 4, 6}, {2, 4, 7}};
+ auto result = kdalgorithms::cartesian_product(v1, v2, v3);
+ QCOMPARE(result, expected);
+ }
+
+ { // Different types
+ std::vector v1{1, 2};
+ std::vector v2{true, false};
+ std::vector> expected{{1, true}, {1, false}, {2, true}, {2, false}};
+ auto result = kdalgorithms::cartesian_product(v1, v2);
+ QCOMPARE(result, expected);
+ }
+
+ { // Different container types
+ // Example from https://en.cppreference.com/w/cpp/ranges/cartesian_product_view
+ const std::array x = {'A', 'B'};
+ const std::vector y = {1, 2, 3};
+ const std::list z = {"α", "β", "γ", "δ"};
+
+ const auto expected = std::vector>{
+ {'A', 1, "α"}, {'A', 1, "β"}, {'A', 1, "γ"}, {'A', 1, "δ"}, {'A', 2, "α"},
+ {'A', 2, "β"}, {'A', 2, "γ"}, {'A', 2, "δ"}, {'A', 3, "α"}, {'A', 3, "β"},
+ {'A', 3, "γ"}, {'A', 3, "δ"}, {'B', 1, "α"}, {'B', 1, "β"}, {'B', 1, "γ"},
+ {'B', 1, "δ"}, {'B', 2, "α"}, {'B', 2, "β"}, {'B', 2, "γ"}, {'B', 2, "δ"},
+ {'B', 3, "α"}, {'B', 3, "β"}, {'B', 3, "γ"}, {'B', 3, "δ"},
+ };
+ auto result = kdalgorithms::cartesian_product(x, y, z);
+ QCOMPARE(result, expected);
+ }
+
+ { // Result Type given
+ std::vector v1{1, 2};
+ std::list v2{true, false};
+ std::deque> expected{{1, true}, {1, false}, {2, true}, {2, false}};
+ auto result = kdalgorithms::cartesian_product(v1, v2);
+ QCOMPARE(result, expected);
+ }
+}
+
QTEST_MAIN(TestAlgorithms)
#include "tst_kdalgorithms.moc"