std::accumulate

std::accumulate() 함수는 범위 내의 모든 요소를 처음부터 끝까지 누적한 결과를 반환한다. 이 함수는 1) 시작 반복자, 2) 종료 반복자, 3) 초기 값, 4) (optional) 그리고 선택적으로 lambda expression을 인자로 받는다. 기본적으로는 아래와 같이 사용할 수 있다:

#include <numeric>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    int sum = std::accumulate(v.begin(), v.end(), 0);
    
    // Output: Sum: 15
    std::cout << "Sum: " << sum << std::endl; 
}

여기서 중요한 것은 초기값과 return을 받는 변수의 타입이 같아야 한다는 것이다. std::accumulate를 의도대로(?) 사용하면 평균을 구하는 함수도 한줄로 작성이 가능하다:

// 거리 값들을 담고 있는 std::vector<double> dist가 있다고 가정
mean = std::accumulate(dist.begin(), dist.end(), 0.0) / dist.size();

std::accumulate의 활용

그런데 주목할 점은 std::accumulate을 무궁무진하게 사용할 수 있다는 것이다. std::accumulate을 아래와 같이 std::vector 내에 탐색을 할 때도 사용할 수 있다:

#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>  // std::max를 위해 필요

int main() {
    std::vector<int> nums = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};

    // std::accumulate를 사용하여 최대값 찾기
    int max_value = std::accumulate(nums.begin(), nums.end(), nums[0],
                                    [](int a, int b) { return std::max(a, b); });

    std::cout << "The maximum value is: " << max_value << std::endl;

    return 0;
}

즉,

int max_value = 0;
for (const auto& elem: nums) {
    max_value = std::max(max_value, elem);
}

을 std::accumulate()를 사용하여 한 줄로 간결히 표현할 수 있다.

이를 더 심화시키면 아래와 struct나 class가 지니는 멤버 변수의 값이 최대인 객체를 찾는 식으로도 활용이 가능하다:

#include <numeric>
#include <vector>
#include <iostream>

struct MyStruct {
    int x = 0;
};
int main() {
    MyStruct s1, s2, s3, s4, s5;
    s1.x = 19;
    s2.x = 21;
    s3.x = 30;
    s4.x = 28;
    s5.x = 3;
    std::vector<MyStruct> v = {s1, s2, s3, s4, s5};
    const MyStruct& max_struct = std::accumulate(v.begin(), v.end(), v[0], [](MyStruct& a, MyStruct& b) {
        return a.x > b.x ? a : b;
    });

    // Output: Sum: 30
    std::cout << "Max value of the max struct: " << max_struct.x << std::endl;
}

이러한 이유로 std::accumulate는 대소비교를 한 후, 가장 큰/작은 값을 리턴할 때도 많이 사용된다.

로보틱스에서 활용 사례

아래의 예시 코드들처럼 합이나 평균을 구하는 과정에서는 모두 다 자명하게 사용 가능 하다.

// From https://github.com/ethz-asl/maplab
double distance_sum = std::accumulate(minimum_distances.begin(), minimum_distances.end(), 0.0);
// From https://github.com/ethz-asl/panoptic_mapping
const float mean = std::accumulate(times.begin(), times.end(), 0.f) / times.size();

좀 더 색다르게 사용하면 Open3D 내에 구현되어 있는 아래 코드처럼 모든 법선이 설정되어 있는지 확인하는 코드도 가능하다:

// if not all normals have been set, then remove the vertex normals
    bool all_normals_set =
            std::accumulate(normals_indicator.begin(), normals_indicator.end(),
                            true, [](bool a, bool b) { return a && b; });

하지만 위의 코드는 파일을 불러오는 코드여서 상관없긴 하지만, std::accumulate를 활용해서 각 요소를 비교하면, normals_indicator의 모든 요소가 조건을 충족하는 지 확인해야 한다는 단점이 있다. 따라서 무진장 큰 vector라면, 모든 요소의 boolean 값을 계속해서 확인하는 것이 비효율적일 수 있다. 예로 들어서, 100,000개의 요소가 있는 vector에서 2번째 vector의 normal이 유효하지 않다는 것을 알게 되었으면 그 즉시 all_normal_set을 false로 return하면 되는데, 남은 999,998개를 확인하는 추가적인 연산이 필요하기 때문이다.

이런 경우에는 std::all_of를 사용하는 것이 더 효율적이다.

// 모든 element가 조건을 만족하는 지 확인
bool all_normals_set = std::all_of(normals_indicator.begin(), normals_indicator.end(), [](bool val) {
        return val; // 여기서 val이 false라면 바로 false를 반환하고 반복을 중단
    });

std::all_of가 std::accumulate보다 더 효율적인 이유는, 조건을 만족하지 않는 요소를 만나면 바로 false를 반환하고 반복을 중단하기 때문이다 (이는 다음 블로그 글에서 자세히 설명한다).

결론

Weighted sum, 평균, 최대값, 최소값에 대응되는 요소 리턴 받기 등 다양한 활용이 가능한 std::accumulate 함수에 대해 알아보았다. 하지만 특정 조건을 만족하는지 확인하는 경우에는, 더 효율적인 함수형 프로그래밍이 있으니 (e.g., std::all_of, std::any_of, std::none_of), 굳이 std::accumulate를 사용하지는 말자.


Robotics 연구자/개발자를 위한 Modern C++ 시리즈입니다. 사용된 코드들은 여기에서 확인할 수 있습니다.

  1. Modern C++ for Robotics 1. Introduction
  2. Modern C++ for Robotics 2. 함수형 프로그래밍과 Lambda Expression
  3. Modern C++ for Robotics 3. Lambda Expression의 Anonymous Function과 Named Function
  4. Modern C++ for Robotics 4. std::for_each() 쉬운 설명 & 예제
  5. Modern C++ for Robotics 5. std::insert() 쉬운 설명 & 예제
  6. Modern C++ for Robotics 6. std::move() 쉬운 설명 & 예제
  7. Modern C++ for Robotics 7. std::transform() 쉬운 설명 & 예제
  8. Modern C++ for Robotics 8. std::accumulate() 쉬운 설명 & 예제
  9. Modern C++ for Robotics 9. std::all_of(), std::any_of(), std::none_of() 쉬운 설명 & 예제
  10. Modern C++ for Robotics 10. std::copy_if() 쉬운 설명 & 예제
  11. Modern C++ for Robotics 11. std::find_if() 쉬운 설명 & 예제
  12. Modern C++ for Robotics 12. std::remove_if() 쉬운 설명 & 예제
  13. Modern C++ for Robotics 13. std::replace_if() 쉬운 설명 & 예제