Introduce Utility/Functional

Utility/Functional is a lightweight high level functional-oriented sub
library that attempts to abstract some common uses and boilerplate
around the parser code (and later maybe for other modules to use).

This patch introduce only three functions, but they have proven common
enough to warrant some common implementation.
This commit is contained in:
Jørgen Kvalsvik
2016-02-26 13:59:16 +01:00
parent 1c32ef5562
commit ba743e488c
6 changed files with 347 additions and 41 deletions

View File

@@ -15,6 +15,7 @@ add_subdirectory(EclipseState/Util/tests)
add_subdirectory(EclipseState/IOConfig/tests)
add_subdirectory(EclipseState/InitConfig/tests)
add_subdirectory(EclipseState/Summary/tests)
add_subdirectory(Utility/tests)
add_subdirectory(Applications)
add_subdirectory(IntegrationTests)
@@ -134,6 +135,10 @@ EclipseState/Summary/Summary.cpp
EclipseState/IOConfig/IOConfig.cpp)
#
set( utility_source
Utility/Functional.cpp
)
set( HEADER_FILES
OpmLog/LogBackend.hpp
OpmLog/TimerLog.hpp
@@ -280,6 +285,7 @@ EclipseState/Tables/TableEnums.hpp
EclipseState/Tables/TableSchema.hpp
EclipseState/Tables/TableIndex.hpp
#
Utility/Functional.hpp
Utility/EquilWrapper.hpp)
add_library(buildParser ${rawdeck_source} ${build_parser_source} ${deck_source} ${unit_source} ${generator_source})
@@ -333,7 +339,7 @@ opm_add_test( runInlineKeywordTest SOURCES ${PROJECT_BINARY_DIR}/generated-sourc
#-----------------------------------------------------------------
add_library(opmparser ${generated_source} ${state_source} ${rawdeck_source} ${parser_source} ${deck_source} ${unit_source} ${log_source} ${generator_source})
add_library(opmparser ${generated_source} ${state_source} ${rawdeck_source} ${parser_source} ${deck_source} ${unit_source} ${log_source} ${generator_source} ${utility_source})
add_dependencies( opmparser generatedCode )
target_link_libraries(opmparser opmjson ${Boost_LIBRARIES} ${ERT_LIBRARIES})
set_target_properties(opmparser PROPERTIES VERSION ${opm-parser_VERSION_MAJOR}.${opm-parser_VERSION_MINOR}

View File

@@ -9,51 +9,12 @@
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well.hpp>
#include <opm/parser/eclipse/EclipseState/Summary/Summary.hpp>
#include <opm/parser/eclipse/Utility/Functional.hpp>
#include <ert/ecl/ecl_smspec.h>
namespace Opm {
namespace fun {
/*
* map :: (a -> b) -> [a] -> [b]
*
* C can be any foreach-compatible container (that supports .begin,
* .end), but will always return a vector.
*/
template< typename F, typename C >
std::vector< typename std::result_of< F( typename C::const_iterator::value_type& ) >::type >
map( F&& f, const C& src ) {
using A = typename C::const_iterator::value_type;
using B = typename std::result_of< F( A& ) >::type;
std::vector< B > ret;
ret.reserve( src.size() );
std::transform( src.begin(), src.end(), std::back_inserter( ret ), f );
return ret;
}
template< typename A >
std::vector< A > concat( std::vector< std::vector< A > >&& src ) {
const auto size = std::accumulate( src.begin(), src.end(), 0,
[]( std::size_t acc, const std::vector< A >& x ) {
return acc + x.size();
}
);
std::vector< A > dst;
dst.reserve( size );
for( auto& x : src )
std::move( x.begin(), x.end(), std::back_inserter( dst ) );
return dst;
}
}
static std::string wellName( const std::shared_ptr< const Well >& well ) {
return well->name();
}

View File

@@ -0,0 +1,49 @@
#include <opm/parser/eclipse/Utility/Functional.hpp>
namespace Opm {
namespace fun {
iota::iota( int begin, int end ) : first( begin ), last( end ) {}
iota::iota( int end ) : iota( 0, end ) {}
size_t iota::size() const {
return this->last - this->first;
}
iota::const_iterator iota::begin() const {
return { first };
}
iota::const_iterator iota::end() const {
return { last };
}
int iota::const_iterator::operator*() const {
return this->value;
}
iota::const_iterator& iota::const_iterator::operator++() {
++( this->value );
return *this;
}
iota::const_iterator iota::const_iterator::operator++( int ) {
iota::const_iterator copy( *this );
this->operator++();
return copy;
}
bool iota::const_iterator::operator==( const const_iterator& rhs ) const {
return this->value == rhs.value;
}
bool iota::const_iterator::operator!=( const const_iterator& rhs ) const {
return !(*this == rhs );
}
iota::const_iterator::const_iterator( int x ) : value( x ) {}
}
}

View File

@@ -0,0 +1,214 @@
#include <algorithm>
#include <iterator>
#include <vector>
namespace Opm {
namespace fun {
/*
* The Utility/Functional library provides convenient high level
* functionality and higher order functions inspiried by functional
* languages (in particular Haskell) and modern C++. The goal is to provide
* lightweight features that reduce boilerplate and make code more
* declarative.
*/
/*
* map :: (a -> b) -> [a] -> [b]
*
* maps the elements [a] of the passed container C to [b], by using the
* passed function f :: a -> b. Works like map in haskell, lisp, python etc.
*
* C can be any foreach-compatible container (that supports .begin,
* .end), but will always return a vector.
*
* F can be any Callable, that is both function pointer,
* operator()-providing class or std::function, including lambdas. F is
* typically passed by reference. F must be unary of type A (which must
* match what C::const_iterator::operator* returns) and have return
* type B (by value).
*
* In short, this function deal with vector allocation, resizing and
* population based on some function f.
*
* fun::map( f, vec ) is equivalent to:
* vector dst;
* for( auto& x : vec ) dst.push_back( f( x ) );
* return dst;
*
* The behaviour is undefined if F has any side effects.
*
* --
*
* int plus1( int x ) { return x + 1; }
* base_vec = { 0, 1, 2, 3, 4 };
* vec = fun::map( &plus1, base_vec );
*
* vec => { 1, 2, 3, 4, 5 }
*
* --
*
* int mul2 = []( int x ) { return x * 2; };
* base_vec = { 0, 1, 2, 3, 4 };
* vec = fun::map( mul2, base_vec );
*
* vec => { 0, 2, 4, 6, 8 };
*
*/
template< typename F, typename C >
std::vector< typename std::result_of< F( typename C::const_iterator::value_type& ) >::type >
map( F f, const C& src ) {
using A = typename C::const_iterator::value_type;
using B = typename std::result_of< F( A& ) >::type;
std::vector< B > ret;
ret.reserve( src.size() );
std::transform( src.begin(), src.end(), std::back_inserter( ret ), f );
return ret;
}
/*
* concat :: [[a]] -> [a]
*
* A primitive concat taking a vector of vectors, flattened into a
* single 1 dimensional vector. Moves all the elements so no unecessary
* copies are done.
*
* vec = { { 1 }, { 2, 2 }, { 3, 3, 3 } }
* cvec = concat( vec ) => { 1, 2, 2, 3, 3, 3 }
*/
template< typename A >
std::vector< A > concat( std::vector< std::vector< A > >&& src ) {
const auto size = std::accumulate( src.begin(), src.end(), 0,
[]( std::size_t acc, const std::vector< A >& x ) {
return acc + x.size();
}
);
std::vector< A > dst;
dst.reserve( size );
for( auto& x : src )
std::move( x.begin(), x.end(), std::back_inserter( dst ) );
return dst;
}
/*
* iota :: int -> [int]
* iota :: (int,int) -> [int]
*
* iota (ι) is borrowed from the APL programming language. This particular
* implementation behaves as a generator-like constant-space consecutive
* sequence of integers [m,n). Written to feel similar to std::iota, but as
* a producer instead of straight-up writer. This is similar to python2.7s
* xrange(), python3s range() and haskell's [0..(n-1)]. Some examples
* follow.
*
* Notes:
* * iota defaults to [0,n)
* * iota uses 0 indexing to feel more familiar to C++'s zero indexing.
* * iota can start at negative indices, but will always count upwards.
* * iota::const_iterator does not support operator-- (which would allow
* support for reverse iterators). This can be implemented if need arises.
* * iota is meant to play nice with the rest of fun and to be able to
* replace mundane for loops when the loops only purpose is to create the
* sequence of elements. iota can feel more declarative and work better
* with functions.
* * iota adds value semantics to things that in C++ normally relies on
* variable mutations. iota is meant to make it less painful to write
* immutable and declarative code.
* * as with all iterators, iota( n, m ) behaviour is undefined if m < n
* * unlike python's range, iota doesn't support steps (only increments).
* this is by design to keep this simple and minimal, as well as the name
* iota being somewhat unsuitable for stepping ranges. If the need for
* this arises it will be a separate function.
*
* fun::iota( 5 ) => [ 0, 1, 2, 3, 4 ]
* fun::iota( 3 ) => [ 0, 1, 2 ]
* fun::iota( 1, 6 ) => [ 1, 2, 3, 4, 5 ]
*
* --
*
* std::vector< int > vec ( 5, 0 );
* std::iota( vec.begin(), vec.end(), 0 );
* vec => [ 0, 1, 2, 3, 4 ]
*
* fun::iota i( 5 );
* std::vector vec( i.begin(), i.end() );
* vec => [ 0, 1, 2, 3, 4 ]
*
* --
*
* int plus( int x ) { return x + 1; }
* auto vec = fun::map( &plus, fun::iota( 5 ) );
* vec => [ 1, 2, 3, 4, 5 ]
*
* is equivalent to
*
* int plus( int x ) { return x + 1; }
* std::vector< int > vec;
* for( int i = 0; i < 5; ++i )
* vec.push_back( plus( i ) );
* vec => [ 1, 2, 3, 4, 5 ]
*
* --
*
* While not the primary intended use case, this enables foreach loop
* syntax over intervals:
*
* for( auto i : fun::iota( 5 ) )
* std::cout << i << " ";
*
* => 0 1 2 3 4
*
* for( auto i : fun::iota( 1, 6 ) )
* std::cout << i << " ";
*
* => 1 2 3 4 5
*
*/
class iota {
public:
iota( int end );
iota( int begin, int end );
class const_iterator {
public:
using difference_type = int;
using value_type = int;
using pointer = int*;
using reference = int&;
using iterator_category = std::forward_iterator_tag;
const_iterator() = default;
int operator*() const;
const_iterator& operator++();
const_iterator operator++( int );
bool operator==( const const_iterator& rhs ) const;
bool operator!=( const const_iterator& rhs ) const;
private:
const_iterator( int );
int value;
friend class iota;
};
size_t size() const;
const_iterator begin() const;
const_iterator end() const;
private:
int first;
int last;
};
}
}

View File

@@ -0,0 +1,6 @@
foreach(tapp FunctionalTests )
opm_add_test(run${tapp} SOURCES ${tapp}.cpp
LIBRARIES opmparser ${Boost_LIBRARIES})
endforeach()

View File

@@ -0,0 +1,70 @@
#define BOOST_TEST_MODULE FunctionalTests
#include <vector>
#include <boost/test/unit_test.hpp>
#include <opm/parser/eclipse/Utility/Functional.hpp>
#include <iostream>
using namespace Opm;
BOOST_AUTO_TEST_CASE(iotaEqualCollections) {
std::vector< int > vec( 5 );
for( int i = 0; i < 5; ++i )
vec[ i ] = i;
fun::iota iota( 5 );
for( auto x : iota )
std::cout << x << " ";
std::cout << std::endl;
std::vector< int > vec_iota( iota.begin(), iota.end() );
BOOST_CHECK_EQUAL_COLLECTIONS(
vec_iota.begin(), vec_iota.end(),
vec.begin(), vec.end() );
BOOST_CHECK_EQUAL_COLLECTIONS(
vec_iota.begin(), vec_iota.end(),
fun::iota( 5 ).begin(), fun::iota( 5 ).end() );
BOOST_CHECK_EQUAL_COLLECTIONS(
vec.begin(), vec.end(),
fun::iota( 5 ).begin(), fun::iota( 5 ).end() );
}
BOOST_AUTO_TEST_CASE(iotaForeach) {
/* this test is mostly a syntax verification test */
std::vector< int > vec = { 0, 1, 2, 3, 4 };
for( auto x : fun::iota( 5 ) )
BOOST_CHECK_EQUAL( vec[ x ], x );
}
BOOST_AUTO_TEST_CASE(iotaSize) {
BOOST_CHECK_EQUAL( 5, fun::iota( 5 ).size() );
BOOST_CHECK_EQUAL( 5, fun::iota( 1, 6 ).size() );
BOOST_CHECK_EQUAL( 0, fun::iota( 0 ).size() );
BOOST_CHECK_EQUAL( 0, fun::iota( 0, 0 ).size() );
}
BOOST_AUTO_TEST_CASE(iotaWithMap) {
const auto plus1 = []( int x ) { return x + 1; };
std::vector< int > vec = { 1, 2, 3, 4, 5 };
auto vec_iota = fun::map( plus1, fun::iota( 5 ) );
BOOST_CHECK_EQUAL_COLLECTIONS(
vec_iota.begin(), vec_iota.end(),
vec.begin(), vec.end() );
}
BOOST_AUTO_TEST_CASE(iotaNegativeBegin) {
const auto vec = { -4, -3, -2, -1, 0 };
fun::iota iota( -4, 1 );
BOOST_CHECK_EQUAL_COLLECTIONS(
vec.begin(), vec.end(),
iota.begin(), iota.end() );
}