diff options
| author | panesher <[email protected]> | 2026-02-05 11:45:44 +0300 |
|---|---|---|
| committer | panesher <[email protected]> | 2026-02-05 12:20:53 +0300 |
| commit | 626d15281a019d018dbb212dedefbc06e78a9edb (patch) | |
| tree | d999a7091919b66584d2ee1b291403478db266e3 /library/cpp | |
| parent | 87348551fa8e5783526db8db2fee2d4d1e6e5c69 (diff) | |
YT-27244: range helpers move to library
commit_hash:f257ebdacfbf0549a0f55cc37df2c059629bac3a
Diffstat (limited to 'library/cpp')
| -rw-r--r-- | library/cpp/yt/misc/range_formatters-inl.h | 31 | ||||
| -rw-r--r-- | library/cpp/yt/misc/range_formatters.h | 27 | ||||
| -rw-r--r-- | library/cpp/yt/misc/range_helpers-inl.h | 165 | ||||
| -rw-r--r-- | library/cpp/yt/misc/range_helpers.h | 70 | ||||
| -rw-r--r-- | library/cpp/yt/misc/unittests/range_helpers_ut.cpp | 127 | ||||
| -rw-r--r-- | library/cpp/yt/misc/unittests/ya.make | 1 |
6 files changed, 421 insertions, 0 deletions
diff --git a/library/cpp/yt/misc/range_formatters-inl.h b/library/cpp/yt/misc/range_formatters-inl.h new file mode 100644 index 00000000000..b1e1d1cd78f --- /dev/null +++ b/library/cpp/yt/misc/range_formatters-inl.h @@ -0,0 +1,31 @@ +#ifndef RANGE_FORMATTERS_INL_H_ +#error "Direct inclusion of this file is not allowed, include range_formatters.h" +// For the sake of sane code completion. +#include "range_formatters.h" +#endif + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +void FormatValue(TStringBuilderBase* builder, const TRange<T>& collection, TStringBuf /*spec*/) +{ + NYT::FormatRange(builder, collection, TDefaultFormatter()); +} + +template <class T> +void FormatValue(TStringBuilderBase* builder, const TSharedRange<T>& collection, TStringBuf /*spec*/) +{ + NYT::FormatRange(builder, collection, TDefaultFormatter()); +} + +template <std::ranges::view T> +void FormatValue(TStringBuilderBase* builder, const T& collection, TStringBuf /*spec*/) +{ + NYT::FormatRange(builder, collection, TDefaultFormatter()); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/misc/range_formatters.h b/library/cpp/yt/misc/range_formatters.h new file mode 100644 index 00000000000..3ebeff17061 --- /dev/null +++ b/library/cpp/yt/misc/range_formatters.h @@ -0,0 +1,27 @@ +#pragma once + +#include <library/cpp/yt/string/format.h> + +#include <library/cpp/yt/memory/range.h> +#include <library/cpp/yt/memory/shared_range.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +void FormatValue(TStringBuilderBase* builder, const TRange<T>& collection, TStringBuf /*spec*/); + +template <class T> +void FormatValue(TStringBuilderBase* builder, const TSharedRange<T>& collection, TStringBuf /*spec*/); + +template <std::ranges::view T> +void FormatValue(TStringBuilderBase* builder, const T& collection, TStringBuf /*spec*/); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define RANGE_FORMATTERS_INL_H_ +#include "range_formatters-inl.h" +#undef RANGE_FORMATTERS_INL_H_ diff --git a/library/cpp/yt/misc/range_helpers-inl.h b/library/cpp/yt/misc/range_helpers-inl.h new file mode 100644 index 00000000000..52649602903 --- /dev/null +++ b/library/cpp/yt/misc/range_helpers-inl.h @@ -0,0 +1,165 @@ +#ifndef RANGE_HELPERS_INL_H_ +#error "Direct inclusion of this file is not allowed, include range_helpers.h" +// For the sake of sane code completion. +#include "range_helpers.h" +#endif + +namespace NYT { +namespace NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +template <class TContainer> +struct TAppendTo +{ }; + +template <class TContainer> + requires requires (TContainer container, typename TContainer::value_type value) { + container.push_back(value); + } +struct TAppendTo<TContainer> +{ + template <class TValue> + static void Append(TContainer& container, TValue&& value) + { + container.push_back(std::forward<TValue>(value)); + } +}; + +template <class TContainer> + requires requires (TContainer container, typename TContainer::value_type value) { + container.insert(value); + } +struct TAppendTo<TContainer> +{ + template <class TValue> + static void Append(TContainer& container, TValue&& value) + { + container.insert(std::forward<TValue>(value)); + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +template <class TContainer> +struct TRangeTo +{ }; + +template <class TContainer> + requires requires (TContainer container, typename TContainer::value_type value) { + TAppendTo<TContainer>::Append(container, value); + } +struct TRangeTo<TContainer> +{ + template <std::ranges::input_range TRange> + static auto ToContainer(TRange&& range) + { + TContainer container; + if constexpr (requires { std::ranges::size(range); } && + requires { container.reserve(std::declval<size_t>()); }) + { + container.reserve(std::ranges::size(range)); + } + + for (auto&& element : range) { + TAppendTo<TContainer>::Append(container, std::forward<decltype(element)>(element)); + } + + return container; + } + + template <class... TValues> + static auto StaticRangeToContainer(TValues... values) + { + TContainer container; + if constexpr (requires { container.reserve(std::declval<size_t>()); }) + { + container.reserve(sizeof...(TValues)); + } + + (TAppendTo<TContainer>::Append(container, std::forward<TValues>(values)), ...); + return container; + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NDetail + +//////////////////////////////////////////////////////////////////////////////// + +template <std::ranges::range... TContainers> +auto ZipMutable(TContainers&&... containers) { + return Zip(std::ranges::views::transform(containers, [] <class T> (T&& t) { + return &t; + })...); +} + +template <class TContainer, std::ranges::input_range TRange> +auto RangeTo(TRange&& range) +{ + return NDetail::TRangeTo<TContainer>::template ToContainer<TRange>(std::forward<TRange>(range)); +} + +template <class TContainer> +constexpr auto RangeTo() +{ + return NDetail::TRangeToTag<TContainer>(); +} + +template<std::ranges::input_range TRange, class TContainer> +auto operator|(TRange&& range, NDetail::TRangeToTag<TContainer>) +{ + return RangeTo<TContainer>(std::forward<TRange>(range)); +} + +template <class TContainer, std::ranges::input_range TRange, class TTransformFunction> +auto TransformRangeTo(TRange&& range, TTransformFunction&& function) +{ + return RangeTo<TContainer>(std::ranges::views::transform( + std::forward<TRange>(range), + std::forward<TTransformFunction>(function))); +} + +template <class TContainer, class... TValues> + requires (std::constructible_from<typename TContainer::value_type, TValues> && ...) +TContainer StaticRangeTo(TValues... values) +{ + return NDetail::TRangeTo<TContainer>::template StaticRangeToContainer<TValues...>(std::forward<TValues>(values)...); +} + +template <class... TValues> +struct TStaticRange +{ +public: + explicit TStaticRange(TValues... values) + : Tuple_(std::forward<TValues>(values)...) + { } + + template <class TContainer> + operator TContainer() && + { + return std::apply(&StaticRangeTo<TContainer, TValues...>, std::move(Tuple_)); + } + +private: + std::tuple<TValues...> Tuple_; +}; + +template <std::ranges::range TRange, class TOperation, class TProjection> +auto FoldRange(TRange&& range, TOperation operation, TProjection projection) +{ + auto iter = range.begin(); + if (iter == range.end()) { + return std::remove_cvref_t<decltype(std::invoke(projection, *iter))>{}; + } + auto accumulator = std::invoke(projection, *iter); + for (++iter; iter != range.end(); ++iter) { + accumulator = std::invoke(operation, accumulator, std::invoke(projection, *iter)); + } + return accumulator; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/misc/range_helpers.h b/library/cpp/yt/misc/range_helpers.h new file mode 100644 index 00000000000..bab26b93824 --- /dev/null +++ b/library/cpp/yt/misc/range_helpers.h @@ -0,0 +1,70 @@ +#pragma once + +#include <library/cpp/iterator/zip.h> + +#include <ranges> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +template <class TContainer> +struct TRangeToTag +{ }; + +} // namespace NDetail + +//////////////////////////////////////////////////////////////////////////////// + +//! An equivalent of Python's `zip()`, but resulting range consists of tuples +//! of pointers and has length equal to the length of the shortest container. +//! Implementation with mutable references depends on "lifetime extension in +//! range-based for loops" from C++23. +template <std::ranges::range... TRanges> +auto ZipMutable(TRanges&&... ranges); + +//! Converts the provided range to the specified container. +//! This is a simplified equivalent of std::ranges::to from ranges-v3. +template <class TContainer, std::ranges::input_range TRange> +auto RangeTo(TRange&& range); + +//! Range to for monadic operations +template <class TContainer> +constexpr auto RangeTo(); + +//! Monadic operations to use RangeTo. Example: +//! auto filteredHashSet = vec | std::views::filter(pred) | RangeTo<THashSet<int>>(); +template<std::ranges::input_range TRange, class TContainer> +auto operator|(TRange&& range, NDetail::TRangeToTag<TContainer>); + +//! Converts a parameter pack into the specified container. +//! Useful for constructing containers of move-only types. +//! Note that `std::vector<TMoveOnly>{std::move(a), std::move(b)}` +//! will not compile since std::initializer_list has only const iterators. +//! However, `StaticRangeTo<std::vector<TMoveOnly>>(std::move(a), std::move(b))` will work. +template <class TContainer, class... TValues> + requires (std::constructible_from<typename TContainer::value_type, TValues> && ...) +TContainer StaticRangeTo(TValues... values); + +//! A tuple wrapper with implicit casts to containers via `StaticRangeTo`. +//! Useful for container list-initialization e.g. `std::vector<TMoveOnly> foo = TStaticRange{std::move(bar)};`. +template <class... TValues> +struct TStaticRange; + +//! Shortcut for `RangeTo(std::ranges::views::transform)`. +template <class TContainer, std::ranges::input_range TRange, class TTransformFunction> +auto TransformRangeTo(TRange&& range, TTransformFunction&& function); + +//! An equivalent of std::ranges::fold_left from ranges-v3. +template <std::ranges::range TRange, class TOperation, class TProjection = std::identity> +auto FoldRange(TRange&& range, TOperation operation, TProjection projection = {}); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define RANGE_HELPERS_INL_H_ +#include "range_helpers-inl.h" +#undef RANGE_HELPERS_INL_H_ diff --git a/library/cpp/yt/misc/unittests/range_helpers_ut.cpp b/library/cpp/yt/misc/unittests/range_helpers_ut.cpp new file mode 100644 index 00000000000..b69c8f58d44 --- /dev/null +++ b/library/cpp/yt/misc/unittests/range_helpers_ut.cpp @@ -0,0 +1,127 @@ +#include <library/cpp/yt/misc/range_helpers.h> + +#include <library/cpp/testing/gtest/gtest.h> + +#include <list> + +namespace NYT { +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TRangeHelpersTest, ZipMutable) +{ + std::vector<int> vectorA(4); + std::vector<int> vectorB = {1, 2, 3}; + for (auto [a, b] : ZipMutable(vectorA, vectorB)) { + *a = *b + 1; + } + + auto expectedA = std::vector<int>{2, 3, 4, 0}; + EXPECT_EQ(expectedA, vectorA); +} + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TRangeHelpersTest, RangeToVector) +{ + auto data = std::vector<std::string>{"A", "B", "C", "D"}; + auto range = std::ranges::views::transform(data, [] (std::string x) { + return "_" + x; + }); + + std::initializer_list<std::string> expectedValues{"_A", "_B", "_C", "_D"}; + EXPECT_EQ(std::vector<std::string>(expectedValues), RangeTo<std::vector<std::string>>(range)); + using TListStrings = std::list<std::string>; + EXPECT_EQ(TListStrings(expectedValues), RangeTo<TListStrings>(range)); +} + +TEST(TRangeHelpersTest, RangeToString) +{ + auto data = "_sample_"sv; + auto range = std::ranges::views::filter(data, [] (char x) { + return x != '_'; + }); + auto expectedData = "sample"sv; + + EXPECT_EQ(std::string(expectedData), RangeTo<std::string>(range)); + EXPECT_EQ(TString(expectedData), RangeTo<TString>(range)); +} + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TRangeHelpersTest, MonadicRangeToVector) +{ + auto data = std::vector<std::string>{"A", "B", "C", "D"}; + auto range = std::ranges::views::transform(data, [] (std::string x) { + return "_" + x; + }); + + std::initializer_list<std::string> expectedValues{"_A", "_B", "_C", "_D"}; + EXPECT_EQ(std::vector<std::string>(expectedValues), range | RangeTo<std::vector<std::string>>()); + using TListStrings = std::list<std::string>; + EXPECT_EQ(TListStrings(expectedValues), range | RangeTo<TListStrings>()); +} + +TEST(TRangeHelpersTest, MonadicRangeToString) +{ + auto data = "_sample_"sv; + auto range = std::ranges::views::filter(data, [] (char x) { + return x != '_'; + }); + auto expectedData = "sample"sv; + + EXPECT_EQ(std::string(expectedData), range | RangeTo<std::string>()); + EXPECT_EQ(TString(expectedData), range | RangeTo<TString>()); +} + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TRangeHelpersTest, Fold) +{ + EXPECT_EQ(0, FoldRange(std::vector<int>{}, std::plus{})); + EXPECT_EQ(6, FoldRange(std::vector<int>{1, 2, 3}, std::plus{})); + EXPECT_EQ(5, FoldRange( + std::vector<std::vector<int>>{{1, 2}, {3, 4, 5}}, + std::plus{}, + std::ranges::ssize)); +} + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TRangeHelpersTest, StaticRangeToVector) +{ + EXPECT_EQ(StaticRangeTo<std::vector<int>>(1), std::vector<int>{1}); + auto expected = std::vector<int>{1, 2, 10}; + auto result = StaticRangeTo<std::vector<int>>(1, 2, 10); + EXPECT_EQ(result, expected); +} + +TEST(TRangeHelpersTest, StaticRangeToVectorMoveOnly) +{ + auto result = StaticRangeTo<std::vector<std::unique_ptr<int>>>(std::make_unique<int>(1), std::make_unique<int>(2)); + ASSERT_EQ(std::ssize(result), 2); + EXPECT_EQ(*result[0], 1); + EXPECT_EQ(*result[1], 2); +} + +TEST(TRangeHelpersTest, TStaticRangeToVector) +{ + EXPECT_EQ(static_cast<std::vector<int>>(TStaticRange{1}), std::vector<int>{1}); + auto expected = std::vector<int>{1, 2, 10}; + std::vector<int> result = TStaticRange{1, 2, 10}; + EXPECT_EQ(result, expected); +} + +TEST(TRangeHelpersTest, TStaticRangeToVectorMoveOnly) +{ + std::vector<std::unique_ptr<int>> result = TStaticRange(std::make_unique<int>(1), std::make_unique<int>(2)); + ASSERT_EQ(std::ssize(result), 2); + EXPECT_EQ(*result[0], 1); + EXPECT_EQ(*result[1], 2); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT diff --git a/library/cpp/yt/misc/unittests/ya.make b/library/cpp/yt/misc/unittests/ya.make index ba7525f66a4..4b42078307f 100644 --- a/library/cpp/yt/misc/unittests/ya.make +++ b/library/cpp/yt/misc/unittests/ya.make @@ -9,6 +9,7 @@ SRCS( guid_ut.cpp hash_ut.cpp preprocessor_ut.cpp + range_helpers_ut.cpp strong_typedef_ut.cpp tag_invoke_cpo_ut.cpp tag_invoke_impl_ut.cpp |
