As an exercise to dive into the C++ standard library functions, particularly in <algorithm>, I've decided to write a simple Dictionary class that attempts to utilize as many standard library functions as possible. I would really appreciate feedback on:
- General standard library usage: Am I using the correct functions, or are there some that could make this smoother?
- Throwing exceptions: I haven't really written programs that throw exceptions, so tips on my usage of them here would be great
- Any other recommendations are welcome
dictionary.hpp
#ifndef _DICTIONARY_HPP
#define _DICTIONARY_HPP
#include <vector>
#include <iostream>
#include <algorithm>
namespace dev {
template <typename K, typename V>
class Dict {
private:
std::vector<std::pair<K, V>> dict;
public:
Dict() {}
// Construct a dictionary from the passed key value pairs
Dict(std::vector<std::pair<K, V>> data) { dict = data; }
// Construct a dictionary from a vector of keys and a vector of items. Must be the same length.
Dict(const std::vector<K>& keys, const std::vector<V>& values) {
if (!(keys.size() == values.size())) throw std::length_error("Keys and Values must be the same sized vector.");
std::transform(keys.begin(), keys.end(), values.begin(), std::back_inserter(dict),
[](const auto& key, const auto& value) {
return std::pair<K, V>(key, value);
});
}
// Removes all the elements from the dictionary
void Clear(void) { dict.clear(); }
// Returns a copy of the dictionary
Dict Copy(void) {
std::vector<std::pair<K, V>> copy;
copy.reserve(dict.size());
std::copy(dict.begin(), dict.end(), std::back_inserter(copy));
return copy;
}
// Returns a dictionary with the specified keys and value
static Dict<K, V> FromKeys(std::vector<K> keys, V value) {
Dict<K, V> result;
std::for_each(keys.begin(), keys.end(),
[&](const K& key) {
result.Add(key, value);
});
return result;
}
// Returns a value of the specified key
V Get(K key) {
auto it = std::find_if(dict.begin(), dict.end(),
[&](const auto& pair) {
return pair.first == key;
});
if (it != dict.end()) {
return it->second;
}
throw std::out_of_range("Key not found.");
}
// Returns a vector containing a pair for each key and value
std::vector<std::pair<K, V>> Items(void) { return dict; }
// Returns a vector containing the dict's keys
std::vector<K> Keys(void) {
std::vector<K> keys;
keys.reserve(dict.size());
std::transform(dict.begin(), dict.end(), std::back_inserter(keys),
[](const auto& pair) {
return pair.first;
});
return keys;
}
// Removes the element with the specified key
void Pop(K key) {
auto it = std::find_if(dict.begin(), dict.end(),
[&](const auto& pair) {
return pair.first == key;
});
if (it != dict.end()) {
dict.erase(it);
dict.shrink_to_fit();
}
}
// Returns the value of the specified key. If the key does not exist: insert the key, with the specified value
V SetDefault(K key, V defaultValue) {
auto it = std::find_if(dict.begin(), dict.end(),
[&](const auto& pair) {
return pair.first == key;
});
if (it != dict.end()) {
return it->second;
}
dict.emplace_back(key, defaultValue);
return defaultValue;
}
// Updates the dictionary with the specified key-value pairs
void Update(std::vector<std::pair<K, V>> items) {
dict = std::move(items);
dict.shrink_to_fit();
}
// Returns a vector of all the values in the dictionary
std::vector<V> Values(void) {
std::vector<V> values;
values.reserve(dict.size());
std::transform(dict.begin(), dict.end(), std::back_inserter(values),
[](const auto& pair) {
return pair.second;
});
return values;
}
// Adds a key value pair to the dict, if key already exists then overwrite previous value
void Add(K key, V value) {
auto it = std::find_if(dict.begin(), dict.end(),
[&](const auto& pair) {
return pair.first == key;
});
if (it != dict.end()) {
it->second = value;
} else {
dict.emplace_back(key, value);
}
}
// Like FromKeys, but accepts iterators instead
template <typename InputIterator>
void Insert(InputIterator start, InputIterator end, const V& value) {
dict.reserve(dict.size() + std::distance(start, end));
std::for_each(start, end, [&](const K& key) {
dict.emplace_back(key, value);
});
}
// Prints each key value pair to the console
void Print(void) {
std::for_each(dict.begin(), dict.end(),
[](const auto& pair) {
std::cout << "(" << pair.first << ", " << pair.second << ")" << std::endl;
});
}
};
}
#endif // _DICTIONARY_HPP
main.cpp (example usage)
#include <iostream>
#include "dictionary.hpp"
int main(void) {
dev::Dict<char, int> dict;
dict.Add('a', 1);
dict.Add('b', 2);
dict.Add('c', 3);
dict.Add('d', 4);
dict.Add('e', 5);
dev::Dict<char, int> dict2 = dict.Copy();
dict.Print();
std::cout << std::endl;
dict2.Print();
auto dict3 = dev::Dict<std::string, int>::FromKeys({"abc", "def"}, 1);
std::cout << std::endl;
dict3.Print();
dict2.Update(dict.Items());
dev::Dict<char, int> dict4(dict.Keys(), dict.Values());
dict4.SetDefault('e', 6);
std::cout << dict.Get('a') << std::endl;
return 0;
}
Makefile (for those interested)
compile:
@-g++ -std=c++2b -Wall -Wextra -Wpedantic main.cpp -o main
run:
@-./main
clean:
@-rm main
all: compile run clean
# @- hides make command output (still allows program output)