diff --git a/doc/algorithm.qbk b/doc/algorithm.qbk index becad46..8ce6685 100644 --- a/doc/algorithm.qbk +++ b/doc/algorithm.qbk @@ -68,6 +68,7 @@ Thanks to all the people who have reviewed this library and made suggestions for [include hex.qbk] [include is_palindrome.qbk] [include is_partitioned_until.qbk] +[include apply_permutation.qbk] [endsect] diff --git a/doc/apply_permutation.qbk b/doc/apply_permutation.qbk new file mode 100644 index 0000000..7f11457 --- /dev/null +++ b/doc/apply_permutation.qbk @@ -0,0 +1,96 @@ +[/ File apply_permutation.qbk] + +[section:apply_permutation apply_permutation] + +[/license +Copyright (c) 2017 Alexander Zaitsev + +Distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +The header file 'apply_permutation.hpp' contains two algorithms, apply_permutation and apply_reverse_permutation. Also there are range-based versions. +The algorithms transform the item sequence according to index sequence order. + +The routine `apply_permutation` takes a item sequence and a order sequence. It reshuffles item sequence according to order sequence. Every value in order sequence means where the item comes from. Order sequence needs to be exactly a permutation of the sequence [0, 1, ... , N], where N is the biggest index in the item sequence (zero-indexed). +The routine `apply_reverse_permutation` takes a item sequence and a order sequence. It will reshuffle item sequence according to order sequence. Every value in order sequence means where the item goes to. Order sequence needs to be exactly a permutation of the sequence [0, 1, ... , N], where N is the biggest index in the item sequence (zero-indexed). + +Implementations are based on these articles: +https://blogs.msdn.microsoft.com/oldnewthing/20170102-00/?p=95095 +https://blogs.msdn.microsoft.com/oldnewthing/20170103-00/?p=95105 +https://blogs.msdn.microsoft.com/oldnewthing/20170104-00/?p=95115 +https://blogs.msdn.microsoft.com/oldnewthing/20170109-00/?p=95145 +https://blogs.msdn.microsoft.com/oldnewthing/20170110-00/?p=95155 +https://blogs.msdn.microsoft.com/oldnewthing/20170111-00/?p=95165 + +The routines come in 2 forms; the first one takes two iterators to define the item range and one iterator to define the beginning of index range. The second form takes range to define the item sequence and range to define index sequence. + + +[heading interface] + +There are two versions of algorithms: +1) takes four iterators. +2) takes two ranges. +`` +template +void apply_permutation(RandomAccessIterator1 item_begin, RandomAccessIterator1 item_end, + RandomAccessIterator2 ind_begin, RandomAccessIterator2 ind_end); +template +void apply_permutation(Range1& item_range, Range2& ind_range); +template +void apply_reverse_permutation(RandomAccessIterator1 item_begin, RandomAccessIterator1 item_end, + RandomAccessIterator2 ind_begin, RandomAccessIterator2 ind_end); +template +void apply_reverse_permutation(Range1& item_range, Range2& ind_range); +`` + + +[heading Examples] + +Given the containers: +std::vector emp_vec, emp_order, +std::vector one{1}, one_order{0}, +std::vector two{1,2}, two_order{1,0}, +std::vector vec{1, 2, 3, 4, 5}, +std::vector order{4, 2, 3, 1, 0}, then +`` + +apply_permutation(emp_vec, emp_order)) --> no changes +apply_reverse_permutation(emp_vec, emp_order)) --> no changes +apply_permutation(one, one_order) --> no changes +apply_reverse_permutation(one, one_order) --> no changes +apply_permutation(two, two_order) --> two:{2,1} +apply_reverse_permutation(two, two_order) --> two:{2,1} +apply_permutation(vec, order) --> vec:{5, 3, 4, 2, 1} +apply_reverse_permutation(vec, order) --> vec:{5, 4, 2, 3, 1} +`` + +[heading Iterator Requirements] + +`apply_permutation` and 'apply_reverse_permutation' work only on RandomAccess iterators. RandomAccess iterators required both for item and index sequences. + +[heading Complexity] + +All of the variants of `apply_permutation` and `apply_reverse_permutation` run in ['O(N)] (linear) time. +More + +[heading Exception Safety] + +All of the variants of `apply_permutation` and `apply_reverse_permutation` take their parameters by iterators or reference, and do not depend upon any global state. Therefore, all the routines in this file provide the strong exception guarantee. + +[heading Notes] +* If ItemSequence and IndexSequence are not equal, behavior is undefined. + +* `apply_permutation` and `apply_reverse_permutation` work also on empty sequences. + +* Order sequence must be zero-indexed. + +* Order sequence gets permuted. + +[endsect] + +[/ File apply_permutation.qbk +Copyright 2017 Alexander Zaitsev +Distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt). +] diff --git a/example/Jamfile.v2 b/example/Jamfile.v2 index 4512a53..100878c 100644 --- a/example/Jamfile.v2 +++ b/example/Jamfile.v2 @@ -22,4 +22,4 @@ exe clamp_example : clamp_example.cpp ; exe search_example : search_example.cpp ; exe is_palindrome_example : is_palindrome_example.cpp; exe is_partitioned_until_example : is_partitioned_until_example.cpp; - +exe apply_permutation_example : apply_permutation_example.cpp; diff --git a/example/apply_permutation_example.cpp b/example/apply_permutation_example.cpp new file mode 100644 index 0000000..7ed91ae --- /dev/null +++ b/example/apply_permutation_example.cpp @@ -0,0 +1,69 @@ +/* + Copyright (c) Alexander Zaitsev , 2017 + + Distributed under the Boost Software License, Version 1.0. (See + accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) + + See http://www.boost.org/ for latest version. +*/ + +#include +#include + +#include + + +namespace ba = boost::algorithm; + +int main ( int /*argc*/, char * /*argv*/ [] ) +{ + // WARNING: Example require C++11 or newer compiler + { + std::cout << "apply_permutation with iterators:\n"; + std::vector vec{1, 2, 3, 4, 5}, order{4, 2, 3, 1, 0}; + + ba::apply_permutation(vec.begin(), vec.end(), order.begin(), order.end()); + for (const auto& x : vec) + { + std::cout << x << ", "; + } + std::cout << std::endl; + } + { + std::cout << "apply_reverse_permutation with iterators:\n"; + std::vector vec{1, 2, 3, 4, 5}, order{4, 2, 3, 1, 0}; + + ba::apply_reverse_permutation(vec.begin(), vec.end(), order.begin(), order.end()); + for (const auto& x : vec) + { + std::cout << x << ", "; + } + std::cout << std::endl; + } + { + std::cout << "apply_reverse_permutation with ranges:\n"; + std::vector vec{1, 2, 3, 4, 5}, order{4, 2, 3, 1, 0}; + + ba::apply_reverse_permutation(vec, order); + for (const auto& x : vec) + { + std::cout << x << ", "; + } + std::cout << std::endl; + } + { + std::cout << "apply_permutation with ranges:\n"; + std::vector vec{1, 2, 3, 4, 5}, order{4, 2, 3, 1, 0}; + + ba::apply_permutation(vec, order); + for (const auto& x : vec) + { + std::cout << x << ", "; + } + std::cout << std::endl; + } + + return 0; +} + diff --git a/include/boost/algorithm/apply_permutation.hpp b/include/boost/algorithm/apply_permutation.hpp new file mode 100644 index 0000000..c844cfc --- /dev/null +++ b/include/boost/algorithm/apply_permutation.hpp @@ -0,0 +1,125 @@ +/* + Copyright (c) Alexander Zaitsev , 2017 + + Distributed under the Boost Software License, Version 1.0. (See + accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) + + See http://www.boost.org/ for latest version. + + + Based on https://blogs.msdn.microsoft.com/oldnewthing/20170104-00/?p=95115 +*/ + +/// \file apply_permutation.hpp +/// \brief Apply permutation to a sequence. +/// \author Alexander Zaitsev + +#ifndef BOOST_ALGORITHM_APPLY_PERMUTATION_HPP +#define BOOST_ALGORITHM_APPLY_PERMUTATION_HPP + +#include +#include + +#include +#include + +namespace boost { namespace algorithm +{ + +/// \fn apply_permutation ( RandomAccessIterator1 item_begin, RandomAccessIterator1 item_end, RandomAccessIterator2 ind_begin ) +/// \brief Reorder item sequence with index sequence order +/// +/// \param item_begin The start of the item sequence +/// \param item_end One past the end of the item sequence +/// \param ind_begin The start of the index sequence. +/// +/// \note Item sequence size should be equal to index size. Otherwise behavior is undefined. +/// Complexity: O(N). +template +void +apply_permutation(RandomAccessIterator1 item_begin, RandomAccessIterator1 item_end, + RandomAccessIterator2 ind_begin, RandomAccessIterator2 ind_end) +{ + using Diff = typename std::iterator_traits::difference_type; + using std::swap; + Diff size = std::distance(item_begin, item_end); + for (Diff i = 0; i < size; i++) + { + auto current = i; + while (i != ind_begin[current]) + { + auto next = ind_begin[current]; + swap(item_begin[current], item_begin[next]); + ind_begin[current] = current; + current = next; + } + ind_begin[current] = current; + } +} + +/// \fn apply_reverse_permutation ( RandomAccessIterator1 item_begin, RandomAccessIterator1 item_end, RandomAccessIterator2 ind_begin ) +/// \brief Reorder item sequence with index sequence order +/// +/// \param item_begin The start of the item sequence +/// \param item_end One past the end of the item sequence +/// \param ind_begin The start of the index sequence. +/// +/// \note Item sequence size should be equal to index size. Otherwise behavior is undefined. +/// Complexity: O(N). +template +void +apply_reverse_permutation( + RandomAccessIterator1 item_begin, + RandomAccessIterator1 item_end, + RandomAccessIterator2 ind_begin, + RandomAccessIterator2 ind_end) +{ + using Diff = typename std::iterator_traits::difference_type; + using std::swap; + Diff length = std::distance(item_begin, item_end); + for (Diff i = 0; i < length; i++) + { + while (i != ind_begin[i]) + { + Diff next = ind_begin[i]; + swap(item_begin[i], item_begin[next]); + swap(ind_begin[i], ind_begin[next]); + } + } +} + +/// \fn apply_permutation ( Range1 item_range, Range2 ind_range ) +/// \brief Reorder item sequence with index sequence order +/// +/// \param item_range The item sequence +/// \param ind_range The index sequence +/// +/// \note Item sequence size should be equal to index size. Otherwise behavior is undefined. +/// Complexity: O(N). +template +void +apply_permutation(Range1& item_range, Range2& ind_range) +{ + apply_permutation(boost::begin(item_range), boost::end(item_range), + boost::begin(ind_range), boost::end(ind_range)); +} + +/// \fn apply_reverse_permutation ( Range1 item_range, Range2 ind_range ) +/// \brief Reorder item sequence with index sequence order +/// +/// \param item_range The item sequence +/// \param ind_range The index sequence +/// +/// \note Item sequence size should be equal to index size. Otherwise behavior is undefined. +/// Complexity: O(N). +template +void +apply_reverse_permutation(Range1& item_range, Range2& ind_range) +{ + apply_reverse_permutation(boost::begin(item_range), boost::end(item_range), + boost::begin(ind_range), boost::end(ind_range)); +} + +}} +#endif //BOOST_ALGORITHM_APPLY_PERMUTATION_HPP diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index e32bc53..813be2c 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -83,6 +83,9 @@ alias unit_test_framework # Is_partitioned_until tests [ run is_partitioned_until_test.cpp unit_test_framework : : : : is_partitioned_until_test ] + +# Apply_permutation tests + [ run apply_permutation_test.cpp unit_test_framework : : : : apply_permutation_test ] ; } diff --git a/test/apply_permutation_test.cpp b/test/apply_permutation_test.cpp new file mode 100644 index 0000000..e9ab970 --- /dev/null +++ b/test/apply_permutation_test.cpp @@ -0,0 +1,169 @@ +/* + Copyright (c) Alexander Zaitsev , 2017 + + Distributed under the Boost Software License, Version 1.0. (See + accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) + + See http://www.boost.org/ for latest version. +*/ + +#include + +#include + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN + +#include + +namespace ba = boost::algorithm; + + +void test_apply_permutation() +{ + //Empty + { + std::vector vec, order, result; + + ba::apply_permutation(vec.begin(), vec.end(), order.begin(), order.end()); + BOOST_CHECK(vec == result); + } + //1 element + { + std::vector vec, order, result; + vec.push_back(1); + order.push_back(0); + result = vec; + + ba::apply_permutation(vec.begin(), vec.end(), order.begin(), order.end()); + BOOST_CHECK(vec == result); + } + //2 elements, no changes + { + std::vector vec, order, result; + vec.push_back(1); vec.push_back(2); + order.push_back(0); order.push_back(1); + result = vec; + + ba::apply_permutation(vec.begin(), vec.end(), order.begin(), order.end()); + BOOST_CHECK(vec == result); + } + //2 elements, changed + { + std::vector vec, order, result; + vec.push_back(1); vec.push_back(2); + order.push_back(1); order.push_back(0); + result.push_back(2); result.push_back(1); + + ba::apply_permutation(vec.begin(), vec.end(), order.begin(), order.end()); + BOOST_CHECK(vec == result); + } + //Multiple elements, no changes + { + std::vector vec, order, result; + vec.push_back(1); vec.push_back(2); vec.push_back(3); vec.push_back(4); vec.push_back(5); + order.push_back(0); order.push_back(1); order.push_back(2); order.push_back(3); order.push_back(4); + result = vec; + + ba::apply_permutation(vec.begin(), vec.end(), order.begin(), order.end()); + BOOST_CHECK(vec == result); + } + //Multiple elements, changed + { + std::vector vec, order, result; + vec.push_back(1); vec.push_back(2); vec.push_back(3); vec.push_back(4); vec.push_back(5); + order.push_back(4); order.push_back(3); order.push_back(2); order.push_back(1); order.push_back(0); + result.push_back(5); result.push_back(4); result.push_back(3); result.push_back(2); result.push_back(1); + + ba::apply_permutation(vec.begin(), vec.end(), order.begin(), order.end()); + BOOST_CHECK(vec == result); + } + //Just test range interface + { + std::vector vec, order, result; + vec.push_back(1); vec.push_back(2); vec.push_back(3); vec.push_back(4); vec.push_back(5); + order.push_back(0); order.push_back(1); order.push_back(2); order.push_back(3); order.push_back(4); + result = vec; + + ba::apply_permutation(vec, order); + BOOST_CHECK(vec == result); + } +} + +void test_apply_reverse_permutation() +{ + //Empty + { + std::vector vec, order, result; + + ba::apply_reverse_permutation(vec.begin(), vec.end(), order.begin(), order.end()); + BOOST_CHECK(vec == result); + } + //1 element + { + std::vector vec, order, result; + vec.push_back(1); + order.push_back(0); + result = vec; + + ba::apply_reverse_permutation(vec.begin(), vec.end(), order.begin(), order.end()); + BOOST_CHECK(vec == result); + } + //2 elements, no changes + { + std::vector vec, order, result; + vec.push_back(1); vec.push_back(2); + order.push_back(0); order.push_back(1); + result = vec; + + ba::apply_reverse_permutation(vec.begin(), vec.end(), order.begin(), order.end()); + BOOST_CHECK(vec == result); + } + //2 elements, changed + { + std::vector vec, order, result; + vec.push_back(1); vec.push_back(2); + order.push_back(1); order.push_back(0); + result.push_back(2); result.push_back(1); + + ba::apply_reverse_permutation(vec.begin(), vec.end(), order.begin(), order.end()); + BOOST_CHECK(vec == result); + } + //Multiple elements, no changes + { + std::vector vec, order, result; + vec.push_back(1); vec.push_back(2); vec.push_back(3); vec.push_back(4); vec.push_back(5); + order.push_back(0); order.push_back(1); order.push_back(2); order.push_back(3); order.push_back(4); + result = vec; + + ba::apply_reverse_permutation(vec.begin(), vec.end(), order.begin(), order.end()); + BOOST_CHECK(vec == result); + } + //Multiple elements, changed + { + std::vector vec, order, result; + vec.push_back(1); vec.push_back(2); vec.push_back(3); vec.push_back(4); vec.push_back(5); + order.push_back(4); order.push_back(3); order.push_back(2); order.push_back(1); order.push_back(0); + result.push_back(5); result.push_back(4); result.push_back(3); result.push_back(2); result.push_back(1); + + ba::apply_reverse_permutation(vec.begin(), vec.end(), order.begin(), order.end()); + BOOST_CHECK(vec == result); + } + //Just test range interface + { + std::vector vec, order, result; + vec.push_back(1); vec.push_back(2); vec.push_back(3); vec.push_back(4); vec.push_back(5); + order.push_back(0); order.push_back(1); order.push_back(2); order.push_back(3); order.push_back(4); + result = vec; + + ba::apply_reverse_permutation(vec, order); + BOOST_CHECK(vec == result); + } +} + +BOOST_AUTO_TEST_CASE(test_main) +{ + test_apply_permutation(); + test_apply_reverse_permutation(); +}