Last active
July 21, 2021 22:25
-
-
Save jkalias/6accf07af66d38e27b2de1ba8f7552af to your computer and use it in GitHub Desktop.
(Deprecated, see this repo for more details https://github.com/jkalias/functional_vector) A wrapper for C++ std::vector geared towards a more fluent API, with functional programming in mind. The primary focus is readability at the call site and not performance. Heavily influenced and inspired from C# and Swift.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MIT License | |
// | |
// Copyright (c) 2021 Ioannis Kaliakatsos | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in all | |
// copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
// SOFTWARE. | |
#ifndef functional_vector_h | |
#define functional_vector_h | |
#include <vector> | |
#include <functional> | |
#include <algorithm> | |
#include <optional> | |
template <typename T> | |
class functional_vector { | |
public: | |
functional_vector() | |
: backing_vector_() | |
{ | |
} | |
functional_vector(const std::vector<T> &vector) | |
: backing_vector_(std::move(vector)) | |
{ | |
} | |
functional_vector(const std::initializer_list<T>& list) | |
: backing_vector_(std::move(list)) | |
{ | |
} | |
functional_vector& add(T value) | |
{ | |
backing_vector_.push_back(value); | |
return *this; | |
} | |
functional_vector adding(T value) const | |
{ | |
auto augmented_vector(backing_vector_); | |
augmented_vector.push_back(value); | |
return functional_vector(augmented_vector); | |
} | |
functional_vector& add_range(const std::vector<T>& vector) | |
{ | |
backing_vector_.insert(backing_vector_.end(), | |
vector.begin(), | |
vector.end()); | |
return *this; | |
} | |
functional_vector adding_range(const std::vector<T>& vector) const | |
{ | |
auto augmented_vector(backing_vector_); | |
augmented_vector.reserve(augmented_vector.size() + vector.size()); | |
augmented_vector.insert(augmented_vector.end(), | |
vector.begin(), | |
vector.end()); | |
return functional_vector(augmented_vector); | |
} | |
functional_vector& add_range(const std::initializer_list<T>& list) | |
{ | |
backing_vector_.insert(backing_vector_.end(), | |
list.begin(), | |
list.end()); | |
return *this; | |
} | |
functional_vector adding_range(const std::initializer_list<T>& list) const | |
{ | |
auto augmented_vector(backing_vector_); | |
augmented_vector.reserve(augmented_vector.size() + list.size()); | |
augmented_vector.insert(augmented_vector.end(), | |
list.begin(), | |
list.end()); | |
return functional_vector(augmented_vector); | |
} | |
template <typename U> | |
functional_vector<U> map(const std::function<U(T)>& transform) const | |
{ | |
std::vector<U> transformed_vector; | |
transformed_vector.reserve(backing_vector_.size()); | |
std::transform(backing_vector_.begin(), | |
backing_vector_.end(), | |
std::back_inserter(transformed_vector), | |
transform); | |
return functional_vector<U>(transformed_vector); | |
} | |
functional_vector& filter(const std::function<bool(T)>& predicate_to_keep) | |
{ | |
backing_vector_.erase(std::remove_if(backing_vector_.begin(), | |
backing_vector_.end(), | |
[&](const auto& element) { return !predicate_to_keep(element); }), | |
backing_vector_.end()); | |
return *this; | |
} | |
functional_vector filtered(const std::function<bool(T)>& predicate_to_keep) const | |
{ | |
std::vector<T> filtered_vector; | |
filtered_vector.reserve(backing_vector_.size()); | |
std::copy_if(backing_vector_.begin(), | |
backing_vector_.end(), | |
std::back_inserter(filtered_vector), | |
predicate_to_keep); | |
return functional_vector(filtered_vector); | |
} | |
functional_vector& reverse() | |
{ | |
std::reverse(backing_vector_.begin(), backing_vector_.end()); | |
return *this; | |
} | |
functional_vector reversed() const | |
{ | |
std::vector<T> reversed_vector(backing_vector_); | |
std::reverse(reversed_vector.begin(), reversed_vector.end()); | |
return functional_vector(reversed_vector); | |
} | |
template <typename U> | |
struct functional_vector_tuple { | |
public: | |
T first; | |
U second; | |
}; | |
template <typename U> | |
functional_vector<functional_vector_tuple<U>> zip(const std::vector<U>& vector) const | |
{ | |
assert(backing_vector_.size() == vector.size()); | |
std::vector<functional_vector_tuple<U>> combined_vector; | |
combined_vector.reserve(vector.size()); | |
for (auto i = 0; i < backing_vector_.size(); i++) { | |
combined_vector.push_back({ backing_vector_[i], vector[i] }); | |
} | |
return functional_vector<functional_vector_tuple<U>>(combined_vector); | |
} | |
template <typename U> | |
functional_vector<functional_vector_tuple<U>> zip(const functional_vector<U>& vector) const | |
{ | |
assert(backing_vector_.size() == vector.size()); | |
std::vector<functional_vector_tuple<U>> combined_vector; | |
combined_vector.reserve(vector.size()); | |
for (auto i = 0; i < backing_vector_.size(); i++) { | |
combined_vector.push_back({ backing_vector_[i], vector[i] }); | |
} | |
return functional_vector<functional_vector_tuple<U>>(combined_vector); | |
} | |
functional_vector& sort(const std::function<bool(T, T)>& comparison_predicate) | |
{ | |
std::sort(backing_vector_.begin(), | |
backing_vector_.end(), | |
comparison_predicate); | |
return *this; | |
} | |
functional_vector& sort_ascending() | |
{ | |
return sort(std::less_equal<T>()); | |
} | |
functional_vector& sort_descending() | |
{ | |
return sort(std::greater_equal<T>()); | |
} | |
functional_vector sorted(const std::function<bool(T, T)>& comparison_predicate) const | |
{ | |
auto sorted_vector(backing_vector_); | |
std::sort(sorted_vector.begin(), | |
sorted_vector.end(), | |
comparison_predicate); | |
return functional_vector(sorted_vector); | |
} | |
functional_vector sorted_ascending() const | |
{ | |
return sorted(std::less_equal<T>()); | |
} | |
functional_vector sorted_descending() const | |
{ | |
return sorted(std::greater_equal<T>()); | |
} | |
size_t size() const | |
{ | |
return backing_vector_.size(); | |
} | |
T& operator[] (size_t index) | |
{ | |
assert(index < size()); | |
return backing_vector_[index]; | |
} | |
const T& operator[] (size_t index) const | |
{ | |
assert(index < size()); | |
return backing_vector_[index]; | |
} | |
typename std::vector<T>::iterator begin() | |
{ | |
return backing_vector_.begin(); | |
} | |
typename std::vector<T>::const_iterator begin() const | |
{ | |
return backing_vector_.begin(); | |
} | |
typename std::vector<T>::iterator end() | |
{ | |
return backing_vector_.end(); | |
} | |
typename std::vector<T>::const_iterator end() const | |
{ | |
return backing_vector_.end(); | |
} | |
functional_vector& for_each(const std::function<void(T)>& operation) | |
{ | |
std::for_each(backing_vector_.begin(), | |
backing_vector_.end(), | |
operation); | |
return *this; | |
} | |
std::optional<size_t> first_index_of(const T& element) const | |
{ | |
auto it = std::find(backing_vector_.begin(), | |
backing_vector_.end(), | |
element); | |
if (it != backing_vector_.end()) { | |
return std::distance(backing_vector_.begin(), it); | |
} | |
return std::nullopt; | |
} | |
std::optional<size_t> last_index_of(const T& element) const | |
{ | |
auto it = std::find(backing_vector_.rbegin(), | |
backing_vector_.rend(), | |
element); | |
if (it != backing_vector_.rend()) { | |
return std::distance(it, backing_vector_.rend()) - 1; | |
} | |
return std::nullopt; | |
} | |
std::vector<size_t> all_indices_of(const T& element) const | |
{ | |
std::vector<size_t> indices; | |
auto it = std::find(backing_vector_.begin(), | |
backing_vector_.end(), | |
element); | |
while (it != backing_vector_.end()) { | |
indices.push_back(std::distance(backing_vector_.begin(), it)); | |
++it; | |
it = std::find(it, backing_vector_.end(), element); | |
} | |
return indices; | |
} | |
functional_vector& remove_at(size_t index) | |
{ | |
assert(index < size()); | |
backing_vector_.erase(begin() + index); | |
return *this; | |
} | |
functional_vector removing_at(size_t index) const | |
{ | |
assert(index < size()); | |
auto copy(backing_vector_); | |
copy.erase(copy.begin() + index); | |
return functional_vector(copy); | |
} | |
functional_vector& remove_last() | |
{ | |
if (size() == 0) { | |
return *this; | |
} | |
return remove_at(size() - 1); | |
} | |
functional_vector removing_last() const | |
{ | |
if (size() == 0) { | |
return *this; | |
} | |
return removing_at(size() - 1); | |
} | |
functional_vector& remove_first() | |
{ | |
if (size() == 0) { | |
return *this; | |
} | |
return remove_at(0); | |
} | |
functional_vector removing_first() const | |
{ | |
if (size() == 0) { | |
return *this; | |
} | |
return removing_at(0); | |
} | |
functional_vector& insert_at(size_t index, const T& element) | |
{ | |
assert(index < size()); | |
backing_vector_.insert(begin() + index, element); | |
return *this; | |
} | |
functional_vector inserting_at(size_t index, const T& element) const | |
{ | |
assert(index < size()); | |
auto copy(backing_vector_); | |
copy.insert(copy.begin() + index, element); | |
return functional_vector(copy); | |
} | |
private: | |
std::vector<T> backing_vector_; | |
}; | |
#endif /* functional_vector_h */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MIT License | |
// | |
// Copyright (c) 2021 Ioannis Kaliakatsos | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in all | |
// copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
// SOFTWARE. | |
#include <iostream> | |
#include <sstream> | |
#include "functional_vector.h" | |
std::string convert_to_string(double value); | |
double convert_to_double(std::string value); | |
int main(int argc, const char * argv[]) { | |
// -------------------------------------------------------------------------------------------------------- | |
// a vector of all double values | |
auto double_values = functional_vector(std::vector { 1.3, 2.5, -2.1 }); | |
// -------------------------------------------------------------------------------------------------------- | |
// mapping all elements of the vector using a lambda | |
// contains: { "1.3", "2.5", "-2.1" } | |
auto string_values_using_lambda = double_values.map<std::string>([](const auto& value) -> std::string { | |
std::ostringstream strs; | |
strs << value; | |
auto str = strs.str(); | |
return str; | |
}); | |
// -------------------------------------------------------------------------------------------------------- | |
// mapping all elements of the vector using a local function | |
// contains: { "1.3", "2.5", "-2.1" } | |
auto string_values_using_local_function = double_values.map<std::string>(convert_to_string); | |
// -------------------------------------------------------------------------------------------------------- | |
// round trip should recover the original double values | |
// contains: { 1.3, 2.5, -2.1 } | |
auto double_values_recovered = double_values | |
.map<std::string>(convert_to_string) | |
.map<double>(convert_to_double); | |
// -------------------------------------------------------------------------------------------------------- | |
// create an augmented vector leaves the original untouched | |
// contains: { 1.3, 2.5, -2.1, 5, -10.3, 1.1, -2, 7 } | |
auto augmented_double_values = double_values | |
.adding(5) | |
.adding(-10.3) | |
.adding_range({ 1.1, -2, 7}); | |
// -------------------------------------------------------------------------------------------------------- | |
// keep elements which match a predicate | |
// contains: { 1.3, 2.5, 5, 1.1, 7 } | |
auto positive_numbers = augmented_double_values.filtered([](const auto& value) { return value >= 0; }); | |
// -------------------------------------------------------------------------------------------------------- | |
// reverse the vector elements | |
// contains: { 2.5, 5, 7 } | |
auto larger_than_2 = augmented_double_values.filtered([](const auto& value) { return value >= 2; }); | |
// contains: { 7, 5, 2.5 } | |
auto larger_than_2_reversed = larger_than_2.reversed(); | |
// -------------------------------------------------------------------------------------------------------- | |
// combine (zip) two vectors for simultaneous processing | |
auto kid_names = functional_vector<std::string>({ "Mary", "John", "Brian" }); | |
/* contains: | |
{ | |
(2.5, "Mary"), | |
(5, "John"), | |
(7, "Brian") | |
} | |
*/ | |
auto kid_ages_and_names = larger_than_2.zip(kid_names); | |
// -------------------------------------------------------------------------------------------------------- | |
// apply a sorting predicate (returning "true" means the first element should appear before the second one) | |
// contains: { 1.1, 1.3, 2.5, 5, 7 } | |
auto positive_numbers_sorted = positive_numbers.sorted([](const auto& n1, const auto& n2) { return n1 < n2; }); | |
// equivalently | |
// positive_numbers_sorted = positive_numbers.sorted_ascending(); | |
// -------------------------------------------------------------------------------------------------------- | |
// retrieve the size of the vector | |
// returns 5 | |
auto number_count = positive_numbers_sorted.size(); | |
// -------------------------------------------------------------------------------------------------------- | |
// subscripting and assignment | |
// contains: { 1.1, 1.3, 1.8, 5, 7 } | |
positive_numbers_sorted[2] = 1.8; | |
// -------------------------------------------------------------------------------------------------------- | |
// using iterators to loop through elements | |
// prints the following | |
// "Mary is 2.5 years old" | |
// "John is 5 years old" | |
// "Brian is 7 years old" | |
for (const auto& age_and_name: kid_ages_and_names) { | |
std::cout << age_and_name.second << " is " << age_and_name.first << " years old" << std::endl; | |
} | |
// -------------------------------------------------------------------------------------------------------- | |
// index searching operations | |
auto classroom = functional_vector({ "Jake", "Mike", "John", "Mary", "Tania", "John", "Kate", "Keith", "Jackie" }); | |
if (auto index = classroom.first_index_of("John")) { | |
// prints "First occurrence of John is at index 2" | |
std::cout << "First occurrence of John is at index " << index.value() << std::endl; | |
} | |
if (!classroom.first_index_of("Jessica").has_value()) { | |
// prints "Jessica is not found" | |
std::cout << "Jessica is not found" << std::endl; | |
} | |
if (auto index = classroom.last_index_of("John")) { | |
// prints "Last occurrence of John is at index 5" | |
std::cout << "Last occurrence of John is at index " << index.value() << std::endl; | |
} | |
if (!classroom.last_index_of("Tom").has_value()) { | |
// prints "Tom is not found" | |
std::cout << "Tom is not found" << std::endl; | |
} | |
// prints the following | |
// "John is found at index 2" | |
// "John is found at index 5" | |
for(const auto& index: classroom.all_indices_of("John")) { | |
std::cout << "John is found at index " << index << std::endl; | |
} | |
// -------------------------------------------------------------------------------------------------------- | |
// execute a function for every element | |
// prints the following | |
// "The student's name is Jake" | |
// "The student's name is Mike" | |
// "The student's name is John" | |
// "The student's name is Mary" | |
// "The student's name is Tania" | |
// "The student's name is John" | |
// "The student's name is Kate" | |
// "The student's name is Keith" | |
// "The student's name is Jackie" | |
classroom.for_each([](const auto& student) { | |
std::cout << "The student's name is " << student << std::endl; | |
}); | |
// -------------------------------------------------------------------------------------------------------- | |
// remove single elements | |
// returns { 1.1, 1.8, 5, 7 } | |
positive_numbers_sorted.removing_at(1); | |
// returns: { 1.1, 1.3, 1.8, 5 } | |
positive_numbers_sorted.removing_last(); | |
// -------------------------------------------------------------------------------------------------------- | |
// inserting single elements | |
// contains: { 1.1, 2.5, 1.3, 1.8, 5, 7 } | |
positive_numbers_sorted.inserting_at(1, 2.5); | |
return 0; | |
} | |
std::string convert_to_string(double value) { | |
std::ostringstream strs; | |
strs << value; | |
auto str = strs.str(); | |
return str; | |
} | |
double convert_to_double(std::string value) { | |
return atof(value.data()); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment