Каково практическое использование "partial_sum" в STL?

Как / где практическое использование алгоритма partial_sum в STL?

Какие еще интересные / нетривиальные примеры или варианты использования?


person Community    schedule 07.11.2010    source источник


Ответы (11)


Я использовал его, чтобы уменьшить использование памяти простым сборщиком мусора с метками в моем игрушечном интерпретаторе лямбда-исчисления.

Пул GC - это массив объектов одинакового размера. Цель состоит в том, чтобы удалить объекты, которые не связаны с другими объектами, и сжать оставшиеся объекты в начале массива. Поскольку объекты перемещаются в памяти, каждую ссылку необходимо обновить. Это требует таблицы переназначения объектов.

partial_sum позволяет сохранять таблицу в сжатом формате (всего один бит на объект) до тех пор, пока анализ не будет завершен и память не будет освобождена. Поскольку объекты маленькие, это значительно сокращает использование памяти.

  1. Рекурсивно пометьте используемые объекты и заполните логический массив.
  2. Используйте remove_if, чтобы уплотнить отмеченные объекты до начала пула.
  3. Use partial_sum over the Boolean values to generate a table of pointers/indexes into the new pool.
    • This works because the Nth marked object has N preceding 1's in the array and acquires pool index N.
  4. Снова проведите по пулу и замените каждую ссылку, используя таблицу переназначения.

Особенно удобно для кеша данных помещать таблицу переназначения в только что освобожденную, а значит, еще горячую память.

person Potatoswatter    schedule 07.11.2010
comment
Я использую partial_sum, когда хочу построить CDF дискретного распределения вероятностей. - person Alexandre C.; 21.04.2011
comment
partial_sum также называется сканированием. Имя Matlab - cumsum для кумулятивной суммы. - person Tyson Hilmer; 12.09.2017
comment
Этот ответ кажется наиболее интересным по этой теме. Однако действительно сложно понять, что именно вы сделали и как именно partial_sum вам помогли. Так что было бы хорошо, если бы вы могли опубликовать какой-нибудь код, чтобы упростить понимание. - person Nawaz; 17.09.2018

Что касается частичной суммы, то следует отметить, что это операция, которая отменяет соседнее различие так же, как - отменяет +. Или еще лучше, если вы помните исчисление, как дифференциация отменяет интеграцию. Лучше, потому что смежная разница - это по сути дифференциация, а частичная сумма - это интегрирование.

Допустим, у вас есть симуляция автомобиля, и на каждом временном шаге вам нужно знать положение, скорость и ускорение. Вам нужно сохранить только одно из этих значений, так как вы можете вычислить два других. Скажем, вы сохраняете позицию на каждом временном шаге, вы можете взять соседнюю разницу положения, чтобы задать скорость, и соседнюю разницу скорости, чтобы дать ускорение. В качестве альтернативы, если вы сохраняете ускорение, вы можете взять частичную сумму для получения скорости, а частичная сумма скорости дает положение.

Частичная сумма - одна из тех функций, которая не слишком часто возникает у большинства людей, но чрезвычайно полезна, когда вы находите правильную ситуацию. Очень похоже на исчисление.

person Bowie Owens    schedule 07.11.2010

В последний раз я (хотел бы) использовать его при преобразовании дискретного распределения вероятностей (массив p (X = k)) в кумулятивное распределение (массив p (X ‹= k)). Чтобы выбрать один раз из распределения, вы можете случайным образом выбрать число из [0-1), а затем выполнить двоичный поиск в совокупном распределении.

Однако этого кода не было на C ++, поэтому я сам подсчитал частичную сумму.

person Steve Jessop    schedule 07.11.2010
comment
С точки зрения точности, на самом деле гораздо лучше оценить PDF-файл в интересующей вас точке, чем делать то, что вы пытаетесь. но тем не менее хорошее предложение. - person ; 08.11.2010
comment
@sonicoder: Простите, я не понимаю, о чем вы. Массив - это функция, что означает дискретное распределение в отличие от непрерывного. Нет и речи о том, чтобы быть более точным. - person Steve Jessop; 08.11.2010
comment
извините, пропустил, я почему-то думал о продолжении. - person ; 08.11.2010
comment
Вот минимальный исполняемый пример этого: stackoverflow.com/a/42332864/895245 - person Ciro Santilli 新疆再教育营六四事件ۍ 20.02.2017

Вы можете использовать его для создания монотонно возрастающей последовательности чисел. Например, следующее создает 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>());
person James McNellis    schedule 07.11.2010
comment
Вам будет приятно узнать, что iota воскрес из мертвых в C ++ 0x. - person Steve Jessop; 07.11.2010
comment
Это больше похоже на тривиальные примеры, найденные на страницах sgi stl и cpp-ref, но тем не менее хорошая попытка. - person ; 08.11.2010
comment
@sonicoder: Ну, первый пример в моем ответе - это единственное, для чего я когда-либо использовал partial_sum. В этом, конечно же, нет ничего захватывающего, чем ответ Potatoswatter. - person James McNellis; 08.11.2010
comment
Это очень похоже на мой ответ. Ваша последовательность всегда увеличивается на 1; вместо этого мой иногда увеличивается на 0. Я очень удивлен полученным здесь ответом, я должен выпустить эту программу ... когда у меня будет время ... - person Potatoswatter; 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 .

person sascha    schedule 07.11.2010

Личный вариант использования: промежуточный этап подсчета сортировки из 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
person user1641854    schedule 10.03.2016

Вы можете построить «скользящую сумму» (предшественницу скользящей средней):

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);
person chrisaycock    schedule 07.11.2010
comment
Сдвиг и вычитание перед partial_sum менее вероятно переполнятся. - person Potatoswatter; 07.11.2010
comment
Также подумайте о том, чтобы ваши moving_sum принимали итераторы в качестве параметров вместо vector. - person Björn Pollex; 07.11.2010
comment
Что такое скользящая сумма? - person Nawaz; 17.09.2018

Знаете, однажды я действительно использовал 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). Но все равно интересно и весело ...

person Mr.Ree    schedule 07.11.2010
comment
Это очень распространенная проблема. Столбец 8 Programming Pearls имеет ответ, который требует O (n), cs.bell-labs.com/cm/cs/pearls/sketch08.html cs.bell-labs.com/cm/cs/pearls/maxsum.c - person ; 08.11.2010
comment
Я исправился. Я раньше не видел Programming Pearls или этой конкретной проблемы. Мне (ошибочно) сообщили, что O (NlogN) - лучшее возможное решение. Хотя мне кажется заманчивым удалить свой пост, возможно, лучше оставить его в качестве примера. (И себе, и другим ...) - person Mr.Ree; 08.11.2010

Частичные суммы часто используются в параллельных алгоритмах. Рассмотрим код

for (int i=0; N>i; ++i) {
  sum += x[i];
  do_something(sum);
}

Если вы хотите распараллелить этот код, вам нужно знать частичные суммы. Я использую параллельную версию partial_sum GNU для чего-то очень похожего.

person Jørgen Fogh    schedule 21.04.2011

Я часто использую частичную сумму не для суммирования, а для вычисления текущего значения в последовательности в зависимости от предыдущего.

Например, если вы интегрируете файл function. Каждый новый шаг - это предыдущий шаг, vt += dvdt или vt = integrate_step(dvdt, t_prev, t_prev+dt);.

person kirill_igum    schedule 24.10.2013

В непараметрических байесовских методах существует шаг Метрополиса-Гастингса (для каждого наблюдения), который определяет выборку нового или существующего кластера. Если необходимо выполнить выборку из существующего кластера, это необходимо сделать с разными весами. Эти взвешенные вероятности смоделированы в следующем примере кода.

#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.

person Anne van Rossum    schedule 18.01.2017
comment
Приятно знать о std::lower_bound! В документе упоминается, что он возвращает первый элемент, больший или равный границе. Чтобы избежать выбора 0-вероятностей, если они идут первыми в последовательности, я бы рекомендовал вместо этого использовать std::upper_bound. Будет выбрано только значение, превышающее (не равное) случайному выбору (а нулевые вероятности никогда не будут). Вы должны убедиться, что ваш случайный генератор является эксклюзивным (никогда не возвращает верхнюю границу случайного диапазона), чтобы это работало во всех случаях. uniform_real_distribution, например, делает это. - person hsandt; 21.12.2017