Recommend C++ container to hold top 20 minimum values - c++

In SQL there is the feature to say something like
SELECT TOP 20 distance FROM dbFile ORDER BY distance ASC
If my SQL is correct with, say 10,000 records, this should return the 20 smallest distances in my databse.
I don't have a database. I have a 100,000-element simple array.
Is there a C++ container, Boost, MFC or STL that provides simple code for a struct like
struct closest{
int ID;
double distance;
closest():ID(-1), distance(std::numeric_limits<double>::max( )){}
};
Where I can build a sorted by distance container like
boost::container::XXXX<closest> top(20);
And then have a simple
top.replace_if(closest(ID,Distance));
Where the container will replace the entry with the current highest distance in my container with my new entry if it is less than the current highest distance in my container.
I am not worried about speed. I like elegant clean solutions where containers and code do all the heavy lifting.
EDIT. Addendum after all the great answers received.
What I really would of liked to have found, due to its elegance. Is a sorted container that I could create with a container size limit. In my case 20. Then I could push or insert to my hearts content a 100 000 items or more. But. There is always a but. The container would of maintained the max size of 20 by replacing or not inserting an item if its comparator value was not within the lowest 20 values.
Yes. I know now from all these answers that via programming and tweaking existing containers the same effect can be achieved. Perhaps when the next round of suggestions for the C & C++ standards committee sits. We could suggest. Self sorting (which we kind of have already) and self size limiting containers.

What you need is to have a maxheap of size 20. Recall that the root of your heap will be the largest value in the heap.
This heap will contain the records with smallest distance that you have encountered so far. For the first 20 out of 10000 values you just push to the heap.
At this point you iterate through the rest of the records and for each record, you compare it to the root of your heap.
Remember that the root of your heap is basically the very worst of the very best.(The record with the largest distance, among the 20 records with the shortest distance you have encountered so far).
If the value you are considering is not worth keeping (its distance is larger that the root of your tree), ignore that record and just keep moving.
Otherwise you pop your heap (get rid of the root) and push the new value in. The priority queue will automatically put its record with the largest distance on the root again.
Once you keep doing this over the entire set of 10000 values, you will be left with the 20 records that have the smallest distance, which is what you want.
Each push-pop takes constant O(1) time, iterating through all inputs of N is O(n) so this is a Linear solution.
Edit:
I thought it would be useful to show my idea in C++ code. This is a toy example, you can write a generic version with templates but I chose to keep it simple and minimalistic:
#include <iostream>
#include <queue>
using namespace std;
class smallestElements
{
private:
priority_queue<int,std::vector<int>,std::less<int> > pq;
int maxSize;
public:
smallestElements(int size): maxSize(size)
{
pq=priority_queue<int, std::vector<int>, std::less<int> >();
}
void possiblyAdd(int newValue)
{
if(pq.size()<maxSize)
{
pq.push(newValue);
return;
}
if(newValue < pq.top())
{
pq.pop(); //get rid of the root
pq.push(newValue); //priority queue will automatically restructure
}
}
void printAllValues()
{
priority_queue<int,std::vector<int>,std::less<int> > cp=pq;
while(cp.size()!=0)
{
cout<<cp.top()<<" ";
cp.pop();
}
cout<<endl;
}
};
How you use this is really straight forward. basically in your main function somewhere you will have:
smallestElements se(20); //we want 20 smallest
//...get your stream of values from wherever you want, call the int x
se.possiblyAdd(x); //no need for bounds checking or anything fancy
//...keep looping or potentially adding until the end
se.printAllValues();//shows all the values in your container of smallest values
// alternatively you can write a function to return all values if you want

If this is about filtering the 20 smallest elements from a stream on the fly, then a solution based on std::priority_queue (or std::multiset) is the way to go.
However, if it is about finding the 20 smallest elements in a given arraym I wouldn't go for a special container at all, but simply the algorithm std::nth_element - a partial sorting algorithm that will give you the n smallest elements - EDIT: or std::partial_sort (thanks Jarod42) if the elements also have to be sorted. It has linear complexity and it's just a single line to write (+ the comparison operator, which you need in any case):
#include <vector>
#include <iostream>
#include <algorithm>
struct Entry {
int ID;
double distance;
};
std::vector<Entry> data;
int main() {
//fill data;
std::nth_element(data.begin(), data.begin() + 19, data.end(),
[](auto& l, auto& r) {return l.distance < r.distance; });
std::cout << "20 elements with smallest distance: \n";
for (size_t i = 0; i < 20; ++i) {
std::cout << data[i].ID << ":" << data[i].distance << "\n";
}
std::cout.flush();
}
If you don't want to change the order of your original array, you would have to make a copy of the whole array first though.

My first idea would be using a std::map or std::set with a custom comparator for this (edit: or even better, a std::priority_queue as mentioned in the comments).
Your comparator does your sorting.
You essentially add all your elements to it. After an element has been added, check whether there are more than n elements inside. If there are, remove the last one.

I am not 100% sure, that there is no more elegant solution, but even std::set is pretty pretty.
All you have to do is to define a proper comparator for your elements (e.g. a > operator) and then do the following:
std::set<closest> tops(arr, arr+20)
tops.insert(another);
tops.erase(tops.begin());

I would use nth_element like #juanchopanza suggested before he deleted it.
His code looked like:
bool comp(const closest& lhs, const closest& rhs)
{
return lhs.distance < rhs.distance;
}
then
std::vector<closest> v = ....;
nth_element(v.begin(), v.begin() + 20, v.end(), comp);
Though if it was only ever going to be twenty elements then I would use a std::array.

Just so you can all see what I am currently doing which seems to work.
struct closest{
int base_ID;
int ID;
double distance;
closest(int BaseID, int Point_ID,
double Point_distance):base_ID(BaseID),
ID(Point_ID),distance(Point_distance){}
closest():base_ID(-1), ID(-1),
distance(std::numeric_limits<double>::max( )){}
bool operator<(const closest& rhs) const
{
return distance < rhs.distance;
}
};
void calc_nearest(void)
{
boost::heap::priority_queue<closest> svec;
for (int current_gift = 0; current_gift < g_nVerticesPopulated; ++current_gift)
{ double best_distance=std::numeric_limits<double>::max();
double our_distance=0.0;
svec.clear();
for (int all_other_gifts = 0; all_other_gifts < g_nVerticesPopulated;++all_other_gifts)
{
our_distance = distanceVincenty(g_pVertices[current_gift].lat,g_pVertices[current_gift].lon,g_pVertices[all_other_gifts].lat,g_pVertices[all_other_gifts].lon);
if (our_distance != 0.0)
{
if (our_distance < best_distance) // don't bother to push and sort if the calculated distance is greater than current 20th value
svec.push(closest(g_pVertices[current_gift].ID,g_pVertices[current_gift].ID,our_distance));
if (all_other_gifts%100 == 0)
{
while (svec.size() > no_of_closest_points_to_calculate) svec.pop(); // throw away any points above no_of_closest_points_to_calculate
closest t = svec.top(); // the furthest of the no_of_closest_points_to_calculate points for optimisation
best_distance = t.distance;
}
}
}
std::cout << current_gift << "\n";
}
}
As you can see. I have 100 000 lat & long points draw on an openGl sphere.
I am calculating each point against every other point and only retaining currently the closest 20 points. There is some primitive optimisation going on by not pushing a value if it is bigger than the 20th closest point.
As I am used to Prolog taking hours to solve something I am not worried about speed. I shall run this overnight.
Thanks to all for your help.
It is much appreciated.
Still have to audit the code and results but happy that I am moving in the right direction.

I have posted a number of approaches to the similar problem of retrieving the top 5 minimum values recently here:
https://stackoverflow.com/a/33687969/1025391
There are implementations that keep a specific number of smallest or greatest items from an input vector in different ways. The nth_element algorithm performs a partial sort, the priority queue maintains a heap, the set a binary search tree, and the deque- and vector-based approaches just remove an element based on a (linear) min/max search.
It should be fairly easy to implement a custom comparison operator and to adapt the number of items to keep n.
Here's the code (refactored based off the other post):
#include <algorithm>
#include <functional>
#include <queue>
#include <set>
#include <vector>
#include <random>
#include <iostream>
#include <chrono>
template <typename T, typename Compare = std::less<T>>
std::vector<T> filter_nth_element(std::vector<T> v, typename std::vector<T>::size_type n) {
auto target = v.begin()+n;
std::nth_element(v.begin(), target, v.end(), Compare());
std::vector<T> result(v.begin(), target);
return result;
}
template <typename T, typename Compare = std::less<T>>
std::vector<T> filter_pqueue(std::vector<T> v, typename std::vector<T>::size_type n) {
std::vector<T> result;
std::priority_queue<T, std::vector<T>, Compare> q;
for (auto i: v) {
q.push(i);
if (q.size() > n) {
q.pop();
}
}
while (!q.empty()) {
result.push_back(q.top());
q.pop();
}
return result;
}
template <typename T, typename Compare = std::less<T>>
std::vector<T> filter_set(std::vector<T> v, typename std::vector<T>::size_type n) {
std::set<T, Compare> s;
for (auto i: v) {
s.insert(i);
if (s.size() > n) {
s.erase(std::prev(s.end()));
}
}
return std::vector<T>(s.begin(), s.end());
}
template <typename T, typename Compare = std::less<T>>
std::vector<T> filter_deque(std::vector<T> v, typename std::vector<T>::size_type n) {
std::deque<T> q;
for (auto i: v) {
q.push_back(i);
if (q.size() > n) {
q.erase(std::max_element(q.begin(), q.end(), Compare()));
}
}
return std::vector<T>(q.begin(), q.end());
}
template <typename T, typename Compare = std::less<T>>
std::vector<T> filter_vector(std::vector<T> v, typename std::vector<T>::size_type n) {
std::vector<T> q;
for (auto i: v) {
q.push_back(i);
if (q.size() > n) {
q.erase(std::max_element(q.begin(), q.end(), Compare()));
}
}
return q;
}
template <typename Clock = std::chrono::high_resolution_clock>
struct stopclock {
std::chrono::time_point<Clock> start;
stopclock() : start(Clock::now()) {}
~stopclock() {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - start);
std::cout << "elapsed: " << elapsed.count() << "ms\n";
}
};
std::vector<int> random_data(std::vector<int>::size_type n) {
std::mt19937 gen{std::random_device()()};
std::uniform_int_distribution<> dist;
std::vector<int> out(n);
for (auto &i: out)
i = dist(gen);
return out;
}
int main() {
std::vector<int> data = random_data(1000000);
stopclock<> sc;
std::vector<int> result = filter_nth_element(data, 5);
std::cout << "smallest values: ";
for (auto i : result) {
std::cout << i << " ";
}
std::cout << "\n";
std::cout << "largest values: ";
result = filter_nth_element<int, std::greater<int>>(data, 5);
for (auto i : result) {
std::cout << i << " ";
}
std::cout << "\n";
}
Example output is:
$ g++ test.cc -std=c++11 && ./a.out
smallest values: 4433 2793 2444 4542 5557
largest values: 2147474453 2147475243 2147477379 2147469788 2147468894
elapsed: 123ms
Note that in this case only the position of the nth element is accurate with respect to the order imposed by the provided comparison operator. The other elements are guaranteed to be smaller/greater or equal to that one however, depending on the comparison operator provided. That is, the top n min/max elements are returned, but they are not correctly sorted.
Don't expect the other algorithms to produce results in a specific order either. (While the approaches using priority queue and set actually produce sorted output, their results have the opposite order).
For reference:
http://en.cppreference.com/w/cpp/algorithm/nth_element
http://en.cppreference.com/w/cpp/container/priority_queue
http://en.cppreference.com/w/cpp/container/set
http://en.cppreference.com/w/cpp/algorithm/max_element

I actually have 100 000 Lat & Lon points drawn on a opengl sphere. I want to work out the 20 nearest points to each of the 100 000 points. So we have two loops to pick each point then calculate that point against every other point and save the closest 20 points.
This reads as if you want to perform a k-nearest neighbor search in the first place. For this, you usually use specialized data structures (e.g., a binary search tree) to speed up the queries (especially when you are doing 100k of them).
For spherical coordinates you'd have to do a conversion to a cartesian space to fix the coordinate wrap-around. Then you'd use an Octree or kD-Tree.
Here's an approach using the Fast Library for Approximate Nearest Neighbors (FLANN):
#include <vector>
#include <random>
#include <iostream>
#include <flann/flann.hpp>
#include <cmath>
struct Point3d {
float x, y, z;
void setLatLon(float lat_deg, float lon_deg) {
static const float r = 6371.; // sphere radius
float lat(lat_deg*M_PI/180.), lon(lon_deg*M_PI/180.);
x = r * std::cos(lat) * std::cos(lon);
y = r * std::cos(lat) * std::sin(lon);
z = r * std::sin(lat);
}
};
std::vector<Point3d> random_data(std::vector<Point3d>::size_type n) {
static std::mt19937 gen{std::random_device()()};
std::uniform_int_distribution<> dist(0, 36000);
std::vector<Point3d> out(n);
for (auto &i: out)
i.setLatLon(dist(gen)/100., dist(gen)/100.);
return out;
}
int main() {
// generate random spherical point cloud
std::vector<Point3d> data = random_data(1000);
// generate query point(s) on sphere
std::vector<Point3d> query = random_data(1);
// convert into library datastructures
auto mat_data = flann::Matrix<float>(&data[0].x, data.size(), 3);
auto mat_query = flann::Matrix<float>(&query[0].x, query.size(), 3);
// build KD-Tree-based index data structure
flann::Index<flann::L2<float> > index(mat_data, flann::KDTreeIndexParams(4));
index.buildIndex();
// perform query: approximate nearest neighbor search
int k = 5; // number of neighbors to find
std::vector<std::vector<int>> k_indices;
std::vector<std::vector<float>> k_dists;
index.knnSearch(mat_query, k_indices, k_dists, k, flann::SearchParams(128));
// k_indices now contains for each query point the indices to the neighbors in the original point cloud
// k_dists now contains for each query point the distances to those neighbors
// removed printing of results for brevity
}
You'd receive results similar to this one (click to enlarge):
For reference:
https://en.wikipedia.org/wiki/Nearest_neighbor_search
https://en.wikipedia.org/wiki/Octree
https://en.wikipedia.org/wiki/Kd-tree
http://www.cs.ubc.ca/research/flann/

Heap is the data structure that you need. pre-C++11 stl only had functions which managed heap data in your own arrays. Someone mentioned that boost has a heap class, but you don't need to go so far as to use boost if your data is simple integers. stl's heap will do just fine. And, of course, the algorithm is to order the heap so that the highest value is the first one. So with each new value, you push it on the heap, and, once the heap reaches 21 elements in size, you pop the first value from the heap. This way whatever 20 values remain are always the 20 lowest.

Related

How to get an element (struct) in an array by a value in the struct

Let's say I have this struct containing an integer.
struct Element
{
int number;
Element(int number)
{
this->number = number;
}
};
And I'm gonna create a vector containing many Element structs.
std::vector<Element> array;
Pretend that all the Element structs inside array have been initialized and have their number variable set.
My question is how can I instantly get an element based on the variable number?
It is very possible to do it with a for loop, but I'm currently focusing on optimization and trying to avoid as many for loops as possible.
I want it to be as instant as getting by index:
Element wanted_element = array[wanted_number]
There must be some kind of overloading stuff, but I don't really know what operators or stuff to overload.
Any help is appreciated :)
With comparator overloading implemented, std::find is available to help:
#include <iostream>
#include <vector>
#include <algorithm>
struct Element
{
int number;
Element(int number)
{
this->number = number;
}
bool operator == (Element el)
{
return number == el.number;
}
};
int main()
{
std::vector<Element> array;
std::vector<int> test;
for(int i=0;i<100;i++)
{
auto t = clock();
test.push_back(t);
array.push_back(Element(t));
}
auto valToFind = test[test.size()/2];
std::cout << "value to find: "<<valToFind<<std::endl;
Element toFind(valToFind);
auto it = std::find(array.begin(),array.end(),toFind);
if(it != array.end())
std::cout<<"found:" << it->number <<std::endl;
return 0;
}
The performance on above method depends on the position of the searched value in the array. Non-existing values & last element values will take the highest time while first element will be found quickest.
If you need to optimize searching-time, you can use another data-structure instead of vector. For example, std::map is simple to use here and fast on average (compared to latest elements of vector-version):
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
struct Element
{
int number;
Element(){ number = -1; }
Element(int number)
{
this->number = number;
}
};
int main()
{
std::map<int,Element> mp;
std::vector<int> test;
for(int i=0;i<100;i++)
{
auto t = clock();
test.push_back(t);
mp[t]=Element(t);
}
auto valToFind = test[test.size()/2];
std::cout << "value to find: "<<valToFind<<std::endl;
auto it = mp.find(valToFind);
if(it != mp.end())
std::cout<<"found:" << it->second.number <<std::endl;
return 0;
}
If you have to use vector, you can still use the map near the vector to keep track of its elements the same way above method just with extra memory space & extra deletions/updates on the map whenever vector is altered.
Anything you invent would with success would look like hashing or a tree in the end. std::unordered_map uses hashing while std::map uses red-black tree.
If range of values are very limited, like 0-to-1000 only, then simply saving its index in a second vector would be enough:
vec[number] = indexOfVector;
Element found = array[vec[number]];
If range is full and if you don't want to use any map nor unordered_map, you can still use a direct-mapped caching on the std::find method. On average, simple caching should decrease total time taken on duplicated searches (how often you search same item?).

Deciding two integer list contains common elements or not (c++)

I have a running-time issue about my c++ program. The program doing millions of times comparing two integer list contains common elements or not. I don't need to learn which elements is common. I wrote the method below but it doesn't look efficient. I need to speed up program. So, what is the best way of doing this process or c++ have any built-in method which is doing this compare efficiently?
bool compareHSAndNewSet(list<int> hs , list<int> newset){
bool isCommon = false;
for(int x : hs){
for(int y : newset){
if(x == y){isCommon = true; break;}
}
if(isCommon == true) {break;}
}
return isCommon;
}
Hint: I don't now maybe this means something. The first input of the function (in the code hs) is ordered.
I was curious about the various strategies, so I made the simple benchmark below.
However, I wouldn't try to sort the second container; comparing all the data inside a container and moving them around seems to be overkill just to find one element in the intersection.
The program gives these results on my computer (Intel(R) Core(TM) i7-10875H CPU # 2.30GHz):
vectors: 1.41164
vectors (dichotomic): 0.0187354
lists: 12.0402
lists (dichotomic): 13.4844
If we ignore that the first container is sorted and iterate its elements in order, we can see that a simpler container (a vector here) with adjacent storage of the elements if much better than multiple elements spread in memory (a list here): 1.41164 s over 12.0402 (8.5 speedup).
But if we consider that the first container is sorted (as told in the question), a dichotomic approach can improve even more the situation.
The best case (dichotomic approach on vectors) is far better than the original case (in order approach on lists): 0.0187354 s over 12.0402 s (642 speedup).
Of course, all of this depends on many other factors (sizes of datasets, distributions of the values...); this is just a micro benchmark, and a specific application could behave differently.
Note that in the question, the parameters were passed by value; this will probably cause some unneeded copies (except if a move operation is used at the call site, but I would find that uncommon for such a function). I switched to pass-by-reference-on-const instead.
Note also that a dichotomic approach on a list is a pessimisation (no random access for the iterators, so it's still linear but more complicated than the simplest linear approach).
edit: my original code was wrong, thanks to #bitmask I changed it; it does not change the general idea.
/**
g++ -std=c++17 -o prog_cpp prog_cpp.cpp \
-pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion \
-O3 -DNDEBUG -march=native
**/
#include <list>
#include <vector>
#include <algorithm>
#include <chrono>
#include <random>
#include <tuple>
#include <iostream>
template<typename Container>
bool
compareHSAndNewSet(const Container &hs,
const Container &newset)
{
for(const auto &elem: newset)
{
const auto it=std::find(cbegin(hs), cend(hs), elem);
if(it!=cend(hs))
{
return true; // found common element
}
}
return false; // no common element
}
template<typename Container>
bool
compareHSAndNewSet_dichotomic(const Container &hs,
const Container &newset)
{
for(const auto &elem: newset)
{
if(std::binary_search(cbegin(hs), cend(hs), elem))
{
return true; // found common element
}
}
return false; // no common element
}
std::tuple<std::vector<int>, // hs
std::vector<int>> // newset
prepare_vectors()
{
static auto rnd_gen=std::default_random_engine {std::random_device{}()};
constexpr auto sz=10'000;
auto distr=std::uniform_int_distribution<int>{0, 10*sz};
auto hs=std::vector<int>{};
auto newset=std::vector<int>{};
for(auto i=0; i<sz; ++i)
{
hs.emplace_back(distr(rnd_gen));
newset.emplace_back(distr(rnd_gen));
}
std::sort(begin(hs), end(hs));
return {hs, newset};
}
std::tuple<std::list<int>, // hs
std::list<int>> // newset
prepare_lists(const std::vector<int> &hs,
const std::vector<int> &newset)
{
return {std::list(cbegin(hs), cend(hs)),
std::list(cbegin(newset), cend(newset))};
}
double // seconds (1e-6 precision) since 1970/01/01 00:00:00 UTC
get_time()
{
const auto now=std::chrono::system_clock::now().time_since_epoch();
const auto us=std::chrono::duration_cast<std::chrono::microseconds>(now);
return 1e-6*double(us.count());
}
int
main()
{
constexpr auto generations=100;
constexpr auto iterations=1'000;
auto duration_v=0.0;
auto duration_vd=0.0;
auto duration_l=0.0;
auto duration_ld=0.0;
for(auto g=0; g<generations; ++g)
{
const auto [hs_v, newset_v]=prepare_vectors();
const auto [hs_l, newset_l]=prepare_lists(hs_v, newset_v);
for(auto i=-1; i<iterations; ++i)
{
const auto t0=get_time();
const auto comp_v=compareHSAndNewSet(hs_v, newset_v);
const auto t1=get_time();
const auto comp_vd=compareHSAndNewSet_dichotomic(hs_v, newset_v);
const auto t2=get_time();
const auto comp_l=compareHSAndNewSet(hs_l, newset_l);
const auto t3=get_time();
const auto comp_ld=compareHSAndNewSet_dichotomic(hs_l, newset_l);
const auto t4=get_time();
if((comp_v!=comp_vd)||(comp_v!=comp_l)||(comp_v!=comp_ld))
{
std::cerr << "comparison mismatch\n";
}
if(i>=0) // first iteration is dry-run (warmup)
{
duration_v+=t1-t0;
duration_vd+=t2-t1;
duration_l+=t3-t2;
duration_ld+=t4-t3;
}
}
}
std::cout << "vectors: " << duration_v << '\n';
std::cout << "vectors (dichotomic): " << duration_vd << '\n';
std::cout << "lists: " << duration_l << '\n';
std::cout << "lists (dichotomic): " << duration_ld << '\n';
return 0;
}
You can try sorting the list and use set_intersection.
bool compareHSAndNewSet(list<int> hs , list<int> newset){
hs.sort();
newset.sort();
list<int>::iterator i;
list<int> commonElts (hs.size()+newset.size());
i = std::set_intersection(hs.begin(), hs.end(), newset.begin(), newset.end(), commonElts.begin());
commonElts.resize(i - commonElts.begin());
return (v.size() == 0);
I'd use std::unordered_map<> to add the first list to, then check each element of the second list if it exists in the map. This would end up iterating each list once, doing length(first) insertions and length(second) lookups on the map.
std::unordered_map<> should have a lookup and insertion complexity of O(1), though worst case could end up with O(n). (I believe).

What container to store unique values?

I've got the following problem. I have a game which runs on average 60 frames per second. Each frame I need to store values in a container and there must be no duplicates.
It probably has to store less than 100 items per frame, but the number of insert-calls will be alot more (and many rejected due to it has to be unique). Only at the end of the frame do I need to traverse the container. So about 60 iterations of the container per frame, but alot more insertions.
Keep in mind the items to store are simple integer.
There are a bunch of containers I can use for this but I cannot make up my mind what to pick. Performance is the key issue for this.
Some pros/cons that I've gathered:
vector
(PRO): Contigous memory, a huge factor.
(PRO): Memory can be reserved first, very few allocations/deallocations afterwards
(CON): No alternative than to traverse the container (std::find) each insert() to find unique keys? The comparison is simple though (integers) and the whole container can probably fit the cache
set
(PRO): Simple, clearly meant for this
(CON): Not constant insert-time
(CON): Alot of allocations/deallocations per frame
(CON): Not contigous memory. Traversing a set of hundreds of objects means jumping around alot in memory.
unordered_set
(PRO): Simple, clearly meant for this
(PRO): Average case constant time insert
(CON): Seeing as I store integers, hash operation is probably alot more expensive than anything else
(CON): Alot of allocations/deallocations per frame
(CON): Not contigous memory. Traversing a set of hundreds of objects means jumping around alot in memory.
I'm leaning on going the vector-route because of memory access patterns, even though set is clearly meant for this issue. The big issue that is unclear to me is whether traversing the vector for each insert is more costly than the allocations/deallocations (especially considering how often this must be done) and the memory lookups of set.
I know ultimately it all comes down to profiling each case, but if nothing else than as a headstart or just theoretically, what would probably be best in this scenario? Are there any pros/cons I might've missed aswell?
EDIT: As I didnt mention, the container is cleared() at the end of each frame
I did timing with a few different methods that I thought were likely candidates. Using std::unordered_set was the winner.
Here are my results:
Using UnorderedSet: 0.078s
Using UnsortedVector: 0.193s
Using OrderedSet: 0.278s
Using SortedVector: 0.282s
Timing is based on the median of five runs for each case.
compiler: gcc version 4.9.1
flags: -std=c++11 -O2
OS: ubuntu 4.9.1
CPU: Intel(R) Core(TM) i5-4690K CPU # 3.50GHz
Code:
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <iostream>
#include <random>
#include <set>
#include <unordered_set>
#include <vector>
using std::cerr;
static const size_t n_distinct = 100;
template <typename Engine>
static std::vector<int> randomInts(Engine &engine,size_t n)
{
auto distribution = std::uniform_int_distribution<int>(0,n_distinct);
auto generator = [&]{return distribution(engine);};
auto vec = std::vector<int>();
std::generate_n(std::back_inserter(vec),n,generator);
return vec;
}
struct UnsortedVectorSmallSet {
std::vector<int> values;
static const char *name() { return "UnsortedVector"; }
UnsortedVectorSmallSet() { values.reserve(n_distinct); }
void insert(int new_value)
{
auto iter = std::find(values.begin(),values.end(),new_value);
if (iter!=values.end()) return;
values.push_back(new_value);
}
};
struct SortedVectorSmallSet {
std::vector<int> values;
static const char *name() { return "SortedVector"; }
SortedVectorSmallSet() { values.reserve(n_distinct); }
void insert(int new_value)
{
auto iter = std::lower_bound(values.begin(),values.end(),new_value);
if (iter==values.end()) {
values.push_back(new_value);
return;
}
if (*iter==new_value) return;
values.insert(iter,new_value);
}
};
struct OrderedSetSmallSet {
std::set<int> values;
static const char *name() { return "OrderedSet"; }
void insert(int new_value) { values.insert(new_value); }
};
struct UnorderedSetSmallSet {
std::unordered_set<int> values;
static const char *name() { return "UnorderedSet"; }
void insert(int new_value) { values.insert(new_value); }
};
int main()
{
//using SmallSet = UnsortedVectorSmallSet;
//using SmallSet = SortedVectorSmallSet;
//using SmallSet = OrderedSetSmallSet;
using SmallSet = UnorderedSetSmallSet;
auto engine = std::default_random_engine();
std::vector<int> values_to_insert = randomInts(engine,10000000);
SmallSet small_set;
namespace chrono = std::chrono;
using chrono::system_clock;
auto start_time = system_clock::now();
for (auto value : values_to_insert) {
small_set.insert(value);
}
auto end_time = system_clock::now();
auto& result = small_set.values;
auto sum = std::accumulate(result.begin(),result.end(),0u);
auto elapsed_seconds = chrono::duration<float>(end_time-start_time).count();
cerr << "Using " << SmallSet::name() << ":\n";
cerr << " sum=" << sum << "\n";
cerr << " elapsed: " << elapsed_seconds << "s\n";
}
I'm going to put my neck on the block here and suggest that the vector route is probably most efficient when the size is 100 and the objects being stored are integral values. The simple reason for this is that set and unordered_set allocate memory for each insert whereas the vector needn't more than once.
You can increase search performance dramatically by keeping the vector ordered, since then all searches can be binary searches and therefore complete in log2N time.
The downside is that the inserts will take a tiny fraction longer due to the memory moves, but it sounds as if there will be many more searches than inserts, and moving (average) 50 contiguous memory words is an almost instantaneous operation.
Final word:
Write the correct logic now. Worry about performance when the users are complaining.
EDIT:
Because I couldn't help myself, here's a reasonably complete implementation:
template<typename T>
struct vector_set
{
using vec_type = std::vector<T>;
using const_iterator = typename vec_type::const_iterator;
using iterator = typename vec_type::iterator;
vector_set(size_t max_size)
: _max_size { max_size }
{
_v.reserve(_max_size);
}
/// #returns: pair of iterator, bool
/// If the value has been inserted, the bool will be true
/// the iterator will point to the value, or end if it wasn't
/// inserted due to space exhaustion
auto insert(const T& elem)
-> std::pair<iterator, bool>
{
if (_v.size() < _max_size) {
auto it = std::lower_bound(_v.begin(), _v.end(), elem);
if (_v.end() == it || *it != elem) {
return make_pair(_v.insert(it, elem), true);
}
return make_pair(it, false);
}
else {
return make_pair(_v.end(), false);
}
}
auto find(const T& elem) const
-> const_iterator
{
auto vend = _v.end();
auto it = std::lower_bound(_v.begin(), vend, elem);
if (it != vend && *it != elem)
it = vend;
return it;
}
bool contains(const T& elem) const {
return find(elem) != _v.end();
}
const_iterator begin() const {
return _v.begin();
}
const_iterator end() const {
return _v.end();
}
private:
vec_type _v;
size_t _max_size;
};
using namespace std;
BOOST_AUTO_TEST_CASE(play_unique_vector)
{
vector_set<int> v(100);
for (size_t i = 0 ; i < 1000000 ; ++i) {
v.insert(int(random() % 200));
}
cout << "unique integers:" << endl;
copy(begin(v), end(v), ostream_iterator<int>(cout, ","));
cout << endl;
cout << "contains 100: " << v.contains(100) << endl;
cout << "contains 101: " << v.contains(101) << endl;
cout << "contains 102: " << v.contains(102) << endl;
cout << "contains 103: " << v.contains(103) << endl;
}
As you said you have many insertions and only one traversal, I’d suggest to use a vector and push the elements in regardless of whether they are unique in the vector. This is done in O(1).
Just when you need to go through the vector, then sort it and remove the duplicate elements. I believe this can be done in O(n) as they are bounded integers.
EDIT: Sorting in linear time through counting sort presented in this video. If not feasible, then you are back to O(n lg(n)).
You will have very little cache miss because of the contiguity of the vector in memory, and very few allocations (especially if you reserve enough memory in the vector).

interval range tree datastructure c++

I have a requirement where I have to update the color of a graphical frontend based on some attribute value.The attribute value has different ranges ....say -30 to -45, -60 to -80 and so on.....So, I needed a datastaructure where I could store these ranges(prefill them)....And When I do determine the point , I would like to know the range in which this point falls either in O(1) Time or O(logN) time....So, My Query would consist of a single point and the output should be a unique range containing that point...
I am confused between range trees and segment trees....i want to build the tree on top of c++ stl map.
What you need is called interval tree. http://en.wikipedia.org/wiki/Interval_tree.
Unfortunately you can't use std::set<> to get O(log N) insert, remove and query, because tree node needs to contain additional data. You can read about them here http://syedwaqarahmad.webs.com/documents/t.cormen-_introduction_to_algorithms_3rd_edition.pdf
chapter 14.3.
Instead you can use boost. It has interval container library.
http://www.boost.org/doc/libs/1_46_1/libs/icl/doc/html/index.html
Maybe this library can help you:
https://github.com/ekg/intervaltree
If I understand you correctly, you can du this quite easily with std::set:
#include <iostream>
#include <set>
struct Interval {
int min;
int max;
};
struct ComInt {
bool operator()(const Interval& lhs, const Interval& rhs){
return lhs.max < rhs.min;
}
};
std::set<Interval, ComInt> intervals = { { -10, -5 }, { -4, 4 }, { 5, 10 } };
int main() {
int point = 3;
Interval tmp = { point, point };
auto result=intervals.find(tmp);
if (result != intervals.end()) {
std::cout << "Min:" << result->min << " - Max:" << result->max << std::endl;
} else {
std::cout << "No matching Interval found" << std::endl;
}
}
of course you should build a wrapper class around it

Storing set of non-overlapping ranges and finding whether a value is present in any one of the ranges strictly

I have a set of ranges :
Range1 ---- (0-10)
Range2 ---- (15-25)
Range3 ---- (100-1000) and likewise.
I would like to have only the bounds stored since storing large ranges , it would be efficient.
Now I need to search for a number , say 14 . In this case, 14 is not present in any of the ranges whereas (say a number) 16 is present in one of the ranges.
I would need a function
bool search(ranges, searchvalue)
{
if searchvalues present in any of the ranges
return true;
else
return false;
}
How best can this be done ? This is strictly non-overlapping and the important criteria is that the search has to be most efficient.
One possibility is to represent ranges as a pair of values and define a suitable comparison function. The following should consider one range less than another if its bounds are smaller and there is no overlap. As a side effect, this comparison function doesn't let you store overlapping ranges in the set.
To look up an integer n, it can be treated as a range [n, n]
#include <set>
#include <iostream>
typedef std::pair<int, int> Range;
struct RangeCompare
{
//overlapping ranges are considered equivalent
bool operator()(const Range& lhv, const Range& rhv) const
{
return lhv.second < rhv.first;
}
};
bool in_range(const std::set<Range, RangeCompare>& ranges, int value)
{
return ranges.find(Range(value, value)) != ranges.end();
}
int main()
{
std::set<Range, RangeCompare> ranges;
ranges.insert(Range(0, 10));
ranges.insert(Range(15, 25));
ranges.insert(Range(100, 1000));
std::cout << in_range(ranges, 14) << ' ' << in_range(ranges, 16) << '\n';
}
The standard way to handle this is through so called interval trees. Basically, you augment an ordinary red-black tree with additional information so that each node x contains an interval x.int and the key of x is the low endpoint, x.int.low, of the interval. Each node x also contains a value x.max, which is the maximum value of any interval endpoint stored in the subtree rooted at x. Now you can determine x.max given interval x.int and the max values of node x’s children as follows:
x.max = max(x.int.high, x.left.max, x.right.max)
This implies that, with n intervals, insertion and deletion run in O(lg n) time. In fact, it is possible to update the max attributes after a rotation in O(1) time. Here is how to search for an element i in the interval tree T
INTERVAL-SEARCH(T, i)
x = T:root
while x is different from T.nil and i does not overlap x.int
if x.left is different from T.nil and x.left.max is greater than or equal to i.low
x = x.left
else
x = x.right
return x
The complexity of the search procedure is O(lg n) as well.
To see why, see CLRS Introduction to algorithms, chapter 14 (Augmenting Data Structures).
You could put something together based on std::map and std::map::upper_bound:
Assuming you have
std::map<int,int> ranges; // key is start of range, value is end of range
You could do the following:
bool search(const std::map<int,int>& ranges, int searchvalue)
{
auto p = ranges.upper_bound(searchvalue);
// p->first > searchvalue
if(p == ranges.begin())
return false;
--p; // p->first <= searchvalue
return searchvalue >= p->first && searchvalue <= p->second;
}
I'm using C++11, if you use C++03, you'll need to replace "auto" by the proper iterator type.
EDIT: replaced pseudo-code inrange() by explicit expression in return statement.
A good solution can be as the following. It is O(log(n)).
A critical condition is non overlapping ranges.
#include <set>
#include <iostream>
#include <assert.h>
template <typename T> struct z_range
{
T s , e ;
z_range ( T const & s,T const & e ) : s(s<=e?s:e), e(s<=e?e:s)
{
}
};
template <typename T> bool operator < (z_range<T> const & x , z_range<T> const & y )
{
if ( x.e<y.s)
return true ;
return false ;
}
int main(int , char *[])
{
std::set<z_range<int> > x;
x.insert(z_range<int>(20,10));
x.insert(z_range<int>(30,40));
x.insert(z_range<int>(5,9));
x.insert(z_range<int>(45,55));
if (x.find(z_range<int>(15,15)) != x.end() )
std::cout << "I have it" << std::endl ;
else
std::cout << "not exists" << std::endl ;
}
If you have ranges ri = [ai, bi]. You could sort all the ai and put them into an array and search for x having x >= ai and ai minimal using binary search.
After you found this element you have to check whether x <= bi.
This is suitable if you have big numbers. If, on the other hand, you have either a lot of memory or small numbers, you can think about putting those ranges into a bool array. This may be suitable if you have a lot of queries:
bool ar[];
ar[0..10] = true;
ar[15..25] = true;
// ...
bool check(int searchValues) {
return ar[searchValues];
}
Since the ranges are non-overlapping the only thing left to do is performing a search within the range that fit's the value. If the values are ordered within the ranges, searching is even simpler. Here is a summary of search algorithms.
With respect to C++ you also can use algorithms from STL or even functions provided by the containers, e. g. set::find.
So, this assumes the ranges are continous (i.e range [100,1000] contains all numbers between 100 and 1000):
#include <iostream>
#include <map>
#include <algorithm>
bool is_in_ranges(std::map<int, int> ranges, int value)
{
return
std::find_if(ranges.begin(), ranges.end(),
[&](std::pair<int,int> pair)
{
return value >= pair.first && value <= pair.second;
}
) != ranges.end();
}
int main()
{
std::map<int, int> ranges;
ranges[0] = 10;
ranges[15] = 25;
ranges[100] = 1000;
std::cout << is_in_ranges(ranges, 14) << '\n'; // 0
std::cout << is_in_ranges(ranges, 16) << '\n'; // 1
}
In C++03, you'd need a functor instead of a lambda function:
struct is_in {
is_in(int x) : value(x) {}
bool operator()(std::pair<int, int> pair)
{
return value >= pair.first && value <= pair.second;
}
private:
int value;
};
bool is_in_ranges(std::map<int, int> ranges, int value)
{
return
std::find_if(ranges.begin(), ranges.end(), is_in(value)) != ranges.end();
}