C++ Utility Functions


Vector ostream operator<<

#ifndef VECTOR_OSTREAM_HPP
#define VECTOR_OSTREAM_HPP

#include <ostream>
#include <string>
#include <vector>

template <typename T>
std::ostream& operator<<(std::ostream& os, const std::vector<T>& v) {
    os << "[";
    for (size_t i = 0; i < v.size(); ++i) {
        os << v[i];
        if (i + 1 < v.size()) os << ", ";
    }
    os << "]";
    return os;
}

// Specialization for vector of strings
std::ostream& operator<<(std::ostream& os, const std::vector<std::string>& v) {
    os << "[";
    for (size_t i = 0; i < v.size(); ++i) {
        os << "\"" << v[i] << "\"";
        if (i + 1 < v.size()) os << ", ";
    }
    os << "]";
    return os;
}

#endif  // VECTOR_OSTREAM_HPP

Note: index checking method rather than \b deletion at end used becuase \b does not work for stringstreams.



Generate random vector

#include <algorithm>
#include <chrono>
#include <functional>
#include <vector>

int CurrentTimeNano() {
    auto current_time = std::chrono::high_resolution_clock::now().time_since_epoch();
    return std::chrono::duration_cast<std::chrono::nanoseconds>(current_time).count();
}

std::vector<int> GenerateRandomVector(size_t size, int min_value, int max_value) {
    std::default_random_engine engine(CurrentTimeNano());
    std::uniform_int_distribution<int> distribution(min_value, max_value);
    std::vector<int> generated_vector;
    std::generate_n(std::back_inserter(generated_vector), size, std::bind(distribution, engine));
    return generated_vector;
}

Or, using the the rng.hpp header in this repository:

#include "rng.hpp"

std::vector<int> GenerateRandomVector(size_t size, int min_value, int max_value) {
  UniformIntRng<int> rng(min_value, max_value);
  std::vector<int> generated_vector(size);
  for (size_t i = 0; i < size; ++i) {
    generated_vector[i] = rng.GenerateValue();
  }
  return generated_vector;
}



Simple struct with ostream operator<<

#include <ostream>

struct ValueIndexPair {
  int value = -1;
  int index = -1;
};

std::ostream& operator<<(std::ostream& os, const ValueIndexPair& p) {
  os << "(" << p.value << ", " << p.index << ")";
  return os;
}
struct Point {
  double x;
  double y;
};

std::ostream& operator<<(std::ostream& os, const Point& p) {
  os << "(" << p.x << ", " << p.y << ")";
  return os;
}



Sort vector of pairs

#include <algorithm>
#include <utility>
#include <vector>

// Using explicitly declared lambda argument types (c++11)
std::sort(pair_vector.begin(), pair_vector.end(),
          [](const std::pair<size_t, double>& left, const std::pair<size_t, double>& right) {
	    return left.second < right.second;
          });

// Using auto lambda argument types (c++14)
std::sort(pair_vector.begin(), pair_vector.end(), [](const auto& left, const auto& right) {
	    return left.second < right.second;
          });



Move elements of one vector to back of another

#include <vector>

// Moves elements of other_vector to the back of primary_vector (in order)
template <typename T>
void VectorJoin(std::vector<T>& primary_vector, std::vector<T>& other_vector) {
    primary_vector.insert(primary_vector.end(),
                          std::make_move_iterator(other_vector.begin()),
                          std::make_move_iterator(other_vector.end()));
    other_vector = {};
}



Copy/move test class

#include <iostream>

class Thing {
  public:
    Thing(int value) : _value(value) {
        std::cout << "Value constructor: " << *this << std::endl;
    }

    Thing(const Thing& other) : _value(other._value) {
        std::cout << "Copy constructor: " << *this << std::endl;
    }

    Thing(Thing&& other) : _value(std::move(other._value)) {
        std::cout << "Move constructor: " << *this << " from " << other << std::endl;
        other._value = 0;
    }

    Thing& operator=(const Thing& other) {
        std::cout << "Copy assignment: " << *this << " = " << other << std::endl;
        _value = other._value;
        return *this;
    }

    Thing& operator=(Thing&& other) {
        std::cout << "Move assignment: " << *this << " = " << other << std::endl;
        _value = std::move(other._value);
        other._value = 0;
        return *this;
    }
    
    ~Thing() {
        std::cout << "Destructor: " << *this << std::endl;
    }

    int GetValue() const { return _value; }

  private:
    int _value;

    friend std::ostream& operator<<(std::ostream& os, const Thing& thing);
};

std::ostream& operator<<(std::ostream& os, const Thing& thing) {
    os << "Thing(" << thing._value << ")";
    return os;
}



Point test class (with copy and swap)

#include <iostream>
#include <utility>

class Point {
 public:
  Point(int x_, int y_) : x_(x_), y_(y_) {
    std::cout << "Value constructor" << std::endl;
  }
  
  Point(const Point& other) : x_(other.x_), y_(other.y_) {
    std::cout << "Copy constructor" << std::endl;
  }
  
  Point(Point&& other) : x_(std::move(other.x_)), y_(std::move(other.y_)) {
    std::cout << "Move constructor" << std::endl;
  }
  
  Point& operator=(Point other) {
    std::cout << "Assignment operator" << std::endl;
    swap(*this, other);
    return *this;
  }
  
  ~Point() {
    std::cout << "Destructor (" << x_ << ", " << y_ << ")" << std::endl;
  }
  
 private:
  int x_;
  int y_;
  
  friend void swap(Point& left, Point& right);
  friend std::ostream& operator<<(std::ostream& os, const Point& p);
};

void swap(Point& left, Point& right) {
  std::swap(left.x_, right.x_);
  std::swap(left.y_, right.y_);
}

std::ostream& operator<<(std::ostream& os, const Point& p) {
  os << "(" << p.x_ << ", " << p.y_ << ")";
  return os;
}



Test algorithm correctness (against reference/naive solution)

#include "random_vector.hpp"
#include "rng.hpp"

void RunRandomTests(int num_tests, size_t max_array_size, int max_array_value) {
  UniformIntRng<size_t> array_size_rng(0, max_array_size);
  int num_tests_passed = 0;
  for (int i = 0; i < num_tests; ++i) {
    // Check 0- and 1-sized vectors on first two iterations
    size_t num_elements = (i < 2) ? i : array_size_rng.GenerateValue();
    std::vector<int> test_vector(random_vector::GenerateIntVector(num_elements, 0, max_array_value));
    int naive_result = NaiveMaxWater(test_vector);
    int result = MaxWater(test_vector);
    num_tests_passed += (result == naive_result);
    if (result != naive_result) {
      std::cout << "Test failed: " << test_vector << std::endl;
      std::cout << "Naive result: " << naive_result << std::endl;
      std::cout << "Other result: " << result << std::endl;
    }
  }
  std::cout << "Tests passed: " << num_tests_passed << " / " << num_tests << std::endl;
}



Timer

See CppTimer repository.



RNG

Raw code

#ifndef RNG_HPP
#define RNG_HPP

#include <chrono>
#include <random>

namespace rng_util {

int CurrentTimeNano() {
  auto current_time = std::chrono::high_resolution_clock::now().time_since_epoch();
  return std::chrono::duration_cast<std::chrono::nanoseconds>(current_time).count();
}

} // namespace rng_util

template <typename IntType>
class UniformIntRng {
 private:
  IntType min_value_;
  IntType max_value_;
  std::default_random_engine engine_;
  std::uniform_int_distribution<IntType> distribution_;

 public:
  UniformIntRng(IntType min_value, IntType max_value) : 
      min_value_(min_value), max_value_(max_value) {
    static_assert(std::is_integral<IntType>::value, "Requires integral type");
    engine_ = std::default_random_engine(rng_util::CurrentTimeNano());
    distribution_ = std::uniform_int_distribution<IntType>(min_value_, max_value_);
  }
  
  IntType GenerateValue() {
    return distribution_(engine_);
  }
};

template <typename FloatType>
class UniformFloatRng {
 private:
  FloatType min_value_;
  FloatType max_value_;
  std::default_random_engine engine_;
  std::uniform_real_distribution<FloatType> distribution_;

 public:
  UniformFloatRng(FloatType min_value, FloatType max_value) : 
      min_value_(min_value), max_value_(max_value) {
    static_assert(std::is_floating_point<FloatType>::value, "Requires floating point type");
    engine_ = std::default_random_engine(rng_util::CurrentTimeNano());
    distribution_ = std::uniform_real_distribution<FloatType>(min_value_, max_value_);
  }
  
  FloatType GenerateValue() {
    return distribution_(engine_);
  }
};

template <typename FloatType>
class NormalFloatRng {
 private:
  FloatType mean_;
  FloatType standard_deviation_;
  std::default_random_engine engine_;
  std::normal_distribution<FloatType> distribution_;

 public:
  NormalFloatRng(FloatType mean, FloatType standard_deviation) : 
      mean_(mean), standard_deviation_(standard_deviation) {
    static_assert(std::is_floating_point<FloatType>::value, "Requires floating point type");
    engine_ = std::default_random_engine(rng_util::CurrentTimeNano());
    distribution_ = std::normal_distribution<FloatType>(mean_, standard_deviation_);
  }
  
  FloatType GenerateValue() {
    return distribution_(engine_);
  }
};

template <typename IntType>
class NormalIntRng {
 private:
  double mean_;
  double standard_deviation_;
  std::default_random_engine engine_;
  std::normal_distribution<double> distribution_;

 public:
  NormalIntRng(double mean, double standard_deviation) : 
      mean_(mean), standard_deviation_(standard_deviation) {
    static_assert(std::is_integral<IntType>::value, "Template type must be integral");
    engine_ = std::default_random_engine(rng_util::CurrentTimeNano());
    distribution_ = std::normal_distribution<double>(mean_, standard_deviation_);
  }
  
  IntType GenerateValue() {
    return static_cast<IntType>(distribution_(engine_) + 0.5);
  }
};

class BernoulliRng {
 private:
  double true_probability_;
  std::default_random_engine engine_;
  std::uniform_real_distribution<double> distribution_;

 public:
  BernoulliRng(double true_probability) : true_probability_(true_probability) {
    engine_ = std::default_random_engine(rng_util::CurrentTimeNano());
    distribution_ = std::uniform_real_distribution<double>(0, 1);
  }
  
  double GenerateValue() {
    return distribution_(engine_) < true_probability_;
  }
};

#endif  // RNG_HPP



To fixed width string

#include <iomanip>
#include <sstream>
#include <string>

// Converts the given value to a string of length at least width by padding on the left with
// left_padding ('0' if unspecified).  Requires ostream insertion operator for T.
template <typename T>
std::string ToFixedWidthString(T value, int width, char left_padding = '0') {
    std::ostringstream oss;
    oss << std::setfill(left_padding) << std::setw(width) << value;
    return oss.str();
}



String trim

#include <algorithm>  // std::find_if
#include <cctype>  // std::isspace
#include <string>

// Trim from left, in-place
void Ltrim(std::string& s) {
    // Note: argument to std::isspace should be unsigned char to prevent undefined behavior
    auto not_space = [](unsigned char ch) { return !std::isspace(ch); };
    s.erase(s.begin(), std::find_if(s.begin(), s.end(), not_space));
}

// Trim from right, in-place
void Rtrim(std::string& s) {
    auto not_space = [](unsigned char ch) { return !std::isspace(ch); };
    // Note: .base() gets forward iterator from reverse
    s.erase(std::find_if(s.rbegin(), s.rend(), not_space).base(), s.end());
}

// Trim from both sides, in-place
void Trim(std::string& s) {
    Ltrim(s);
    Rtrim(s);
}

// Trim from left, makes copy
static inline std::string LtrimCopy(std::string s) {
  Ltrim(s);
  return s;
}

// Trim from right, makes copy
static inline std::string RtrimCopy(std::string s) {
  Rtrim(s);
  return s;
}

// Trim from both sides, makes copy
static inline std::string TrimCopy(std::string s) {
  Trim(s);
  return s;
}



String split

#include <string>
#include <vector>

// Split string by delim, optionally retaining empty strings (caused by consecutive delim chars)
std::vector<std::string> SplitString(const std::string& input_string,
                                     const char delim,
                                     bool retain_empty = false) {
    std::vector<std::string> output_vector;
    size_t start_i = 0;
    size_t found_i = 0;  // dummy initialization to start while loop
    size_t len;
    while (found_i != std::string::npos) {
        found_i = input_string.find(delim, start_i);
        len = (found_i == std::string::npos) ? input_string.length() - start_i : found_i - start_i;
        if (len > 0) {  // non-consecutive delimiters
            output_vector.push_back(input_string.substr(start_i, found_i - start_i));
        } else if (retain_empty) {
            output_vector.push_back(std::string(""));
        }
        start_i = found_i + 1;
    }
    return output_vector;
}



String join

#include <vector>
#include <string>

// Delimiter as char
std::string JoinStrings(const std::vector<std::string>& input_vector, const char delim) {
  std::string result("");
  for (unsigned int i = 0; i < input_vector.size(); i++) {
    result += input_vector[i];
    if (i + 1 < input_vector.size()) {
      result += delim;
    }
  }
  return result;
}

// Delimiter as string
std::string JoinStrings(const std::vector<std::string>& input_vector, const std::string& delim) {
  std::string result("");
  for (unsigned int i = 0; i < input_vector.size(); i++) {
    result += input_vector[i];
    if (i + 1 < input_vector.size()) {
      result += delim;
    }
  }
  return result;
}



Linear interpolation (lerp)

/*
 * Linear interpolation, given:
 *  - input variable reference points: [t_min, t_max]
 *  - target value of input variable: t
 *  - output values: f(t_min), f(t_max)
 *
 * Returns: f(t) where f is the line passing through (t_min, f(t_min)), (t_max, f(t_max))
 */
double Lerp(double t_min, double t_max, double t, double f_t_min, double f_t_max) {
    double r = (t - t_min) / (t_max - t_min);
    return (1 - r) * f_t_min + r * f_t_max;
}
</p></details><br/>

<br/>

<details>
  <summary><b>Integer power function</b></summary><p>
  
```c++
#include <type_traits>

// Fast int power function, assumes exponent is non-negative
template <typename BaseType, typename ExpType>
BaseType IntPower(BaseType base, ExpType exponent) {
  static_assert(std::is_integral<BaseType>::value, "Integral type required for base");
  static_assert(std::is_integral<ExpType>::value, "Integral type required for exponent");
  BaseType result = 1;
  if (base < 0) {
    result *= (exponent & 1) ? -1 : 1;
    base *= -1;
  }
  while (exponent > 0) {
    if (exponent & 1) {
       result *= base;
    }
    // Now exponent is even
    exponent /= 2;
    base *= base;
  }
  return result;
}



Float To string given sig figs

#include <cmath> // std::log10
#include <type_traits>

// Generate string representing a float rounded to given number of significant figures
template <typename FloatType>
std::string FloatToSigFigs(FloatType value, int sig_figs) {
  static_assert(std::is_floating_point<FloatType>::value, "Float type required");
  int digits_before_decimal = (value > 1) ? std::floor(std::log10(value)) + 1 : 1;
  std::stringstream ss;
  ss << std::fixed << std::setprecision(sig_figs - digits_before_decimal) << value;
  return ss.str();
}  



Scientific notation

#include <iomanip> // std::setprecision
#include <sstream>
#include <string>
#include <type_traits>
  
// Returns std::string containg scientific notation with given precision, float version
template <typename FloatType>
typename std::enable_if<std::is_floating_point<FloatType>::value, std::string>::type
SciNotation(FloatType value, int precision = 3) {
  std::stringstream ss;
  ss << std::scientific << std::setprecision(precision) << value;
  return ss.str();
}

// Returns std::string containg scientific notation with given precision, int version
template <typename IntType>
typename std::enable_if<std::is_integral<IntType>::value, std::string>::type
SciNotation(IntType value, int precision = 3) {
  return SciNotation(static_cast<double>(value), precision);
}



Human-readable number formatting

#include <cmath> // std::log10
#include <cstdlib> // std::to_string
#include <iomanip> // std::setprecision
#include <sstream>
#include <string>
#include <vector>

// Fast int power function, assumes exponent is non-negative
template <typename BaseType, typename ExpType>
BaseType IntPower(BaseType base, ExpType exponent) {
  static_assert(std::is_integral<BaseType>::value, "Integral type required for base");
  static_assert(std::is_integral<ExpType>::value, "Integral type required for exponent");
  BaseType result = 1;
  if (base < 0) {
    result *= (exponent & 1) ? -1 : 1;
    base *= -1;
  }
  while (exponent > 0) {
    if (exponent & 1) {
       result *= base;
    }
    // Now exponent is even
    exponent /= 2;
    base *= base;
  }
  return result;
}

// Generate string representing a float rounded to given number of significant figures
template <typename FloatType>
std::string FloatToSigFigs(FloatType value, int sig_figs) {
  static_assert(std::is_floating_point<FloatType>::value, "Float type required");
  int digits_before_decimal = (value > 1) ? std::floor(std::log10(value)) + 1 : 1;
  std::stringstream ss;
  ss << std::fixed << std::setprecision(sig_figs - digits_before_decimal) << value;
  return ss.str();
}  

template <typename IntType>
std::string IntSciNotation(IntType value, int sig_figs) {
  static_assert(std::is_integral<IntType>::value, "Integral type required");
  std::stringstream ss;
  ss << std::scientific << std::setprecision(sig_figs - 1) << static_cast<double>(value);
  return ss.str();
}

template <typename IntType>
std::string HumanReadableFormat(IntType num, int sig_figs = 3) {
  static_assert(std::is_integral<IntType>::value, "Integral type required");
  std::string result_string("");
  // First, check for negative values
  if (num < 0) {
    num = -num;
    result_string += '-';
  }
  // Next, check number's size
	static const std::vector<char> kSuffixChars{' ', 'K', 'M', 'B'};
  int log_scale = num ? std::floor(std::log10(num)) : 0;
  int k_log_scale = log_scale / 3;
  if (k_log_scale < 1) {
    return result_string + std::to_string(num);
  }
  else if (k_log_scale >= kSuffixChars.size()) {
    return result_string + IntSciNotation(num, sig_figs);
  }
  // Now, put into form {prefix-digits} + {suffix-char}
  double prefix_digits = static_cast<double>(num) / IntPower(1000, k_log_scale);
  result_string += FloatToSigFigs(prefix_digits, sig_figs);
  result_string += kSuffixChars[k_log_scale];
  return result_string;
}