Skip to content

Commit

Permalink
[FEATURE] Add pseudo_random_access view.
Browse files Browse the repository at this point in the history
  • Loading branch information
rrahn committed Sep 17, 2019
1 parent e87f395 commit 18e84d8
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 0 deletions.
1 change: 1 addition & 0 deletions include/seqan3/range/views/all.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <seqan3/range/views/istreambuf.hpp>
#include <seqan3/range/views/pairwise_combine.hpp>
#include <seqan3/range/views/persist.hpp>
#include <seqan3/range/views/pseudo_random_access.hpp>
#include <seqan3/range/views/rank_to.hpp>
#include <seqan3/range/views/single_pass_input.hpp>
#include <seqan3/range/views/take.hpp>
Expand Down
199 changes: 199 additions & 0 deletions include/seqan3/range/views/pseudo_random_access.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// -----------------------------------------------------------------------------------------------------
// Copyright (c) 2006-2019, Knut Reinert & Freie Universität Berlin
// Copyright (c) 2016-2019, Knut Reinert & MPI für molekulare Genetik
// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License
// shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md
// -----------------------------------------------------------------------------------------------------

/*!\file
* \brief Provides seqan3::views::pseudo_random_access.
* \author Rene Rahn <rene.rahn AT fu-berlin.de>
*/

#pragma once

#include <seqan3/range/views/detail.hpp>
#include <seqan3/std/iterator>
#include <seqan3/std/ranges>

namespace seqan3::detail
{

/*!\interface seqan3::detail::pseudo_random_access_range <>
* \extends std::ranges::range
* \brief This concept checks if a type models a random access range via `begin_ra` and `end_ra` member
* functions, albeit constant access time might not be guaranteed.
* \ingroup range
*
* \details
*
* This concept describes the requirements a range must fulfil in order to be used as a random access range.
*
* ### Concepts and doxygen
*
* The requirements for this concept are given as related functions and type traits.
* Types that model this concept are shown as "implementing this interface".
*/
/*!\name Requirements for seqan3::detail::pseudo_random_access_range
* \brief You can expect these functions on all types that model seqan3::pseudo_random_access_range.
* \relates seqan3::detail::pseudo_random_access_range
* \{
*/
/*!\fn pseudo_random_access_range::begin_ra()
* \brief Returns a pseudo random access iterator.
*
* \details
*
* The returned iterator must model the std::random_access_iterator, although accessing elements through this iterator
* might not be constant.
*
* \attention This is a concept requirement, not an actual function (however types
* modelling this concept will provide an implementation).
*/
/*!\fn pseudo_random_access_range::end_ra()
* \brief Returns a pseudo random access sentinel.
*
* \details
*
* The returned sentinel must model std::sentinel_for with the corresponding iterator obtained by begin_ra().
*
* \attention This is a concept requirement, not an actual function (however types
* modelling this concept will provide an implementation).
*/
//!\cond
template <typename rng_t>
SEQAN3_CONCEPT pseudo_random_access_range = std::ranges::range<rng_t> && requires (rng_t rng)
{
rng.begin_ra();
rng.end_ra();

requires std::random_access_iterator<decltype(rng.begin_ra())>;
requires std::sentinel_for<decltype(rng.begin_ra()), decltype(rng.end_ra())>;
};
//!\endcond

// ============================================================================
// pseudo_random_access_fn (adaptor definition)
// ============================================================================

//!\brief View adaptor definition for seqan3::views::pseudo_random_access.
struct pseudo_random_access_fn : public adaptor_base<pseudo_random_access_fn>
{
private:
//!\brief Type of the CRTP-base.
using base_t = adaptor_base<pseudo_random_access_fn>;

public:
//!\brief Inherit the base class's Constructors.
using base_t::base_t;

private:
//!\brief Befriend the base class so it can call impl().
friend base_t;

/*!\brief Call the view's constructor with the underlying view as argument.
* \returns An instance of the adapted range.
*/
template <std::ranges::viewable_range urng_t>
static constexpr auto impl(urng_t && urange)
{
static_assert(std::ranges::random_access_range<urng_t> || pseudo_random_access_range<urng_t>,
"The adapted range must either model std::ranges::random_access_range or must be "
"a specific SeqAn range type that supports pseudo random access.");

if constexpr (std::ranges::random_access_range<urng_t>)
{ // Nothing to do, just return as ref_view or original view.
return std::views::all(std::forward<urng_t>(urange));
}
else
{ // Get a subrange using the random access iterators of the container.
using iterator_ra_t = decltype(std::declval<urng_t>().begin_ra());
using sentinel_ra_t = decltype(std::declval<urng_t>().end_ra());

return std::ranges::subrange<iterator_ra_t, sentinel_ra_t>{urange.begin_ra(), urange.end_ra()};
}
}
};

} // namespace seqan3::detail

namespace seqan3::views
{

/*!\name General purpose views
* \{
*/

/*!\brief A view adaptor that converts a pseudo random access range to a std::random_access_range.
* \tparam urng_t The type of the range being processed. See below for requirements. [template parameter is
* omitted in pipe notation]
* \param[in] urange The range being processed. [parameter is omitted in pipe notation]
* \returns A std::ranges::random_access_range over the given range.
* \ingroup views
*
* \details
*
* **Header**
* ```cpp
* #include <seqan3/range/views/pseudo_random_access.hpp>
* ```
*
* A pseudo random access range is a range whose iterator typically defines all the interfaces necessary to allow
* random access, but cannot guarantee accessing an arbitrary element in constant time.
* Thus, the highest category it can support by default is std::ranges::bidirectional_range. However, for many of these
* pseudo random access ranges better algorithms and data structures with sub-linear runtime complexities can be used
* (for example logarithmic time complexity). To enforce the faster behaviour of the range in a generic
* range-based context you can use this range adaptor, which will return a range that models
* std::ranges::random_access_range. Note, that this does not mean that the complexity of accessing an arbitrary element
* of the adapted range improves to constant time, but merely all syntactical requirements are fulfilled including the
* iterator tag.
*
* ### View properties
*
* | range concepts and reference_t | `urng_t` (underlying range type) | `rrng_t` (returned range type) |
* |----------------------------------|:---------------------------------:|:------------------------------------------:|
* | std::ranges::input_range | *required* | *guaranteed* |
* | std::ranges::forward_range | | *guaranteed* |
* | std::ranges::bidirectional_range | | *guaranteed* |
* | std::ranges::random_access_range | | *guaranteed* |
* | std::ranges::contiguous_range | | *preserved* |
* | | | |
* | std::ranges::viewable_range | *required* | *guaranteed* |
* | std::ranges::view | | *guaranteed* |
* | std::ranges::sized_range | | *preserved* |
* | std::ranges::common_range | | *preserved* |
* | std::ranges::output_range | | *preserved* |
* | seqan3::const_iterable_range | | *preserved* |
* | | | |
* | seqan3::reference_t | | seqan3::reference_t<urng_t> |
*
* See the \link views views submodule documentation \endlink for detailed descriptions of the view properties.
*
* This adaptor requires that the underlying range models either std::ranges::random_access_range or is one of the
* following range types:
*
* * seqan3::gap_decorator.
*
* ### Return type
*
* | `urng_t` (underlying range type) | `rrng_t` (returned range type) |
* |:--------------------------------------:|:----------------------------------------------------:|
* | `std::ranges::random_access_range` | `std::ranges::ref_view<urng_t>` |
* | `seqan3::gap_decorator` | `std::ranges::subrange<ra_iterator_t, ra_iterator_t>`|
*
* The adaptor returns exactly the type specified above. In the second case `ra_iterator_t` refers to
* seqan3::gap_decorator::gap_decorator_iterator_ra.
*
* ### Example
*
* \include test/snippet/range/view/pseudo_random_access.cpp
*
* ### Complexity
*
* Construction of the returned view is in \f$ O(1) \f$.
*
* \hideinitializer
*/
inline constexpr auto pseudo_random_access = detail::pseudo_random_access_fn{};
//!\}
} // namespace seqan3::views
27 changes: 27 additions & 0 deletions test/snippet/range/view/pseudo_random_access.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include <string>

#include <seqan3/alphabet/nucleotide/dna4.hpp>
#include <seqan3/core/debug_stream.hpp>
#include <seqan3/range/decorator/gap_decorator.hpp>
#include <seqan3/range/views/pseudo_random_access.hpp>

int main()
{
using seqan3::operator""_dna4;

// A gap decorator is a pseudo random access range using logarithmic time complexity internally.
auto seq = "ACGTACGACT"_dna4;
seqan3::gap_decorator dec{seq};

// The default interface models at most std::bidirectional_range.
auto it = std::ranges::begin(dec); // Returned iterator models std::bidirectional_iterator.
std::ranges::advance(it, 3); // Advancing the iterator takes linear time.
seqan3::debug_stream << *it << '\n'; // "T"

// After adapting it with pseudo_random_access, the returned range models std::random_access_range.
auto dec_ra = dec | seqan3::views::pseudo_random_access;
auto it_ra = std::ranges::begin(dec_ra); // Returned iterator models std::random_access_iterator.
std::ranges::advance(it_ra, 3); // Advancing the iterator takes now logarithmic time.
seqan3::debug_stream << *it_ra << '\n'; // "T"

}
1 change: 1 addition & 0 deletions test/unit/range/views/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ seqan3_test(view_pairwise_combine_test.cpp)
seqan3_test(view_drop_test.cpp)
seqan3_test(view_istreambuf_test.cpp)
seqan3_test(view_persist_test.cpp)
seqan3_test(view_pseudo_random_access_test.cpp)
seqan3_test(view_rank_to_test.cpp)
seqan3_test(view_repeat_n_test.cpp)
seqan3_test(view_repeat_test.cpp)
Expand Down
62 changes: 62 additions & 0 deletions test/unit/range/views/view_pseudo_random_access_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// -----------------------------------------------------------------------------------------------------
// Copyright (c) 2006-2019, Knut Reinert & Freie Universität Berlin
// Copyright (c) 2016-2019, Knut Reinert & MPI für molekulare Genetik
// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License
// shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md
// -----------------------------------------------------------------------------------------------------

#include <gtest/gtest.h>

#include <string>
#include <vector>

#include <seqan3/alphabet/nucleotide/dna4.hpp>
#include <seqan3/range/decorator/gap_decorator.hpp>
#include <seqan3/range/views/pseudo_random_access.hpp>
#include <seqan3/range/views/to_char.hpp>
#include <seqan3/std/ranges>

using namespace seqan3;

template <typename t>
class pseudo_random_access_test : public ::testing::Test
{};

using testing_types = ::testing::Types<std::vector<dna4>, gap_decorator<std::vector<dna4> const &>>;

TYPED_TEST_CASE(pseudo_random_access_test, testing_types);

TYPED_TEST(pseudo_random_access_test, concepts)
{
using test_type = decltype(std::declval<TypeParam &>() | views::pseudo_random_access);

// guaranteed concepts
EXPECT_TRUE(std::ranges::random_access_range<test_type>);
EXPECT_TRUE(std::ranges::view<test_type>);
EXPECT_TRUE(std::ranges::viewable_range<test_type>);

// preserved concepts
EXPECT_EQ(std::ranges::sized_range<TypeParam>, std::ranges::sized_range<test_type>);
EXPECT_EQ(std::ranges::common_range<TypeParam>, std::ranges::common_range<test_type>);
EXPECT_EQ(std::ranges::contiguous_range<TypeParam>, std::ranges::contiguous_range<test_type>);
EXPECT_EQ(const_iterable_range<TypeParam>, const_iterable_range<test_type>);
EXPECT_EQ((std::ranges::output_range<TypeParam, dna4>), (std::ranges::output_range<test_type, dna4>));
}

TYPED_TEST(pseudo_random_access_test, adaptor)
{
std::vector<dna4> source{'A'_dna4, 'C'_dna4, 'G'_dna4};
TypeParam test_range{source};

// pipe notation
auto v = test_range | views::pseudo_random_access;
EXPECT_EQ(v | views::to_char | std::ranges::to<std::string>, "ACG");

// function notation
auto v2 = views::pseudo_random_access(test_range);
EXPECT_EQ(v2 | views::to_char | std::ranges::to<std::string>, "ACG");

// combinability
auto v3 = test_range | views::pseudo_random_access | std::views::drop(1);
EXPECT_EQ(v3 | views::to_char | std::ranges::to<std::string>, "CG");
}

0 comments on commit 18e84d8

Please sign in to comment.