I've got bored, so I decided to write a code using ancient knowledge of sentinels.
Sentinel based find
Basically what we do is put value that is needed to find at the end, and then write an endless loop. Breaking from loop happens in the body of it, so one branch (which probably won't make much, since it is a loop branch, but still) is eliminated. Requires the type to have noexcept
copy assign and noexcept
copy construct (in C++17, since I guess T t = value
will have guaranteed copy elision).
Is there any possibilities to make this faster without compromising functionality?
#ifndef FAST_FIND_HPP
#define FAST_FIND_HPP
#include <type_traits>
#include <utility>
#include <iterator>
namespace impl
{
template <typename BidirIt, typename T>
BidirIt find(std::true_type, BidirIt first, BidirIt last, const T& value)
{
if (first == last)
{
return last;
}
auto real_last = std::prev(last);
if (*real_last == value)
{
return real_last;
}
auto copy = *real_last;
*real_last = value;
auto it = first;
for (; ; ++it)
{
if (*it == value)
break;
}
*real_last = copy;
if (it == real_last)
{
return last;
}
return it;
}
template <typename BidirIt, typename T>
BidirIt find(std::false_type, BidirIt first, BidirIt last, const T& value)
{
return std::find(first, last, value);
}
}
template <typename BidirIt, typename T>
BidirIt find(BidirIt first, BidirIt last, const T& value)
{
return impl::find(std::integral_constant<bool, std::is_nothrow_copy_assignable<std::remove_reference_t<decltype(*first)> >::value> {},
first, last, value);
//I'd like to allow T to be different from *first
}
#endif
Usage:
#include "fast_find.hpp"
#include "randomsequence.h"
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v(10'000'000);
random_int_generator<> gen(0, 10'000); //generate values in range
gen(v.begin(), v.end());
int n;
std::cin >> n;
auto it = find(v.begin(), v.end(), n);
if (it != v.end())
{
std::cout << "Found it!\n" << *it << '\n';
}
else
{
std::cout << "Sadness\n";
}
}
Here is the random_int_generator<>
, do not review this, I posted it to make it easier for you to benchmark if needed:
#pragma once
#include <random>
template <typename T = int, class Engine = std::random_device>
class random_int_generator
{
Engine engine;
std::uniform_int_distribution<T> dist;
public:
random_int_generator(T begin = 0, T end = std::numeric_limits<T>::max()) :
dist(begin, end)
{
}
template <typename OutputIt>
void operator()(OutputIt first, OutputIt last)
{
while (first != last)
{
*first++ = dist(engine);
}
}
T operator()()
{
return dist(engine);
}
};
You can find current version in my github repo (it's a mess, I hope you won't mind that).
makefile
in the github repo is written by someone who've never writtenmakefile
's before, so be careful. \$\endgroup\$