Как / где практическое использование алгоритма partial_sum
в STL?
Какие еще интересные / нетривиальные примеры или варианты использования?
Как / где практическое использование алгоритма partial_sum
в STL?
Какие еще интересные / нетривиальные примеры или варианты использования?
Я использовал его, чтобы уменьшить использование памяти простым сборщиком мусора с метками в моем игрушечном интерпретаторе лямбда-исчисления.
Пул GC - это массив объектов одинакового размера. Цель состоит в том, чтобы удалить объекты, которые не связаны с другими объектами, и сжать оставшиеся объекты в начале массива. Поскольку объекты перемещаются в памяти, каждую ссылку необходимо обновить. Это требует таблицы переназначения объектов.
partial_sum
позволяет сохранять таблицу в сжатом формате (всего один бит на объект) до тех пор, пока анализ не будет завершен и память не будет освобождена. Поскольку объекты маленькие, это значительно сокращает использование памяти.
remove_if
, чтобы уплотнить отмеченные объекты до начала пула.partial_sum
over the Boolean values to generate a table of pointers/indexes into the new pool.
Особенно удобно для кеша данных помещать таблицу переназначения в только что освобожденную, а значит, еще горячую память.
partial_sum
, когда хочу построить CDF дискретного распределения вероятностей.
- person Alexandre C.; 21.04.2011
partial_sum
вам помогли. Так что было бы хорошо, если бы вы могли опубликовать какой-нибудь код, чтобы упростить понимание.
- person Nawaz; 17.09.2018
Что касается частичной суммы, то следует отметить, что это операция, которая отменяет соседнее различие так же, как - отменяет +. Или еще лучше, если вы помните исчисление, как дифференциация отменяет интеграцию. Лучше, потому что смежная разница - это по сути дифференциация, а частичная сумма - это интегрирование.
Допустим, у вас есть симуляция автомобиля, и на каждом временном шаге вам нужно знать положение, скорость и ускорение. Вам нужно сохранить только одно из этих значений, так как вы можете вычислить два других. Скажем, вы сохраняете позицию на каждом временном шаге, вы можете взять соседнюю разницу положения, чтобы задать скорость, и соседнюю разницу скорости, чтобы дать ускорение. В качестве альтернативы, если вы сохраняете ускорение, вы можете взять частичную сумму для получения скорости, а частичная сумма скорости дает положение.
Частичная сумма - одна из тех функций, которая не слишком часто возникает у большинства людей, но чрезвычайно полезна, когда вы находите правильную ситуацию. Очень похоже на исчисление.
В последний раз я (хотел бы) использовать его при преобразовании дискретного распределения вероятностей (массив p (X = k)) в кумулятивное распределение (массив p (X ‹= k)). Чтобы выбрать один раз из распределения, вы можете случайным образом выбрать число из [0-1), а затем выполнить двоичный поиск в совокупном распределении.
Однако этого кода не было на C ++, поэтому я сам подсчитал частичную сумму.
Вы можете использовать его для создания монотонно возрастающей последовательности чисел. Например, следующее создает vector
, содержащий числа от 1 до 42:
std::vector<int> v(42, 1);
std::partial_sum(v.begin(), v.end(), v.begin());
Это повседневный случай использования? Наверное, нет, хотя несколько раз я находил это полезным.
Вы также можете использовать std::partial_sum
для создания списка факториалов. (Это даже менее полезно, поскольку количество факториалов, которые могут быть представлены типичным целочисленным типом данных, довольно ограничено. Хотя это весело :-D)
std::vector<int> v(10, 1);
std::partial_sum(v.begin(), v.end(), v.begin());
std::partial_sum(v.begin(), v.end(), v.begin(), std::multiplies<int>());
iota
воскрес из мертвых в C ++ 0x.
- person Steve Jessop; 07.11.2010
partial_sum
. В этом, конечно же, нет ничего захватывающего, чем ответ Potatoswatter.
- person James McNellis; 08.11.2010
Пример использования в личных целях: выбор колеса рулетки
Я использую partial_sum
в алгоритме выбора колеса рулетки (текст ссылки). Этот алгоритм случайным образом выбирает элементы из контейнера с вероятностью, линейной по отношению к некоторому заранее заданному значению.
Поскольку все мои элементы выбирают из не обязательно нормализованного значения, я использую алгоритм partial_sum
для построения чего-то вроде «колеса рулетки», потому что я суммирую все элементы. Затем я выбрал случайную переменную из этого диапазона (последний partial_sum
- это сумма всех) и использовал stl::lower_bound
для поиска «колеса», на котором остановился мой случайный поиск. Элемент, возвращаемый алгоритмом lower_bound
, является выбранным.
Помимо преимущества ясного и выразительного кода с использованием partial_sum
, я мог также получить некоторую скорость, экспериментируя с GCC параллельный режим, в котором используются параллельные версии некоторых алгоритмов, одним из которых является partial_sum (текст ссылки).
Еще одно известное применение: один из наиболее важных алгоритмических примитивов в параллельной обработке (но, возможно, немного в стороне от STL)
Если вас интересуют сильно оптимизированные алгоритмы, использующие partial_sum
(в этом случае может быть больше результатов под синонимами «сканирование» или «prefix_sum»), чем обращайтесь к сообществу параллельных алгоритмов. Им это нужно постоянно. Вы не найдете параллельного алгоритма сортировки, основанного на быстрой сортировке или mergesort без его использования. Эта операция - один из наиболее важных используемых параллельных примитивов. Я думаю, что он чаще всего используется для расчета смещений в динамических алгоритмах. Подумайте об этапе разделения в быстрой сортировке, который разделяется и передается в параллельные потоки. Вы не знаете количество элементов в каждом слоте раздела до его расчета. Итак, вам нужны смещения для всех потоков для последующего доступа.
Возможно, вы найдете больше информации в популярной сейчас теме обработки GPU. Одна короткая статья о CUDA от Nvidia и примитиве сканирования с несколькими примерами приложений, которые вы найдете в < em> Глава 39. Сумма параллельных префиксов (сканирование) с помощью CUDA .
Личный вариант использования: промежуточный этап подсчета сортировки из CLRS:
COUNTING_SORT (A, B, k)
for i ← 1 to k do
c[i] ← 0
for j ← 1 to n do
c[A[j]] ← c[A[j]] + 1
//c[i] now contains the number of elements equal to i
// std::partial_sum here
for i ← 2 to k do
c[i] ← c[i] + c[i-1]
// c[i] now contains the number of elements ≤ i
for j ← n downto 1 do
B[c[A[i]]] ← A[j]
c[A[i]] ← c[A[j]] - 1
Вы можете построить «скользящую сумму» (предшественницу скользящей средней):
template <class T>
void moving_sum (const vector<T>& in, int num, vector<T>& out)
{
// cummulative sum
partial_sum (in.begin(), in.end(), out.begin());
// shift and subtract
int j;
for (int i = out.size() - 1; i >= 0; i--) {
j = i - num;
if (j >= 0)
out[i] -= out[j];
}
}
А затем вызовите его с помощью:
vector<double> v(10);
// fill in v
vector<double> v2 (v.size());
moving_sum (v, 3, v2);
partial_sum
менее вероятно переполнятся.
- person Potatoswatter; 07.11.2010
moving_sum
принимали итераторы в качестве параметров вместо vector
.
- person Björn Pollex; 07.11.2010
Знаете, однажды я действительно использовал partial_sum () ... Это была небольшая интересная проблема, которую мне задали на собеседовании. Мне это так понравилось, я пошел домой и написал код.
Проблема заключалась в следующем: для заданной последовательной последовательности целых чисел найти самую короткую подпоследовательность с наибольшим значением. Например. Данный:
Value: -1 2 3 -1 4 -2 -4 5
Index: 0 1 2 3 4 5 6 7
Мы нашли бы подпоследовательность [1,4]
Теперь очевидное решение - запустить 3 цикла for, перебирая все возможные начала и окончания и по очереди складывая значения каждой возможной подпоследовательности. Неэффективно, но быстро кодируется и трудно ошибаться. (Особенно, когда третий цикл for просто Накопить (начало, конец, 0).)
Правильное решение предполагает подход «разделяй и властвуй» / «снизу вверх». Например. Разделите проблемное пространство пополам и для каждой половины вычислите наибольшую подпоследовательность, содержащуюся в этом разделе, наибольшую подпоследовательность, включая начальный номер, наибольшую подпоследовательность, включая конечный номер, и подпоследовательность всего раздела. Вооружившись этими данными, мы можем затем объединить две половинки вместе без какой-либо дальнейшей оценки любой из них. Очевидно, что данные для каждой половины можно вычислить, разбив каждую половину на половины (четверти), каждую четверть на половинки (восьмые) и так далее, пока у нас не будет тривиальных одноэлементных случаев. Все довольно эффективно.
Но помимо всего этого, есть третий (несколько менее эффективный) вариант, который я хотел изучить. Это похоже на случай трех циклов, только мы добавляем соседние числа, чтобы избежать такой большой работы. Идея состоит в том, что нет необходимости добавлять a + b, a + b + c и a + b + c + d, когда мы можем сложить t1 = a + b, t2 = t1 + c и t3 = t2 + d. Это компромисс между пространством и вычислениями. Он работает путем преобразования последовательности:
Index: 0 1 2 3 4
FROM: 1 2 3 4 5
TO: 1 3 6 10 15
Таким образом, мы получаем все возможные подстроки, начиная с index = 0 и заканчивая indexes = 0,1,2,3,4.
Затем мы перебираем этот набор, вычитая последовательные возможные «начальные» точки ...
FROM: 1 3 6 10 15
TO: - 2 5 9 14
TO: - - 3 7 12
TO: - - - 4 9
TO: - - - - 5
Тем самым давая нам значения (суммы) всех возможных подпоследовательностей.
Мы можем найти максимальное значение каждой итерации с помощью max_element ().
Первый шаг проще всего выполнить с помощью partial_sum ().
Остальные шаги через цикл for и преобразование (data + i, data + size, data + i, bind2nd (минус ‹TYPE› (), data [i-1])) < / em>.
Ясно, что O (N ^ 2). Но все равно интересно и весело ...
Частичные суммы часто используются в параллельных алгоритмах. Рассмотрим код
for (int i=0; N>i; ++i) {
sum += x[i];
do_something(sum);
}
Если вы хотите распараллелить этот код, вам нужно знать частичные суммы. Я использую параллельную версию partial_sum GNU для чего-то очень похожего.
Я часто использую частичную сумму не для суммирования, а для вычисления текущего значения в последовательности в зависимости от предыдущего.
Например, если вы интегрируете файл function. Каждый новый шаг - это предыдущий шаг, vt += dvdt
или vt = integrate_step(dvdt, t_prev, t_prev+dt);
.
В непараметрических байесовских методах существует шаг Метрополиса-Гастингса (для каждого наблюдения), который определяет выборку нового или существующего кластера. Если необходимо выполнить выборку из существующего кластера, это необходимо сделать с разными весами. Эти взвешенные вероятности смоделированы в следующем примере кода.
#include <random>
#include <iostream>
#include <algorithm>
int main() {
std::default_random_engine generator(std::random_device{}());
std::uniform_real_distribution<double> distribution(0.0,1.0);
int K = 8;
std::vector<double> weighted_likelihood(K);
for (int i = 0; i < K; ++i) {
weighted_likelihood[i] = i*10;
}
std::cout << "Weighted likelihood: ";
for (auto i: weighted_likelihood) std::cout << i << ' ';
std::cout << std::endl;
std::vector<double> cumsum_likelihood(K);
std::partial_sum(weighted_likelihood.begin(), weighted_likelihood.end(), cumsum_likelihood.begin());
std::cout << "Cumulative sum of weighted likelihood: ";
for (auto i: cumsum_likelihood) std::cout << i << ' ';
std::cout << std::endl;
std::vector<int> frequency(K);
int N = 280000;
for (int i = 0; i < N; ++i) {
double pick = distribution(generator) * cumsum_likelihood.back();
auto lower = std::lower_bound(cumsum_likelihood.begin(), cumsum_likelihood.end(), pick);
int index = std::distance(cumsum_likelihood.begin(), lower);
frequency[index]++;
}
std::cout << "Frequencies: ";
for (auto i: frequency) std::cout << i << ' ';
std::cout << std::endl;
}
Обратите внимание, что это не отличается от ответа https://stackoverflow.com/users/13005/steve-jessop. Он добавлен, чтобы дать немного больше контекста о конкретной ситуации (непараметрические байесовские методы, например, алгоритмы Нила, использующие процесс Дирихле, как и раньше) и фактический код, который использует partial_sum
в сочетании с lower_bound
.
std::lower_bound
! В документе упоминается, что он возвращает первый элемент, больший или равный границе. Чтобы избежать выбора 0-вероятностей, если они идут первыми в последовательности, я бы рекомендовал вместо этого использовать std::upper_bound
. Будет выбрано только значение, превышающее (не равное) случайному выбору (а нулевые вероятности никогда не будут). Вы должны убедиться, что ваш случайный генератор является эксклюзивным (никогда не возвращает верхнюю границу случайного диапазона), чтобы это работало во всех случаях. uniform_real_distribution
, например, делает это.
- person hsandt; 21.12.2017