C++ вычислить и отсортировать вектор во время компиляции
у меня есть class A
что есть std::vector<int>
в качестве атрибута.
A
должен заполнить этот вектор, когда экземпляр это.
Расчет может занять некоторое время, и я хотел бы знать, если:
- это можно сделать во время компиляции.
- вектор также может быть отсортирован во время компиляции
Я не знаком с метапрограммированием, и я не нашел способа сделать это сейчас. Это не вопрос конкретной ОС.
здесь это :
#include "A.h"
#define SIZEV 100
A::A()
{
fillVector();
}
void A::fillVector()
{
// m_vector is an attribute of class "A"
// EXPECTATION 1 : fill the vector with the following calculation at compile time
const int a=5;
const int b=7;
const int c=9;
for(int i=0;i<SIZEV;i++){
for(int j=0;j<SIZEV;j++){
for(int k=0;k<SIZEV;k++){
this->m_vector.push_back(a*i+b*j+c*k);
}
}
}
// EXPECTATION 2 : sort the vector as compile time
}
std::vector<int> A::getVector() const
{
return m_vector;
}
void A::setVector(const std::vector<int> &vector)
{
m_vector = vector;
}
и main.cpp
(Qt приложение, но не имеет значения):
#include <QCoreApplication>
#include "A.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
A a;
auto vec = a.getVector();
// use vec, etc ...
return app.exec();
}
6 ответов
A std::vector<int>
нет constexpr
конструкторы (поскольку динамическое выделение памяти запрещено для constexpr
). Таким образом, вы не можете сортировать std::vector<int>
во время компиляции.
вы можете создать std::array<int, N>
во время компиляции для постоянной N
, но вам придется написать свою собственную процедуру сортировки, потому что std::sort
не constexpr
либо.
вы также можете написать ускорение.MPL вектор или список времени компиляции и используйте sort
режим что. Но это не будет масштабироваться так же хорошо, как std::array
.
другим углом атаки может быть сохранение вектора в static
переменная и сортировка при инициализации программы. Ваша программа просто занимает немного больше времени, чтобы начать, но это не повлияет на остальную часть его основной функциональности.
так как сортировка O(N log N)
, у вас может быть даже двухэтапная сборка и запись отсортированного вектора в файл, и либо скомпилировать / связать его с вашей основной программой, либо загрузить его в O(N)
at запуск программы в static
переменной.
классический подход для длительных вычислений, которые могут быть предварительно вычислены, заключается в вычислении результата как части процесса сборки, генерирующего .cpp
это жестко кодирует результат (на платформах, которые имеют встроенные ресурсы, они также могут использоваться). .
однако здесь расчет чрезвычайно прост, медленная часть, вероятно, просто распределение, которое, если вы хотите сохранить данные в std::vector
, и произойдет во время выполнения. Если вы можете жить с Массив C-style вы можете поместить все это в исполняемый файл, как описано выше, но это приведет к увеличению исполняемого файла на 4 Мб, а замедление, вызванное его загрузкой с диска, компенсирует любое преимущество скорости предварительного расчета.
IOW: предварительный расчет во время сборки имеет смысл, когда вычисление дорого, а выход мал. Ваш случай находится в прямой противоположности спектра, поэтому я бы его избегал.
пока это можно сделать (видео) вы не должны делать это. Это займет много времени для компиляции.
компилятор не предназначен для быстрой и эффективной числовой массовой обработки. Пока ограничьте работу во время компиляции относительно простыми вещами, не сортируя 10 миллионов элементов.
даже если вы напишете "совместимый" код, Большинство компиляторов сегодня взорвутся на вас. Код, который я написал, умирает довольно рано, хотя я пытался быть осторожным с моим ограничение глубины рекурсии.
во всяком случае, для потомков:
template<class T>struct tag{using type=T;};
template<class Tag>using type_t=typename Tag::type;
template<int...Xs> struct values { constexpr values() {}; };
template<int...Xs> constexpr values<Xs...> values_v = {};
template<class...Vs> struct append;
template<class...Vs> using append_t=type_t<append<Vs...>>;
template<class...Vs> constexpr append_t<Vs...> append_v = {};
template<> struct append<>:tag<values<>>{};
template<int...Xs>struct append<values<Xs...>>:tag<values<Xs...>>{};
template<int...Lhs, int...Rhs, class...Vs>
struct append<values<Lhs...>,values<Rhs...>,Vs...>:
tag<append_t<values<Lhs...,Rhs...>,Vs...>>
{};
template<int...Lhs>
constexpr values<Lhs...> simple_merge( values<Lhs...>, values<> ) { return {}; }
template<int...Rhs>
constexpr values<Rhs...> simple_merge( values<>, values<Rhs...> ) { return {}; }
constexpr values<> simple_merge( values<>, values<> ) { return {}; }
template<int L0, int...Lhs, int R0, int...Rhs>
constexpr auto simple_merge( values<L0, Lhs...>, values<R0, Rhs...> )
-> std::conditional_t<
(R0 < L0),
append_t< values<R0>, decltype( simple_merge( values<L0,Lhs...>{}, values<Rhs...>{} ) ) >,
append_t< values<L0>, decltype( simple_merge( values<Lhs...>{}, values<R0, Rhs...>{} ) ) >
> {
return {};
}
template<class Lhs, class Rhs>
using simple_merge_t = decltype( simple_merge( Lhs{}, Rhs{} ) );
template<class Lhs, class Rhs>
constexpr simple_merge_t<Lhs, Rhs> simple_merge_v = {};
template<class Values, size_t I> struct split
{
private:
using one = split<Values, I/2>;
using two = split<typename one::rhs, I-I/2>;
public:
using lhs = append_t< typename one::lhs, typename two::lhs >;
using rhs = typename two::rhs;
};
template<class Values, size_t I> using split_t=type_t<split<Values, I>>;
template<class Values> struct split<Values, 0>{
using lhs = values<>;
using rhs = Values;
};
template<int X0, int...Xs> struct split<values<X0, Xs...>, 1> {
using lhs = values<X0>;
using rhs = values<Xs...>;
};
template<class Values, size_t I> using before_t = typename split<Values, I>::lhs;
template<class Values, size_t I> using after_t = typename split<Values, I>::rhs;
template<size_t I>using index_t=std::integral_constant<size_t, I>;
template<int I>using int_t=std::integral_constant<int, I>;
template<int I>constexpr int_t<I> int_v={};
template<class Values> struct front;
template<int X0, int...Xs> struct front<values<X0, Xs...>>:tag<int_t<X0>>{};
template<class Values> using front_t=type_t<front<Values>>;
template<class Values> constexpr front_t<Values> front_v = {};
template<class Values, size_t I>
struct get:tag<front_t< after_t<Values, I> >> {};
template<class Values, size_t I> using get_t = type_t<get<Values, I>>;
template<class Values, size_t I> constexpr get_t<Values, I> get_v = {};
template<class Values>
struct length;
template<int...Xs>
struct length<values<Xs...>>:tag<index_t<sizeof...(Xs)>> {};
template<class Values> using length_t = type_t<length<Values>>;
template<class Values> constexpr length_t<Values> length_v = {};
template<class Values> using front_half_t = before_t< Values, length_v<Values>/2 >;
template<class Values> constexpr front_half_t<Values> front_half_v = {};
template<class Values> using back_half_t = after_t< Values, length_v<Values>/2 >;
template<class Values> constexpr back_half_t<Values> back_half_v = {};
template<class Lhs, class Rhs>
struct least : tag< std::conditional_t< (Lhs{}<Rhs{}), Lhs, Rhs > > {};
template<class Lhs, class Rhs> using least_t = type_t<least<Lhs, Rhs>>;
template<class Lhs, class Rhs>
struct most : tag< std::conditional_t< (Lhs{}>Rhs{}), Lhs, Rhs > > {};
template<class Lhs, class Rhs> using most_t = type_t<most<Lhs, Rhs>>;
template<class Values>
struct pivot {
private:
using a = get_t<Values, 0>;
using b = get_t<Values, length_v<Values>/2>;
using c = get_t<Values, length_v<Values>-1>;
using d = most_t< least_t<a,b>, most_t< least_t<b,c>, least_t<a,c> > >;
public:
using type = d;
};
template<int X0, int X1>
struct pivot<values<X0, X1>>: tag< most_t< int_t<X0>, int_t<X1> > > {};
template<class Values> using pivot_t = type_t<pivot<Values>>;
template<class Values> constexpr pivot_t<Values> pivot_v = {};
template<int P>
constexpr values<> lower_split( int_t<P>, values<> ) { return {}; }
template<int P, int X0>
constexpr std::conditional_t< (X0<P), values<X0>, values<> > lower_split( int_t<P>, values<X0> ) { return {}; }
template<int P, int X0, int X1, int...Xs >
constexpr auto lower_split( int_t<P>, values<X0, X1, Xs...> )
-> append_t<
decltype(lower_split( int_v<P>, front_half_v<values<X0, X1, Xs...>> )),
decltype(lower_split( int_v<P>, back_half_v<values<X0, X1, Xs...>> ))
>{ return {}; }
template<int P>
constexpr values<> upper_split( int_t<P>, values<> ) { return {}; }
template<int P, int X0>
constexpr std::conditional_t< (X0>P), values<X0>, values<> > upper_split( int_t<P>, values<X0> ) { return {}; }
template<int P, int X0, int X1, int...Xs>
constexpr auto upper_split( int_t<P>, values<X0, X1, Xs...> )
-> append_t<
decltype(upper_split( int_v<P>, front_half_v<values<X0, X1, Xs...>> )),
decltype(upper_split( int_v<P>, back_half_v<values<X0, X1, Xs...>> ))
>{ return {}; }
template<int P>
constexpr values<> middle_split( int_t<P>, values<> ) { return {}; }
template<int P, int X0>
constexpr std::conditional_t< (X0==P), values<X0>, values<> > middle_split( int_t<P>, values<X0> ) { return {}; }
template<int P, int X0, int X1, int...Xs>
constexpr auto middle_split( int_t<P>, values<X0, X1, Xs...> )
-> append_t<
decltype(middle_split( int_v<P>, front_half_v<values<X0, X1, Xs...>> )),
decltype(middle_split( int_v<P>, back_half_v<values<X0, X1, Xs...>> ))
>{ return {}; }
template<class Values>
using lower_split_t = decltype(lower_split( pivot_v<Values>, Values{} ) );
template<class Values> constexpr lower_split_t<Values> lower_split_v = {};
template<class Values>
using upper_split_t = decltype(upper_split( pivot_v<Values>, Values{} ) );
template<class Values> constexpr upper_split_t<Values> upper_split_v = {};
template<class Values>
using middle_split_t = decltype(middle_split( pivot_v<Values>, Values{} ) );
template<class Values> constexpr middle_split_t<Values> middle_split_v = {};
constexpr values<> simple_merge_sort( values<> ) { return {}; }
template<int X>
constexpr values<X> simple_merge_sort( values<X> ) { return {}; }
template<class Values>
using simple_merge_sort_t = decltype( simple_merge_sort( Values{} ) );
template<class Values>
constexpr simple_merge_sort_t<Values> simple_merge_sort_v = {};
template<int X0, int X1, int...Xs>
constexpr auto simple_merge_sort( values<X0, X1, Xs...> )
->
simple_merge_t<
simple_merge_t<
simple_merge_sort_t<lower_split_t<values<X0, X1, Xs...>>>, simple_merge_sort_t<upper_split_t<values<X0, X1, Xs...>>>
>,
middle_split_t<values<X0, X1, Xs...>>
>
{ return {}; }
template<class Values>constexpr Values cross_add( Values ) { return {}; }
template<class Values>constexpr values<> cross_add( values<>, Values ) { return {}; }
template<int A0, int...B>constexpr values<(B+A0)...> cross_add( values<A0>, values<B...> ) { return {}; }
template<int A0, int A1, int...A, int...B>
constexpr auto cross_add( values<A0, A1, A...>, values<B...>)
-> append_t<
decltype(cross_add( front_half_v<values<A0, A1, A...>>, values_v<B...> ) ),
decltype(cross_add( back_half_v<values<A0, A1, A...>>, values_v<B...> ) )
> { return {}; }
template<class V0, class V1, class V2, class... Vs>
constexpr auto cross_add( V0, V1, V2, Vs... )
-> decltype(
cross_add( cross_add( V0{}, V1{} ), V2{}, Vs{}... )
) { return {}; }
template<class...Vs>
using cross_add_t = decltype( cross_add(Vs{}...) );
template<class...Vs>
constexpr cross_add_t<Vs...> cross_add_v = {};
template<int X, int...Xs>
constexpr values<(X*Xs)...> scale( int_t<X>, values<Xs...> ) { return {}; }
template<class X, class Xs>
using scale_t = decltype( scale(X{}, Xs{}) );
template<class X, class Xs> constexpr scale_t<X,Xs> scale_v = {};
template<int X0, int...Xs> struct generate_values : generate_values<X0-1, X0-1, Xs...> {};
template<int...Xs> struct generate_values<0,Xs...>:tag<values<Xs...>>{};
template<int X0> using generate_values_t = type_t<generate_values<X0>>;
сортировка слияния медианы из трех во время компиляции и генератор перекрестных продуктов. Я мог бы, с усилием, вероятно, значительно уменьшить количество строк.
вероятно, делает это с constexpr std::array
будет быстрее, чем вышеупомянутое решение чистого типа.
данные являются целыми числами от 0
to SIZEV * (a+b+c)
, но число целых чисел равно SIZEV
3. Это плотная группа целых чисел с небольшим диапазоном, поэтому CountingSort идеально (и вам никогда не нужно строить несортированный массив, просто приращение подсчитывает при генерации).
независимо от того, чтобы держать вокруг подсчетов / префиксных сумм,CountingSort абсолютно будет большой победой во время запуска, чтобы отсортировать вектор против других видов, все остальное осталось прежним.
вы можете сохранить компактную форму(размер O(cuberoot (n)) ваших данных в виде вектора префикс суммы, для поиска из m_vector в O (log(cuberoot (n))) времени (двоичный поиск сумм префиксов), где n-длина m_vector. Увидеть ниже.
в зависимости от задержки кэша / памяти, фактически не расширение m_vector может или не может быть выигрышем в производительности. Если нужен диапазон значений, вы можете очень быстро создавать последовательные элементы m_vector на лету, из сумм префиксов.
class A {
// vector<uint16_t> m_counts; // needs to be 32b for SIZEV>=794 (found experimentally).
vector<uint32_t> m_pos; // values are huge: indices into m_vector, up to SIZEV**3 - 1
vector<uint16_t> m_vector; // can be 16b until SIZEV>3121: max val is only (a+b+c) * (SIZEV-1)
}
void A::fillVector()
{
const int a=5;
const int b=7;
const int c=9;
const auto max_val = (SIZEV-1) * (a+b+c);
m_vector.reserve(SIZEV*SIZEV*SIZEV);
m_vector.resize(0);
// or clear it, but that writes tons of mem, unless you use a custom Allocator::construct to leave it uninit
// http://en.cppreference.com/w/cpp/container/vector/resize
m_pos.resize(max_val + 1); // again, ideally avoid zeroing
// but if not, do it before m_counts
m_counts.clear(); // do this one last, so it's hot in cache even if others wasted time writing zeros.
m_counts.resize(max_val + 1); // vector is now zeroed
// Optimization: don't have a separate m_counts.
// zero and count into m_pos, then do prefix summing in-place
// manually strength-reduce the multiplication to addition
// in case the compiler decides it won't, or can't prove it won't overflow the same way
// Not necessary with gcc or clang: they both do this already
for(int kc=c*(SIZEV-1) ; kc >= 0 ; kc-=c) {
for(int jb=b*(SIZEV-1) ; jb >= 0 ; jb-=b) {
for(int ia=a*(SIZEV-1) ; ia >= 0 ; ia-=a) {
m_counts[kc + jb + ia]++;
// do the smallest stride in the inner-most loop, for better cache locality
}
}
}
// write the early elements last, so they'll be hot in the cache when we're done
int val = 0;
uint32_t sum = 0;
for ( auto &count : m_counts ) {
m_vector.insert(m_vector.end(), count, val++);
// count is allowed to be zero for vector::insert(pos, count, value)
m_pos[val] = sum; // build our vector of prefix sums
sum += count;
//count = (sum+=count); // in-place conversion to prefix sums
}
assert(m_vector.size() == SIZEV*SIZEV*SIZEV);
}
или вместо фактического расширения массива 1.6 GB сделайте префикс суммы отсчетов, давая вам вектор начальной позиции запуска этого индекса в качестве элемента в m_vector
. т. е. idx = m_pos[val]; m_vector[idx] == val
. (Это разбивается на val m_count, и повторяет в m_pos
)
в любом случае, вы можете заменить чтение m_vector[i]
с двоичным-поиск i
на m_pos
. Вы ищете самый высокий индекс в m_pos
имеет значение m_vector[i]. (Или что-то в этом роде; у меня может быть ошибка "один на один".)
хэш-таблица не будет работать, потому что вам нужно сопоставить несколько значений i
для каждого числа от 0..(750*(a+b+c)). (Все i
здесь m_vector[i]
имеет то же значение.)
если вы нужен прогон последовательных элементов, генерировать их на лету в буфер tmp. Посмотреть m_pos[i+1]
чтобы увидеть, когда придет следующий элемент с другим значением. (Глядя на m_counts
может сэкономить некоторое вычитание, но вам, вероятно, лучше просто взять различия в m_pos
чтобы инвертировать суммы префиксов, чтобы избежать пропусков кэша / загрязнения кэша от касания 2-го массива.)
на самом деле m_counts
вероятно, не нужно держать вокруг как член класса вообще, просто временный в FillVector. Или FillVector может рассчитывать на m_pos
, и преобразуйте его на месте в префиксные суммы.
в идеале есть что-то умное, что вы можете сделать с шаблонами, чтобы выбрать типы, которые достаточно широки, но не шире, чем необходимо, для m_counts и m_vector. IDK теория чисел, поэтому я не знаю, как доказать, что не будет одного ведра m_counts
переполнить uint16_t
. The в среднем графа будет 750**3 / (750*(5+7+9)) = 26786, и они, безусловно, кластерный в конце m_counts
. На практике sizev=793 может использовать счетчики uint16_t, в то время как SIZEV=794 производит несколько отсчетов > 65536 (спасибо Крису за рабочий пример, где я мог бы легко проверить это).
m_vector
может быть uint16_t
до (SIZEV-1)*(a+b+c) > MAX_UINT16
(65535). т. е. до SIZEV >= 3122, в который момент m_vector
занимает 28,3 гиб ОЗУ.
на величину, равную sizev = 750, m_pos
- это размер кэша 2x L1 (процессор Intel) (750*(5+7+9) * 4B per short = 63000B
). Если компилятор делает хорошая работа и делает двоичный поиск с условным перемещением вместо непредсказуемых инструкций ветви, это может быть довольно быстро. Это, безусловно, сэкономит вам много трафика основной памяти, что ценно, если у вас есть несколько потоков.
кроме того, никогда не прикасайтесь к m_vector
означает, что вы можете обрабатывать размеры проблем, которые потребуют больше памяти, чем у вас есть для хранения списка.
если вы хотите получить действительно творческий с оптимизацией для кэша, когда создание m_counts в первую очередь (с тройным вложенным циклом), имеет самый внутренний цикл идти вперед, а затем назад, а не в том же направлении оба раза. Это будет иметь значение только для чрезвычайно большого SIZEV, или если другой hyperthread оказывает большое давление на кэш.
for(int kc=c*(SIZEV-1) ; kc >= 0 ; kc-=c) {
for(int jb=b*(SIZEV-1) ; jb >= 0 ; jb-=b) {
for(int ia=0 ; ia<SIZEV*a ; ia+=a)
counts[kc + jb + ia]++;
if (! (jb-=b )) break;
for(int ia=a*(SIZEV-1) ; ia >= 0 ; ia-=a)
counts[kc + jb + ia]++;
}
}
обратный отсчет до нуля (с двунаправленными внутренними циклами или без них), скорее всего, будет небольшим выигрышем для начала следующего цикла, прежде чем он станет связанным с памятью, делая большие memsets когда счет становится высоким. Также выигрыш для сканирования через форварды, чтобы сделать префиксные суммы на месте.
мой предыдущий ответ, который, вероятно, в тупик:
есть ли надежда найти формулу замкнутой формы для i
th элемент в отсортированном векторе? Или даже алгоритм O (log i) для его генерации на лету?
Если вам не нужно много последовательных элементов из этого вектора при доступе к нему, это может быть быстрее вычислить на лету. Память медленная, процессор быстрый, поэтому, если вы можете вычислить a[i]
менее чем за 150 тактов вы выходите вперед. (Предполагая, что каждый доступ является пропуском кэша или что не касаясь всей векторной памяти уменьшает пропуски кэша в остальной части вашей программы).
если мы сможем это сделать, мы могли бы теоретически написать отсортированный массив в порядке в первую очередь.
для этого: перетасуйте константы так a <= b <= c
.
0, a, [a*2 .. a*int(b/a)], b, [b + a .. b + a*int((c-b)/a) mixed with b*2 .. b*int(c/b)], c, [some number of b*x + a*y], c+a, [more b*x + a*y], ...
Ok, таким образом, это превращается в комбинаторный беспорядок, и эта идея, вероятно, не жизнеспособна. По крайней мере, не для общего случая любых a, b и c.
С a=5, b=7, c=9:
0, 5=a, 7=b, 9=c, 10=2a, 12=b+a, 14=2b, 14=c+a, 15=3a, 16=c+b, 18=2c
я слишком сонный, чтобы увидеть шаблон, но вот более длинный список
# bash
limit=5; for ((i=0 ; i<limit ; i++)); do
for ((j=0 ; j<limit ; j++)); do
for ((k=0 ; k<limit ; k++)); do
printf "%2d: %d %d %d\n" $((5*i + 7*j + 9*k)) $i $j $k;
done; done; done | sort -n | cat -n
1 0: 0 0 0
2 5: 1 0 0
3 7: 0 1 0
4 9: 0 0 1
5 10: 2 0 0
6 12: 1 1 0
7 14: 0 2 0
8 14: 1 0 1
9 15: 3 0 0
10 16: 0 1 1
11 17: 2 1 0
12 18: 0 0 2
13 19: 1 2 0
14 19: 2 0 1
15 20: 4 0 0
16 21: 0 3 0
17 21: 1 1 1
18 22: 3 1 0
19 23: 0 2 1
20 23: 1 0 2
21 24: 2 2 0
22 24: 3 0 1
23 25: 0 1 2
24 26: 1 3 0
25 26: 2 1 1
26 27: 0 0 3
27 27: 4 1 0
28 28: 0 4 0
29 28: 1 2 1
30 28: 2 0 2
31 29: 3 2 0
32 29: 4 0 1
33 30: 0 3 1
34 30: 1 1 2
35 31: 2 3 0
36 31: 3 1 1
37 32: 0 2 2
38 32: 1 0 3
39 33: 1 4 0
40 33: 2 2 1
41 33: 3 0 2
42 34: 0 1 3
43 34: 4 2 0
44 35: 1 3 1
45 35: 2 1 2
46 36: 0 0 4
47 36: 3 3 0
48 36: 4 1 1
49 37: 0 4 1
50 37: 1 2 2
51 37: 2 0 3
52 38: 2 4 0
53 38: 3 2 1
54 38: 4 0 2
55 39: 0 3 2
56 39: 1 1 3
57 40: 2 3 1
58 40: 3 1 2
59 41: 0 2 3
60 41: 1 0 4
61 41: 4 3 0
62 42: 1 4 1
63 42: 2 2 2
64 42: 3 0 3
65 43: 0 1 4
66 43: 3 4 0
67 43: 4 2 1
68 44: 1 3 2
69 44: 2 1 3
70 45: 3 3 1
71 45: 4 1 2
72 46: 0 4 2
73 46: 1 2 3
74 46: 2 0 4
75 47: 2 4 1
76 47: 3 2 2
77 47: 4 0 3
78 48: 0 3 3
79 48: 1 1 4
80 48: 4 4 0
81 49: 2 3 2
82 49: 3 1 3
83 50: 0 2 4
84 50: 4 3 1
85 51: 1 4 2
86 51: 2 2 3
87 51: 3 0 4
88 52: 3 4 1
89 52: 4 2 2
90 53: 1 3 3
91 53: 2 1 4
92 54: 3 3 2
93 54: 4 1 3
94 55: 0 4 3
95 55: 1 2 4
96 56: 2 4 2
97 56: 3 2 3
98 56: 4 0 4
99 57: 0 3 4
100 57: 4 4 1
101 58: 2 3 3
102 58: 3 1 4
103 59: 4 3 2
104 60: 1 4 3
105 60: 2 2 4
106 61: 3 4 2
107 61: 4 2 3
108 62: 1 3 4
109 63: 3 3 3
110 63: 4 1 4
111 64: 0 4 4
112 65: 2 4 3
113 65: 3 2 4
114 66: 4 4 2
115 67: 2 3 4
116 68: 4 3 3
117 69: 1 4 4
118 70: 3 4 3
119 70: 4 2 4
120 72: 3 3 4
121 74: 2 4 4
122 75: 4 4 3
123 77: 4 3 4
124 79: 3 4 4
125 84: 4 4 4
не совсем то, что вы ищете, возможно, но вы можете написать отдельную программу для вычисления вектора, отсортировать его, а затем вывести его в список. Тогда вы можете просто прочитать в этом файле.
Если чтение с диска слишком медленное, вы также можете массировать вывод в законный файл C++, который инициализирует экземпляр вашего класса, обладающий жестко закодированными значениями, отвечающими вашим требованиям. Затем это может быть связано с вашим основным проектом и скомпилировано, по существу давая то же самое функциональность гораздо более сложной задачи метапрограммирования, которую вы описываете здесь.
это простая сортировка времени компиляции для ints. Он работает для каждого элемента, разрабатывая свою позицию в списке. Из этого следует, что должно быть на каждой позиции. Затем он строит новый список, вставленный в соответствующие позиции. Вероятно, это не так эффективно с точки зрения сложности (это O(n^2)), как предыдущее решение, но его гораздо легче понять, и он не использует рекурсию.
#include <initializer_list>
#include <array>
#include <tuple>
template<int... members>
struct IntList
{
constexpr bool operator==(IntList) const { return true; }
template<int... others>
constexpr bool operator==(IntList<others...>) const { return false; }
template<int idx>
static constexpr auto at()
{
return std::get<idx>(std::make_tuple(members...));
}
template<int x>
static constexpr auto indexOf()
{
int sum {};
auto _ = { 0, (x > members ? ++sum : 0)... };
return sum;
}
template<int x>
static constexpr auto count()
{
int sum {};
auto _ = { 0, (x == members ? ++sum : 0)... };
return sum;
}
template<int i>
static constexpr auto ith()
{
int result{};
auto _ = {
( i >= indexOf<members>() && i < indexOf<members>() + count<members>() ?
result = members : 0 )...
};
return result;
}
template<std::size_t... i>
static constexpr auto sortImpl(std::index_sequence<i...>)
{
return IntList< ith<i>()... >();
}
static constexpr auto sort()
{
return sortImpl(std::make_index_sequence<sizeof...(members)>());
}
};
static_assert(IntList<1, 2, 3>().at<1>() == 2, "");
static_assert(IntList<>().indexOf<1>() == 0, "");
static_assert(IntList<1>().indexOf<1>() == 0, "");
static_assert(IntList<1, 2, 3, 4>().indexOf<3>() == 2, "");
static_assert(IntList<>().count<1>() == 0, "");
static_assert(IntList<1>().count<1>() == 1, "");
static_assert(IntList<1, 1>().count<1>() == 2, "");
static_assert(IntList<2, 2, 1>().count<1>() == 1, "");
static_assert(IntList<1, 2, 1>().count<1>() == 2, "");
static_assert(IntList<>().sort() == IntList<>(), "");
static_assert(IntList<1>().sort() == IntList<1>(), "");
static_assert(IntList<1, 2>().sort() == IntList<1, 2>(), "");
static_assert(IntList<2, 1>().sort() == IntList<1, 2>(), "");
static_assert(IntList<3, 2, 1>().sort() == IntList<1, 2, 3>(), "");
static_assert(IntList<2, 2, 1>().sort() == IntList<1, 2, 2>(), "");
static_assert(IntList<4, 7, 2, 5, 1>().sort() == IntList<1, 2, 4, 5, 7>(), "");
static_assert(IntList<4, 7, 7, 5, 1, 1>().sort() == IntList<1, 1, 4, 5, 7, 7>(), "");