I am creating multiple indexes (ie, that use different keys) into a large collection of objects. The objects can change, and the collection can shrink and grow. My thought
I recognize that your use case is probably different from the one I contrived for my example, and without a lot more details I won't be able to make one that matches (I also think that if you had a lot of details you would be able to find a solution yourself).
#include <iostream>
#include <map>
#include <set>
#include <memory>
#include <stdexcept>
using namespace std;
class Thing
{
public:
Thing() = default;
Thing(const Thing &other) = default;
Thing(int i, string p, string d) : id(i), desc(d), part(p) {}
int id;
string desc;
string part;
};
ostream &operator<<(ostream &out, const Thing &t)
{
if (&t == NULL) out << "(NULL)"; // don't judge me
else out << t.id << ": " << t.part << " (" << t.desc << ")";
}
class Datastore
{
public:
Datastore() = default;
shared_ptr<const Thing> Add(const Thing &t)
{
if (!(index_bydesc.find(t.desc) == index_bydesc.end() &&
index_bypart.find(t.part) == index_bypart.end() &&
index_byid.find(t.id) == index_byid.end()))
throw runtime_error("Non-unique insert");
shared_ptr<const Thing> newt = make_shared<const Thing>(t);
weak_ptr<const Thing> weak = weak_ptr<const Thing>(newt);
index_bydesc[newt->desc] = weak;
index_bypart[newt->part] = weak;
index_byid[newt->id] = weak;
store.insert(newt);
return newt;
}
void Remove(const Thing &t)
{
shared_ptr<const Thing> p = FindBy_Desc(t.desc);
store.erase(p);
index_bydesc.erase(p->desc);
index_bypart.erase(p->part);
index_byid.erase(p->id);
}
shared_ptr<const Thing> FindBy_Desc(string desc)
{
map<string, weak_ptr<const Thing> >::iterator iter = index_bydesc.find(desc);
if (iter == index_bydesc.end()) return shared_ptr<const Thing>();
return iter->second.lock();
}
// index accessors for part and quantity omitted
private:
std::set<shared_ptr<const Thing> > store;
std::map<string, weak_ptr<const Thing> > index_bydesc;
std::map<string, weak_ptr<const Thing> > index_bypart;
std::map<int, weak_ptr<const Thing> > index_byid;
};
int main() {
Datastore d;
d.Add(Thing(1, "TRNS-A", "Automatic transmission"));
d.Add(Thing(2, "SPKPLG", "Spark plugs"));
d.Add(Thing(3, "HOSE-S", "Small hoses"));
d.Add(Thing(4, "HOSE-L", "Large hoses"));
d.Add(Thing(5, "BATT-P", "Primary battery (14.5v nominal)"));
d.Add(Thing(6, "BATT-S", "Secondary batteries (1.5v nominal)"));
d.Add(Thing(7, "CRKSFT", "Crank shaft"));
d.Add(Thing(8, "REAC-F", "Fusion reactor power source"));
cout << *d.FindBy_Desc("Crank shaft") << endl;
d.Remove(*d.FindBy_Desc("Crank shaft"));
cout << *d.FindBy_Desc("Crank shaft") << endl;
return 0;
}
list<Thing>
as indices for non-unique fields, not just maps containing Thing
.std::map
. std::unordered_map
is an alternative with better (constant amortized) access times for huge data structures (the same as std::unordered_set
).Here's one way.
std::vector<unique_ptr>
to hold the data items (to ensure that the addresses don't change when the vector resizes) and then containers holding reference_wrappers (copyable references) to make the indexes.
compilable example:
#include <map>
#include <vector>
#include <set>
#include <string>
#include <functional>
#include <memory>
#include <iostream>
struct Thing {
Thing(std::string name, int value)
: _name { std::move(name) }
, _value { value }
{}
const std::string& name() const {
return _name;
}
void write(std::ostream& os) const {
os << "{ " << _name << " : " << _value << " }";
}
private:
std::string _name;
int _value;
};
inline std::ostream& operator<<(std::ostream& os, const Thing& t) {
t.write(os);
return os;
}
struct multi_index
{
using multi_by_name_index = std::multimap<std::string, std::reference_wrapper<Thing>>;
void add_thing(std::string name, int value) {
// todo: checks to ensure that indexes won't be violated
// add a new thing to the main store
_main_store.emplace_back(new Thing{std::move(name), value});
// store a reference to it in each index
auto& new_thing = *(_main_store.back().get());
_name_index.emplace(new_thing.name(), new_thing);
}
using multi_by_name_range = std::pair<multi_by_name_index::const_iterator, multi_by_name_index::const_iterator>;
multi_by_name_range get_all_by_name(const std::string name) const
{
return _name_index.equal_range(name);
}
private:
std::vector<std::unique_ptr<Thing>> _main_store;
std::multimap<std::string, std::reference_wrapper<Thing>> _name_index;
};
using namespace std;
int main()
{
multi_index mi;
mi.add_thing("bob", 8);
mi.add_thing("ann", 4);
mi.add_thing("bob", 6);
auto range = mi.get_all_by_name("bob");
for( ; range.first != range.second ; ++range.first) {
cout << range.first->second << endl;
}
return 0;
}
expected output:
{ bob : 8 }
{ bob : 6 }