Namespaces
Variants
Views
Actions

Difference between revisions of "cpp/algorithm/reduce"

From cppreference.com
< cpp‎ | algorithm
(Comparison is unfair if reduce sums to long and accumulate sums to int.)
m (Minor fix.)
 
(6 intermediate revisions by 4 users not shown)
Line 1: Line 1:
 
{{cpp/title|reduce}}
 
{{cpp/title|reduce}}
{{cpp/algorithm/navbar}}
+
{{cpp/algorithm/numeric/navbar}}
 
{{dcl begin}}
 
{{dcl begin}}
{{dcl header | numeric}}
+
{{dcl header|numeric}}
{{dcl rev multi | num=1
+
{{dcl|num=1|since=c++17|notes={{mark constexpr since c++20}}|
| since1=c++17 | dcl1=
+
 
template< class InputIt >
 
template< class InputIt >
 
typename std::iterator_traits<InputIt>::value_type
 
typename std::iterator_traits<InputIt>::value_type
    reduce( InputIt first, InputIt last );
 
| since2=c++20 | dcl2=
 
template< class InputIt >
 
constexpr typename std::iterator_traits<InputIt>::value_type
 
 
     reduce( InputIt first, InputIt last );
 
     reduce( InputIt first, InputIt last );
 
}}
 
}}
{{dcl | num=2 | since=c++17 |
+
{{dcl|num=2|since=c++17|
 
template< class ExecutionPolicy, class ForwardIt >
 
template< class ExecutionPolicy, class ForwardIt >
 
typename std::iterator_traits<ForwardIt>::value_type
 
typename std::iterator_traits<ForwardIt>::value_type
Line 19: Line 14:
 
             ForwardIt first, ForwardIt last );
 
             ForwardIt first, ForwardIt last );
 
}}
 
}}
{{dcl rev multi | num=3
+
{{dcl|num=3|since=c++17|notes={{mark constexpr since c++20}}|
| since1=c++17 | dcl1=
+
 
template< class InputIt, class T >
 
template< class InputIt, class T >
 
T reduce( InputIt first, InputIt last, T init );
 
T reduce( InputIt first, InputIt last, T init );
| since2=c++20 | dcl2=
 
template< class InputIt, class T >
 
constexpr T reduce( InputIt first, InputIt last, T init );
 
 
}}
 
}}
{{dcl | num=4 | since=c++17 |
+
{{dcl|num=4|since=c++17|
 
template< class ExecutionPolicy, class ForwardIt, class T >
 
template< class ExecutionPolicy, class ForwardIt, class T >
 
T reduce( ExecutionPolicy&& policy,
 
T reduce( ExecutionPolicy&& policy,
 
           ForwardIt first, ForwardIt last, T init );
 
           ForwardIt first, ForwardIt last, T init );
 
}}
 
}}
{{dcl rev multi | num=5
+
{{dcl|num=5|since=c++17|notes={{mark constexpr since c++20}}|
| since1=c++17 | dcl1=
+
template< class InputIt, class T, class BinaryOp >
+
T reduce( InputIt first, InputIt last, T init, BinaryOp binary_op );
+
| since2=c++20 | dcl2=
+
 
template< class InputIt, class T, class BinaryOp >
 
template< class InputIt, class T, class BinaryOp >
constexpr T reduce( InputIt first, InputIt last, T init, BinaryOp binary_op );
+
T reduce( InputIt first, InputIt last, T init, BinaryOp op );
 
}}
 
}}
{{dcl | num=6 | since=c++17 |
+
{{dcl|num=6|since=c++17|
template< class ExecutionPolicy, class ForwardIt, class T, class BinaryOp >
+
template< class ExecutionPolicy,
 +
          class ForwardIt, class T, class BinaryOp >
 
T reduce( ExecutionPolicy&& policy,
 
T reduce( ExecutionPolicy&& policy,
           ForwardIt first, ForwardIt last, T init, BinaryOp binary_op );
+
           ForwardIt first, ForwardIt last, T init, BinaryOp op );
 
}}
 
}}
 
{{dcl end}}
 
{{dcl end}}
  
@1@ same as {{c|reduce(first, last, typename std::iterator_traits<InputIt>::value_type{})}}
+
@1@ Equivalent to {{c|reduce(first, last, typename std::iterator_traits<InputIt>::value_type{})}}.
@3@ same as {{c|reduce(first, last, init, std::plus<>())}}
+
@5@ Reduces the range {{math|[first; last)}}, possibly permuted and aggregated in unspecified manner, along with the initial value {{tt|init}} over {{tt|binary_op}}.  
+
@2,4,6@ Same as {{v|1,3,5}}, but executed according to {{tt|policy}}. {{cpp/algorithm/parallel overload precondition|plural=true}}
+
  
The behavior is non-deterministic if {{tt|binary_op}} is not associative or not commutative.
+
@3@ Equivalent to {{c|reduce(first, last, init, std::plus<>())}}.
  
The behavior is undefined if {{tt|binary_op}} modifies any element or invalidates any iterator in {{math|[first; last]}}, including the end iterator.
+
@5@ Reduces the range {{range|first|last}}, possibly permuted and aggregated in unspecified manner, along with the initial value {{c|init}} over {{c|op}}.
 +
 
 +
@2,4,6@ Same as {{v|1,3,5}}, but executed according to {{c|policy}}.
 +
@@ {{cpp/algorithm/parallel overload precondition|plural=yes}}
 +
 
 +
Given {{c|binary_op}} as the actual binary operation:
 +
 
 +
* The result is non-deterministic if the {{c|binary_op}} is not associative or not commutative (such as floating-point addition).
 +
 
 +
* If any of the following values is not convertible to {{tt|T}}, the program is ill-formed:
 +
:* {{c|binary_op(init, *first)}}
 +
:* {{c|binary_op(*first, init)}}
 +
:* {{c|binary_op(init, init)}}
 +
:* {{c|binary_op(*first, *first)}}
 +
 
 +
* If any of the following conditions is satisfied, the behavior is undefined:
 +
:* {{tt|T}} is not {{named req|MoveConstructible}}.
 +
:* {{c|binary_op}} modifies any element of {{range|first|last}}.
 +
:* {{c|binary_op}} invalidates any iterator or subrange of {{closed range|first|last}}.
  
 
===Parameters===
 
===Parameters===
 
{{par begin}}
 
{{par begin}}
{{par | first, last | the range of elements to apply the algorithm to}}
+
{{par|first, last|the range of elements to apply the algorithm to}}
{{par | init | the initial value of the generalized sum}}
+
{{par|init|the initial value of the generalized sum}}
{{par | policy | the execution policy to use. See [[cpp/algorithm/execution_policy_tag_t|execution policy]] for details.}}
+
{{par exec pol}}
{{par | binary_op | binary {{named req|FunctionObject}} that will be applied in unspecified order to the result of dereferencing the input iterators, the results of other {{tt|binary_op}} and {{tt|init}}.}}
+
{{par|op|binary {{named req|FunctionObject}} that will be applied in unspecified order to the result of dereferencing the input iterators, the results of other {{c|op}} and {{c|init}}.}}
 
{{par hreq}}
 
{{par hreq}}
{{par req named | InputIt | InputIterator}}
+
{{par req named|InputIt|InputIterator}}
{{par req named | ForwardIt | ForwardIterator}}
+
{{par req named|ForwardIt|ForwardIterator}}
{{par req named | T | MoveConstructible|notes=and {{tt|binary_op(init, *first)}}, {{tt|binary_op(*first, init)}}, {{tt|binary_op(init, init)}}, and {{tt|binary_op(*first, *first)}} must be convertible to {{tt|T}}.}}
+
 
{{par end}}
 
{{par end}}
  
 
===Return value===
 
===Return value===
Generalized sum of {{tt|init}} and {{tt|*first}}, {{tt|*(first+1)}}, ... {{tt|*(last-1)}} over {{tt|binary_op}},
+
@1-4@ The generalized sum of {{c|init}} and the elements of {{range|first|last}} over {{c|std::plus<>()}}.
  
where generalized sum {{math|GSUM(op, a{{su|b=1}}, ..., a{{su|b=N}})}} is defined as follows:
+
@5,6@ The generalized sum of {{c|init}} and the elements of {{range|first|last}} over {{c|op}}.
* if {{math|N{{=}}1}}, {{math|a{{su|b=1}}}}
+
* if {{math|N > 1}}, {{math|op(GSUM(op, b{{su|b=1}}, ..., b{{su|b=K}}), GSUM(op, b{{su|b=M}}, ..., b{{su|b=N}}))}} where
+
:* {{math|b{{su|b=1}}, ..., b{{su|b=N}}}} may be any permutation of {{math|a1, ..., aN}} and
+
:* {{math|1 < K+1 {{=}} M ≤ N}}
+
  
in other words, {{tt|reduce}} behaves like {{lc|std::accumulate}} except the elements of the range may be grouped and rearranged in arbitrary order
+
The ''generalized sum'' of a group of elements over an binary operation {{c|binary_op}} is defined as follows:
 +
* If the group only has one element, the sum is the value of the element.
 +
* Otherwise, performs the following operations in order:
 +
# Takes any two elements {{c|elem1}} and {{c|elem2}} from the group.
 +
# Calculates {{c|binary_op(elem1, elem2)}} and puts the result back to the group.
 +
# Repeats steps 1 and 2 until there is only one element in the group.
  
 
===Complexity===
 
===Complexity===
{{math|O(last - first)}} applications of {{tt|binary_op}}.
+
Given {{mathjax-or|\(\scriptsize N\)|N}} as {{c|std::distance(first, last)}}:
 +
 
 +
@1-4@ {{mathjax-or|\(\scriptsize O(N)\)|O(N)}} applications of {{c|std::plus<>()}}.
 +
 
 +
@5,6@ {{mathjax-or|\(\scriptsize O(N)\)|O(N)}} applications of {{c|op}}.
  
 
===Exceptions===
 
===Exceptions===
{{cpp/algorithm/parallel_exceptions_reporting_behavior}}
+
{{cpp/algorithm/parallel exceptions reporting behavior}}
  
 
===Notes===
 
===Notes===
If the range is empty, {{tt|init}} is returned, unmodified
+
{{tt|std::reduce}} behaves like {{lc|std::accumulate}} except the elements of the range may be grouped and rearranged in arbitrary order.
  
 
===Example===
 
===Example===
{{example|side-by-side comparison between reduce and {{lc|std::accumulate}}:
+
{{example|side-by-side comparison between {{tt|std::reduce}} and {{lc|std::accumulate}}:
 
|code=
 
|code=
 
#if PARALLEL
 
#if PARALLEL
Line 111: Line 118:
 
     std::cout.imbue(std::locale("en_US.UTF-8"));
 
     std::cout.imbue(std::locale("en_US.UTF-8"));
 
     std::cout << std::fixed << std::setprecision(1);
 
     std::cout << std::fixed << std::setprecision(1);
     auto eval = [](auto fun) {
+
   
 +
     auto eval = [](auto fun)
 +
    {
 
         const auto t1 = std::chrono::high_resolution_clock::now();
 
         const auto t1 = std::chrono::high_resolution_clock::now();
 
         const auto [name, result] = fun();
 
         const auto [name, result] = fun();
Line 117: Line 126:
 
         const std::chrono::duration<double, std::milli> ms = t2 - t1;
 
         const std::chrono::duration<double, std::milli> ms = t2 - t1;
 
         std::cout << std::setw(28) << std::left << name << "sum: "
 
         std::cout << std::setw(28) << std::left << name << "sum: "
                   << result << "\t time: " << ms.count() << " ms\n";
+
                   << result << '\t' << "time: " << ms.count() << " ms\n";
 
     };
 
     };
 +
   
 
     {
 
     {
 
         const std::vector<double> v(100'000'007, 0.1);
 
         const std::vector<double> v(100'000'007, 0.1);
 
+
       
 
         eval([&v]{ return std::pair{"std::accumulate (double)",
 
         eval([&v]{ return std::pair{"std::accumulate (double)",
             std::accumulate(v.cbegin(), v.cend(), 0.0)}; } );
+
             std::accumulate(v.cbegin(), v.cend(), 0.0)}; });
 
         eval([&v]{ return std::pair{"std::reduce (seq, double)",
 
         eval([&v]{ return std::pair{"std::reduce (seq, double)",
             std::reduce(SEQ v.cbegin(), v.cend())}; } );
+
             std::reduce(SEQ v.cbegin(), v.cend())}; });
 
         eval([&v]{ return std::pair{"std::reduce (par, double)",
 
         eval([&v]{ return std::pair{"std::reduce (par, double)",
             std::reduce(PAR v.cbegin(), v.cend())}; } );
+
             std::reduce(PAR v.cbegin(), v.cend())}; });
 
     }
 
     }
 +
   
 
     {
 
     {
 
         const std::vector<long> v(100'000'007, 1);
 
         const std::vector<long> v(100'000'007, 1);
 
+
       
 
         eval([&v]{ return std::pair{"std::accumulate (long)",
 
         eval([&v]{ return std::pair{"std::accumulate (long)",
             std::accumulate(v.cbegin(), v.cend(), 0l)}; } );
+
             std::accumulate(v.cbegin(), v.cend(), 0l)}; });
 
         eval([&v]{ return std::pair{"std::reduce (seq, long)",
 
         eval([&v]{ return std::pair{"std::reduce (seq, long)",
             std::reduce(SEQ v.cbegin(), v.cend())}; } );
+
             std::reduce(SEQ v.cbegin(), v.cend())}; });
 
         eval([&v]{ return std::pair{"std::reduce (par, long)",
 
         eval([&v]{ return std::pair{"std::reduce (par, long)",
             std::reduce(PAR v.cbegin(), v.cend())}; } );
+
             std::reduce(PAR v.cbegin(), v.cend())}; });
 
     }
 
     }
 
}
 
}
Line 143: Line 154:
 
|output=
 
|output=
 
// POSIX: g++ -std=c++23 ./example.cpp -ltbb -O3; ./a.out
 
// POSIX: g++ -std=c++23 ./example.cpp -ltbb -O3; ./a.out
std::accumulate (double)    sum: 10,000,000.7   time: 356.9 ms
+
std::accumulate (double)    sum: 10,000,000.7 time: 356.9 ms
std::reduce (seq, double)  sum: 10,000,000.7   time: 140.1 ms
+
std::reduce (seq, double)  sum: 10,000,000.7 time: 140.1 ms
std::reduce (par, double)  sum: 10,000,000.7   time: 140.1 ms
+
std::reduce (par, double)  sum: 10,000,000.7 time: 140.1 ms
std::accumulate (long)      sum: 100,000,007     time: 46.0 ms
+
std::accumulate (long)      sum: 100,000,007 time: 46.0 ms
std::reduce (seq, long)    sum: 100,000,007     time: 67.3 ms
+
std::reduce (seq, long)    sum: 100,000,007 time: 67.3 ms
std::reduce (par, long)    sum: 100,000,007     time: 63.3 ms
+
std::reduce (par, long)    sum: 100,000,007 time: 63.3 ms
  
 
// POSIX: g++ -std=c++23 ./example.cpp -ltbb -O3 -DPARALLEL; ./a.out
 
// POSIX: g++ -std=c++23 ./example.cpp -ltbb -O3 -DPARALLEL; ./a.out
std::accumulate (double)    sum: 10,000,000.7   time: 353.4 ms
+
std::accumulate (double)    sum: 10,000,000.7 time: 353.4 ms
std::reduce (seq, double)  sum: 10,000,000.7   time: 140.7 ms
+
std::reduce (seq, double)  sum: 10,000,000.7 time: 140.7 ms
std::reduce (par, double)  sum: 10,000,000.7   time: 24.7 ms
+
std::reduce (par, double)  sum: 10,000,000.7 time: 24.7 ms
std::accumulate (long)      sum: 100,000,007     time: 42.4 ms
+
std::accumulate (long)      sum: 100,000,007 time: 42.4 ms
std::reduce (seq, long)    sum: 100,000,007     time: 52.0 ms
+
std::reduce (seq, long)    sum: 100,000,007 time: 52.0 ms
std::reduce (par, long)    sum: 100,000,007     time: 23.1 ms
+
std::reduce (par, long)    sum: 100,000,007 time: 23.1 ms
 
}}
 
}}
  
 
===See also===
 
===See also===
 
{{dsc begin}}
 
{{dsc begin}}
{{dsc inc | cpp/algorithm/dsc accumulate}}
+
{{dsc inc|cpp/algorithm/dsc accumulate}}
{{dsc inc | cpp/algorithm/dsc transform}}
+
{{dsc inc|cpp/algorithm/dsc transform}}
{{dsc inc | cpp/algorithm/dsc transform_reduce}}
+
{{dsc inc|cpp/algorithm/dsc transform_reduce}}
 +
{{dsc inc|cpp/algorithm/ranges/dsc fold_left}}
 
{{dsc end}}
 
{{dsc end}}
  
 
{{langlinks|de|es|fr|it|ja|pt|ru|zh}}
 
{{langlinks|de|es|fr|it|ja|pt|ru|zh}}

Latest revision as of 00:21, 18 April 2024

 
 
Algorithm library
Constrained algorithms and algorithms on ranges (C++20)
Constrained algorithms, e.g. ranges::copy, ranges::sort, ...
Execution policies (C++17)
Non-modifying sequence operations
Batch operations
(C++17)
Search operations
(C++11)                (C++11)(C++11)

Modifying sequence operations
Copy operations
(C++11)
(C++11)
Swap operations
Transformation operations
Generation operations
Removing operations
Order-changing operations
(until C++17)(C++11)
(C++20)(C++20)
Sampling operations
(C++17)

Sorting and related operations
Partitioning operations
Sorting operations
Binary search operations
(on partitioned ranges)
Set operations (on sorted ranges)
Merge operations (on sorted ranges)
Heap operations
Minimum/maximum operations
(C++11)
(C++17)
Lexicographical comparison operations
Permutation operations
C library
Numeric operations
Operations on uninitialized memory
 
 
Defined in header <numeric>
template< class InputIt >

typename std::iterator_traits<InputIt>::value_type

    reduce( InputIt first, InputIt last );
(1) (since C++17)
(constexpr since C++20)
template< class ExecutionPolicy, class ForwardIt >

typename std::iterator_traits<ForwardIt>::value_type
    reduce( ExecutionPolicy&& policy,

            ForwardIt first, ForwardIt last );
(2) (since C++17)
template< class InputIt, class T >
T reduce( InputIt first, InputIt last, T init );
(3) (since C++17)
(constexpr since C++20)
template< class ExecutionPolicy, class ForwardIt, class T >

T reduce( ExecutionPolicy&& policy,

          ForwardIt first, ForwardIt last, T init );
(4) (since C++17)
template< class InputIt, class T, class BinaryOp >
T reduce( InputIt first, InputIt last, T init, BinaryOp op );
(5) (since C++17)
(constexpr since C++20)
template< class ExecutionPolicy,

          class ForwardIt, class T, class BinaryOp >
T reduce( ExecutionPolicy&& policy,

          ForwardIt first, ForwardIt last, T init, BinaryOp op );
(6) (since C++17)
1) Equivalent to reduce(first, last, typename std::iterator_traits<InputIt>::value_type{}).
3) Equivalent to reduce(first, last, init, std::plus<>()).
5) Reduces the range [firstlast), possibly permuted and aggregated in unspecified manner, along with the initial value init over op.
2,4,6) Same as (1,3,5), but executed according to policy.
These overloads participate in overload resolution only if

std::is_execution_policy_v<std::decay_t<ExecutionPolicy>> is true.

(until C++20)

std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> is true.

(since C++20)

Given binary_op as the actual binary operation:

  • The result is non-deterministic if the binary_op is not associative or not commutative (such as floating-point addition).
  • If any of the following values is not convertible to T, the program is ill-formed:
  • binary_op(init, *first)
  • binary_op(*first, init)
  • binary_op(init, init)
  • binary_op(*first, *first)
  • If any of the following conditions is satisfied, the behavior is undefined:
  • T is not MoveConstructible.
  • binary_op modifies any element of [firstlast).
  • binary_op invalidates any iterator or subrange of [firstlast].

Contents

[edit] Parameters

first, last - the range of elements to apply the algorithm to
init - the initial value of the generalized sum
policy - the execution policy to use. See execution policy for details.
op - binary FunctionObject that will be applied in unspecified order to the result of dereferencing the input iterators, the results of other op and init.
Type requirements
-
InputIt must meet the requirements of LegacyInputIterator.
-
ForwardIt must meet the requirements of LegacyForwardIterator.

[edit] Return value

1-4) The generalized sum of init and the elements of [firstlast) over std::plus<>().
5,6) The generalized sum of init and the elements of [firstlast) over op.

The generalized sum of a group of elements over an binary operation binary_op is defined as follows:

  • If the group only has one element, the sum is the value of the element.
  • Otherwise, performs the following operations in order:
  1. Takes any two elements elem1 and elem2 from the group.
  2. Calculates binary_op(elem1, elem2) and puts the result back to the group.
  3. Repeats steps 1 and 2 until there is only one element in the group.

[edit] Complexity

Given N as std::distance(first, last):

1-4) O(N) applications of std::plus<>().
5,6) O(N) applications of op.

[edit] Exceptions

The overloads with a template parameter named ExecutionPolicy report errors as follows:

  • If execution of a function invoked as part of the algorithm throws an exception and ExecutionPolicy is one of the standard policies, std::terminate is called. For any other ExecutionPolicy, the behavior is implementation-defined.
  • If the algorithm fails to allocate memory, std::bad_alloc is thrown.

[edit] Notes

std::reduce behaves like std::accumulate except the elements of the range may be grouped and rearranged in arbitrary order.

[edit] Example

side-by-side comparison between std::reduce and std::accumulate:

#if PARALLEL
#include <execution>
#define SEQ std::execution::seq,
#define PAR std::execution::par,
#else
#define SEQ
#define PAR
#endif
 
#include <chrono>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <utility>
#include <vector>
 
int main()
{
    std::cout.imbue(std::locale("en_US.UTF-8"));
    std::cout << std::fixed << std::setprecision(1);
 
    auto eval = [](auto fun)
    {
        const auto t1 = std::chrono::high_resolution_clock::now();
        const auto [name, result] = fun();
        const auto t2 = std::chrono::high_resolution_clock::now();
        const std::chrono::duration<double, std::milli> ms = t2 - t1;
        std::cout << std::setw(28) << std::left << name << "sum: "
                  << result << '\t' << "time: " << ms.count() << " ms\n";
    };
 
    {
        const std::vector<double> v(100'000'007, 0.1);
 
        eval([&v]{ return std::pair{"std::accumulate (double)",
            std::accumulate(v.cbegin(), v.cend(), 0.0)}; });
        eval([&v]{ return std::pair{"std::reduce (seq, double)",
            std::reduce(SEQ v.cbegin(), v.cend())}; });
        eval([&v]{ return std::pair{"std::reduce (par, double)",
            std::reduce(PAR v.cbegin(), v.cend())}; });
    }
 
    {
        const std::vector<long> v(100'000'007, 1);
 
        eval([&v]{ return std::pair{"std::accumulate (long)",
            std::accumulate(v.cbegin(), v.cend(), 0l)}; });
        eval([&v]{ return std::pair{"std::reduce (seq, long)",
            std::reduce(SEQ v.cbegin(), v.cend())}; });
        eval([&v]{ return std::pair{"std::reduce (par, long)",
            std::reduce(PAR v.cbegin(), v.cend())}; });
    }
}

Possible output:

// POSIX: g++ -std=c++23 ./example.cpp -ltbb -O3; ./a.out
std::accumulate (double)    sum: 10,000,000.7	time: 356.9 ms
std::reduce (seq, double)   sum: 10,000,000.7	time: 140.1 ms
std::reduce (par, double)   sum: 10,000,000.7	time: 140.1 ms
std::accumulate (long)      sum: 100,000,007	time: 46.0 ms
std::reduce (seq, long)     sum: 100,000,007	time: 67.3 ms
std::reduce (par, long)     sum: 100,000,007	time: 63.3 ms
 
// POSIX: g++ -std=c++23 ./example.cpp -ltbb -O3 -DPARALLEL; ./a.out
std::accumulate (double)    sum: 10,000,000.7	time: 353.4 ms
std::reduce (seq, double)   sum: 10,000,000.7	time: 140.7 ms
std::reduce (par, double)   sum: 10,000,000.7	time: 24.7 ms
std::accumulate (long)      sum: 100,000,007	time: 42.4 ms
std::reduce (seq, long)     sum: 100,000,007	time: 52.0 ms
std::reduce (par, long)     sum: 100,000,007	time: 23.1 ms

[edit] See also

sums up or folds a range of elements
(function template) [edit]
applies a function to a range of elements, storing results in a destination range
(function template) [edit]
applies an invocable, then reduces out of order
(function template) [edit]
left-folds a range of elements
(niebloid)[edit]