diff --git a/include/Rivet/Tools/Utils.hh b/include/Rivet/Tools/Utils.hh --- a/include/Rivet/Tools/Utils.hh +++ b/include/Rivet/Tools/Utils.hh @@ -1,513 +1,514 @@ // -*- C++ -*- #ifndef RIVET_Utils_HH #define RIVET_Utils_HH #include "Rivet/Tools/RivetSTL.hh" #include "Rivet/Tools/PrettyPrint.hh" #include <ostream> #include <iostream> #include <cctype> #include <cerrno> #include <stdexcept> #include <numeric> #include <limits> #include <climits> #include <cfloat> #include <cmath> // // Macro to help with overzealous compiler warnings // /// @note It's easier and better to just not give an arg name to args which won't be used, when possible. // #ifdef UNUSED // #elif defined(__GNUC__) // # define UNUSED(x) UNUSED_ ## x __attribute__((unused)) // #elif defined(__LCLINT__) // # define UNUSED(x) /*@unused@*/ x // #else // # define UNUSED(x) x // #endif /// Macro to help mark code as deprecated to produce compiler warnings #ifndef DEPRECATED #if __GNUC__ && __cplusplus && RIVET_NO_DEPRECATION_WARNINGS == 0 #define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) #if GCC_VERSION >= 40500 #if __cplusplus > 201103L #define DEPRECATED(x) [[deprecated(x)]] #else #define DEPRECATED(x) __attribute__((deprecated(x))) #endif #else #define DEPRECATED(x) __attribute__((deprecated)) #endif #else #define DEPRECATED(x) #endif #endif namespace Rivet { /// Convenient const for getting the double NaN value static constexpr double DBL_NAN = std::numeric_limits<double>::quiet_NaN(); /// @name String utils //@{ struct bad_lexical_cast : public std::runtime_error { bad_lexical_cast(const std::string& what) : std::runtime_error(what) {} }; /// @brief Convert between any types via stringstream template<typename T, typename U> T lexical_cast(const U& in) { try { std::stringstream ss; ss << in; T out; ss >> out; return out; } catch (const std::exception& e) { throw bad_lexical_cast(e.what()); } } /// @brief Convert any object to a string /// /// Just a convenience wrapper for the more general Boost lexical_cast template <typename T> inline string to_str(const T& x) { return lexical_cast<string>(x); } /// @brief Convert any object to a string /// /// An alias for to_str() with a more "Rivety" mixedCase name. template <typename T> inline string toString(const T& x) { return to_str(x); } /// Replace the first instance of patt with repl inline string& replace_first(string& str, const string& patt, const string& repl) { if (!contains(str, patt)) return str; //< contains from RivetSTL str.replace(str.find(patt), patt.size(), repl); return str; } /// @brief Replace all instances of patt with repl /// /// @note Finding is interleaved with replacement, so the second search happens after /// first replacement, etc. This could lead to infinite loops and other counterintuitive /// behaviours if not careful. inline string& replace_all(string& str, const string& patt, const string& repl) { if (!contains(str, patt)) return str; //< contains from RivetSTL while (true) { string::size_type it = str.find(patt); if (it == string::npos) break; str.replace(it, patt.size(), repl); } return str; } /// Case-insensitive string comparison function inline int nocase_cmp(const string& s1, const string& s2) { string::const_iterator it1 = s1.begin(); string::const_iterator it2 = s2.begin(); while ( (it1 != s1.end()) && (it2 != s2.end()) ) { if(::toupper(*it1) != ::toupper(*it2)) { // < Letters differ? // Return -1 to indicate smaller than, 1 otherwise return (::toupper(*it1) < ::toupper(*it2)) ? -1 : 1; } // Proceed to the next character in each string ++it1; ++it2; } size_t size1 = s1.size(), size2 = s2.size(); // Cache lengths // Return -1,0 or 1 according to strings' lengths if (size1 == size2) return 0; return (size1 < size2) ? -1 : 1; } /// Case-insensitive string equality function inline bool nocase_equals(const string& s1, const string& s2) { return nocase_cmp(s1, s2) == 0; } /// Convert a string to lower-case inline string toLower(const string& s) { string out = s; std::transform(out.begin(), out.end(), out.begin(), (int(*)(int)) std::tolower); return out; } /// Convert a string to upper-case inline string toUpper(const string& s) { string out = s; std::transform(out.begin(), out.end(), out.begin(), (int(*)(int)) std::toupper); return out; } /// Check whether a string @a start is found at the start of @a s inline bool startsWith(const string& s, const string& start) { if (s.length() < start.length()) return false; return s.substr(0, start.length()) == start; } /// Check whether a string @a end is found at the end of @a s inline bool endsWith(const string& s, const string& end) { if (s.length() < end.length()) return false; return s.substr(s.length() - end.length()) == end; } /// Make a string containing the string representations of each item in v, separated by sep template <typename T> inline string join(const vector<T>& v, const string& sep=" ") { string rtn; for (size_t i = 0; i < v.size(); ++i) { if (i != 0) rtn += sep; rtn += to_str(v[i]); } return rtn; } /// Make a string containing the string representations of each item in s, separated by sep template <typename T> inline string join(const set<T>& s, const string& sep=" ") { string rtn; for (const T& x : s) { if (rtn.size() > 0) rtn += sep; rtn += to_str(x); } return rtn; } /// @brief Split a string on a specified separator string inline vector<string> split(const string& s, const string& sep) { vector<string> dirs; string tmp = s; while (true) { const size_t delim_pos = tmp.find(sep); if (delim_pos == string::npos) break; const string dir = tmp.substr(0, delim_pos); if (dir.length()) dirs.push_back(dir); // Don't insert "empties" tmp.replace(0, delim_pos+1, ""); } if (tmp.length()) dirs.push_back(tmp); // Don't forget the trailing component! return dirs; } //@} /// @name Path utils //@{ /// @brief Split a path string with colon delimiters /// /// Ignores zero-length substrings. Designed for getting elements of filesystem paths, naturally. inline vector<string> pathsplit(const string& path) { return split(path, ":"); } /// @brief Join several filesystem paths together with the standard ':' delimiter /// /// Note that this does NOT join path elements together with a platform-portable /// directory delimiter, cf. the Python @c {os.path.join}! inline string pathjoin(const vector<string>& paths) { return join(paths, ":"); } /// Operator for joining strings @a a and @a b with filesystem separators inline string operator / (const string& a, const string& b) { // Ensure that a doesn't end with a slash, and b doesn't start with one, to avoid "//" const string anorm = (a.find("/") != string::npos) ? a.substr(0, a.find_last_not_of("/")+1) : a; const string bnorm = (b.find("/") != string::npos) ? b.substr(b.find_first_not_of("/")) : b; return anorm + "/" + bnorm; } /// Get the basename (i.e. terminal file name) from a path @a p inline string basename(const string& p) { if (!contains(p, "/")) return p; return p.substr(p.rfind("/")+1); } /// Get the dirname (i.e. path to the penultimate directory) from a path @a p inline string dirname(const string& p) { if (!contains(p, "/")) return ""; return p.substr(0, p.rfind("/")); } /// Get the stem (i.e. part without a file extension) from a filename @a f inline string file_stem(const string& f) { if (!contains(f, ".")) return f; return f.substr(0, f.rfind(".")); } /// Get the file extension from a filename @a f inline string file_extn(const string& f) { if (!contains(f, ".")) return ""; return f.substr(f.rfind(".")+1); } //@} /// @name Container utils //@{ /// Return number of elements in the container @a c for which @c f(x) is true. template <typename CONTAINER> inline unsigned int count(const CONTAINER& c) { // return std::count_if(std::begin(c), std::end(c), [](const typename CONTAINER::value_type& x){return bool(x);}); unsigned int rtn = 0; for (const auto& x : c) if (bool(x)) rtn += 1; return rtn; } /// Return number of elements in the container @a c for which @c f(x) is true. template <typename CONTAINER, typename FN> inline unsigned int count(const CONTAINER& c, const FN& f) { return std::count_if(std::begin(c), std::end(c), f); } /// Return true if x is true for any x in container c, otherwise false. template <typename CONTAINER> inline bool any(const CONTAINER& c) { // return std::any_of(std::begin(c), std::end(c), [](const auto& x){return bool(x);}); for (const auto& x : c) if (bool(x)) return true; return false; } /// Return true if f(x) is true for any x in container c, otherwise false. template <typename CONTAINER, typename FN> inline bool any(const CONTAINER& c, const FN& f) { return std::any_of(std::begin(c), std::end(c), f); } /// Return true if @a x is true for all @c x in container @a c, otherwise false. template <typename CONTAINER> inline bool all(const CONTAINER& c) { // return std::all_of(std::begin(c), std::end(c), [](const auto& x){return bool(x);}); for (const auto& x : c) if (!bool(x)) return false; return true; } /// Return true if @a f(x) is true for all @c x in container @a c, otherwise false. template <typename CONTAINER, typename FN> inline bool all(const CONTAINER& c, const FN& f) { return std::all_of(std::begin(c), std::end(c), f); } /// Return true if @a x is false for all @c x in container @a c, otherwise false. template <typename CONTAINER> inline bool none(const CONTAINER& c) { // return std::none_of(std::begin(c), std::end(c), [](){}); for (const auto& x : c) if (bool(x)) return false; return true; } /// Return true if @a f(x) is false for all @c x in container @a c, otherwise false. template <typename CONTAINER, typename FN> inline bool none(const CONTAINER& c, const FN& f) { return std::none_of(std::begin(c), std::end(c), f); } /// A single-container-arg version of std::transform, aka @c map template <typename C1, typename C2, typename FN> inline const C2& transform(const C1& in, C2& out, const FN& f) { out.clear(); out.resize(in.size()); std::transform(in.begin(), in.end(), out.begin(), f); return out; } /// A single-container-arg version of std::accumulate, aka @c reduce template <typename C1, typename T, typename FN> inline T accumulate(const C1& in, const T& init, const FN& f) { const T rtn = std::accumulate(in.begin(), in.end(), init, f); return rtn; } /// Generic sum function, adding @c x for all @c x in container @a c, starting with @a start template <typename CONTAINER, typename T> inline T sum(const CONTAINER& c, const T& start=T()) { T rtn = start; for (const auto& x : c) rtn += x; return rtn; } /// Generic sum function, adding @a fn(@c x) for all @c x in container @a c, starting with @a start template <typename CONTAINER, typename FN, typename T> inline T sum(const CONTAINER& c, const FN& f, const T& start=T()) { T rtn = start; for (const auto& x : c) rtn += f(x); return rtn; } /// Filter a collection in-place, removing the subset that passes the supplied function template <typename CONTAINER, typename FN> inline CONTAINER& ifilter_discard(CONTAINER& c, const FN& f) { const auto newend = std::remove_if(std::begin(c), std::end(c), f); c.erase(newend, c.end()); return c; } /// Filter a collection by copy, removing the subset that passes the supplied function template <typename CONTAINER, typename FN> inline CONTAINER filter_discard(const CONTAINER& c, const FN& f) { CONTAINER rtn = c; return ifilter_discard(rtn, f); ///< @todo More efficient would be copy_if with back_inserter... } /// Filter a collection by copy into a supplied container, removing the subset that passes the supplied function /// @note New container will be replaced, not appended to template <typename CONTAINER, typename FN> inline CONTAINER& filter_discard(const CONTAINER& c, const FN& f, CONTAINER& out) { out = filter_discard(c, f); return out; } /// Filter a collection in-place, keeping the subset that passes the supplied function template <typename CONTAINER, typename FN> inline CONTAINER& ifilter_select(CONTAINER& c, const FN& f) { //using value_type = typename std::remove_reference<decltype(*std::begin(std::declval<typename std::add_lvalue_reference<CONTAINER>::type>()))>::type; auto invf = [&](const typename CONTAINER::value_type& x){ return !f(x); }; return ifilter_discard(c, invf); } /// Filter a collection by copy, keeping the subset that passes the supplied function template <typename CONTAINER, typename FN> inline CONTAINER filter_select(const CONTAINER& c, const FN& f) { CONTAINER rtn = c; return ifilter_select(rtn, f); ///< @todo More efficient would be copy_if with back_inserter ... but is that equally container agnostic? } /// Filter a collection by copy into a supplied container, keeping the subset that passes the supplied function /// @note New container will be replaced, not appended to template <typename CONTAINER, typename FN> inline CONTAINER& filter_select(const CONTAINER& c, const FN& f, CONTAINER& out) { out = filter_select(c, f); return out; } /// @brief Slice of the container elements cf. Python's [i:j] syntax /// /// The element at the @j index is not included in the returned container. /// @a i and @a j can be negative, treated as backward offsets from the end of the container. template <typename CONTAINER> inline CONTAINER slice(const CONTAINER& c, int i, int j) { CONTAINER rtn; const size_t off1 = (i >= 0) ? i : c.size() + i; const size_t off2 = (j >= 0) ? j : c.size() + j; if (off1 > c.size() || off2 > c.size()) throw RangeError("Attempting to slice beyond requested offsets"); if (off2 < off1) throw RangeError("Requested offsets in invalid order"); rtn.resize(off2 - off1); std::copy(c.begin()+off1, c.begin()+off2, rtn.begin()); return rtn; } /// @brief Tail slice of the container elements cf. Python's [i:] syntax /// /// Single-index specialisation of @c slice(c, i, j) template <typename CONTAINER> inline CONTAINER slice(const CONTAINER& c, int i) { return slice(c, i, c.size()); } /// @brief Head slice of the @a n first container elements /// /// Negative @a n means to take the head excluding the @a{n}-element tail template <typename CONTAINER> inline CONTAINER head(const CONTAINER& c, int n) { // if (n > c.size()) throw RangeError("Requested head longer than container"); if (n < 0) n = std::max(0, (int)c.size()+n); n = std::min(n, (int)c.size()); return slice(c, 0, n); } /// @brief Tail slice of the @a n last container elements /// /// Negative @a n means to take the tail from after the @a{n}th element template <typename CONTAINER> inline CONTAINER tail(const CONTAINER& c, int n) { // if (n > c.size()) throw RangeError("Requested tail longer than container"); if (n < 0) n = std::max(0, (int)c.size()+n); n = std::min(n, (int)c.size()); return slice(c, c.size()-n); } using std::min; using std::max; /// Find the minimum value in the vector inline double min(const vector<double>& in, double errval=DBL_NAN) { - return *std::min_element(in.begin(), in.end()); + const auto e = std::min_element(in.begin(), in.end()); + return e != in.end() ? *e : errval; } /// Find the maximum value in the vector inline double max(const vector<double>& in, double errval=DBL_NAN) { const auto e = std::max_element(in.begin(), in.end()); return e != in.end() ? *e : errval; } /// Find the minimum and maximum values in the vector inline pair<double,double> minmax(const vector<double>& in, double errval=DBL_NAN) { const auto e = std::minmax_element(in.begin(), in.end()); const double rtnmin = e.first != in.end() ? *e.first : errval; const double rtnmax = e.second != in.end() ? *e.first : errval; return std::make_pair(rtnmin, rtnmax); } /// Find the minimum value in the vector inline int min(const vector<int>& in, int errval=-1) { const auto e = std::min_element(in.begin(), in.end()); return e != in.end() ? *e : errval; } /// Find the maximum value in the vector inline int max(const vector<int>& in, int errval=-1) { const auto e = std::max_element(in.begin(), in.end()); return e != in.end() ? *e : errval; } /// Find the minimum and maximum values in the vector inline pair<int,int> minmax(const vector<int>& in, int errval=-1) { const auto e = std::minmax_element(in.begin(), in.end()); const double rtnmin = e.first != in.end() ? *e.first : errval; const double rtnmax = e.second != in.end() ? *e.first : errval; return std::make_pair(rtnmin, rtnmax); } //@} /// @brief Get a parameter from a named environment variable, with automatic type conversion /// @note Return @a fallback if the variable is not defined, otherwise convert its string to the template type /// @todo Should the param name have to be specific to an analysis? Can specialise as an Analysis member fn. template <typename T> T getEnvParam(const std::string name, const T& fallback) { char* env = getenv(name.c_str()); return env ? lexical_cast<T>(env) : fallback; } } #endif