Algorithm Intuition
This page exists to catalog and explore the C++ STL algorithms, and to name and justify missed algorithms.
The name "algorithm intuition" comes from Kate Gregory by way of Conor Hoekstra. It refers to an ability to look at a problem and break it down into a series of high-level algorithmic steps, rather than as a low-level series of discrete steps, in the same way as skilled programmers are able to look at a set of characteristics and select the correct abstract data structure, without having to think about the discrete nodes or pointers that make it up. Building up an intuition for algorithms is essential to being a great C++ programmer, and makes a huge difference in the clarity, consiceness, and ultimately correctness of one's code.
This webpage is an attempt to take the long list of over a hundred named algorithms and break them down into families, providing an easy high-level view of what they do, expressed in the form of input, output, and abstract operations. If you want to link to a specific row in the table, you can click the # in the upper-left corner of that row's name column.
Links in the "Name" column are external links to documentation (cppreference or other). They are dotted-underlined to signify that they open a new tab on click. Links in the "Compare to" column will take you directly to that row on this page.
The source code and data I used to generate this table (not including the source for this page itself) is located here. The PHP code I used to transclude the generated tables into this page is available here.
I do not actually know if a search (and particularly, a search which returns a whole subrange) is strictly speaking a catamorphism, but even if they are not, there are very strong links between several of the searching and folding algorithms, and several of the folds can be implemented in terms of a related search algorithm. I am also uncertain about whether everything I have labelled as an anamorphism truly is one, as explained below∅.
Blue
names indicate algorithms described by Conor. Green
names are from kblib. Occasionally, I may add algorithms specified in C++ standard proposals, which will be indicated in orange
. Red
names in the "Default operations" column indicate non-overridable parameters and a lack of generality. The notes below the tables explain the rest of the terminology and notation I used.
Where kblib provides an alternate definition of a standard library algorithm, usually only the standard algorithm is shown, unless the two differ in some significant way. For minor differences, which affect only one or two columns, two values may be shown, with the kblib version colored green
.
Catamorphisms (folds) and Searches
Name | Input ranges* | Accumulator | Returns⌖ | Operations† | Default operations | Complexity | Order‡ | Compare to |
---|---|---|---|---|---|---|---|---|
2↓I | Arg | Value | A, bT | plus , multiplies |
Unspc. | Fwd. | transform_ |
|
1s↓F | Arg | Value | A, bT | Unspc. | Fwd. | inner_ |
||
1↓I,∥F / 2↓I,∥F | Arg | Value | acR, uT / bT | plus , multiplies |
O(N) | ∥ | ||
1↓I,∥F+Value | Position | equal_to |
O(N) | S/C, ∥ | ||||
1↓I,∥F | Position | uP | O(N) | S/C, ∥ | ||||
1↓I,∥F + 1↓F | Position | bP | equal_to |
O(S×N) | S/C, ∥ | find_if |
||
1↓F | First | Position | bP | less |
O(N), =(max(N-1, 0)) | Fwd., ∥ | ||
1↓F | First | 2 Positions⌖ | bP | less |
O(N), ≤(max(floor((3/2)*(N−1)), 0)) | Fwd., ∥ | ||
1↓F+Value | Position | bP | less |
O(log N) + O(1)↓R; O(N) | B/S | |||
1↓F+Value | Range⌖ | bP | less |
O(log N) + O(1)↓R; O(N) | B/S | |||
2↓F | Position | bP | equal_to |
≤(S×N) | ∥ | |||
2↓F | Position | bP | equal_to |
≤(S×(N-S+1)) | ∥ | search |
||
2↓F | bool |
bP | equal_to |
N > S ? O(S) : O(1)↓R; O(min(S, N))↓F | Fwd. | |||
2↓B | bool |
bP | equal_to |
N > S ? O(S) : O(1)↓R; O(min(S, N))↓B | ||||
2↓I | bool |
bP, uT, uTΔ | equal_to , identity , identity |
≤(min(S, N)) | ||||
2↓F | bool |
bP, uT, uTΔ | equal_to , identity , identity |
≤(min(S, N)) | ||||
1↓F | Position | Searcher | Depends on Searcher | |||||
2↓I | Position⌖ | bP | equal_to |
O(N), O(min(N, M)) | S/C | |||
2↓I,∥F | Position⌖ | bP | equal_to |
O(N), O(min(N, M)) | S/C, ∥ | find_match |
||
1s↓F | Position | bP | equal_to |
=(min((result-first)+1, (last-first)-1)); O(N)↓∥ | S/C, ∥ | find_match |
||
1s↓F | Position | bP | less |
O(N) | S/C, ∥ | find_match |
||
1↓F + Count + Value | Position | bP | equal_to |
≤(N) | ∥ | adjacent_ |
||
1↓I | Arg | Value | A | plus |
Unspc.; O(N) | Fwd. | ||
1↓I | First | Value | R | plus |
O(N) | Fwd. | accumulate |
|
1↓I,∥F | Arg | Value | acR | plus |
O(N) | ∥ | ||
1↓I,∥F+Value | 0 |
size_t |
equal_to |
=(N) | Fwd., ∥ | accumulate |
||
1↓I,∥F | 0 |
size_t |
uP | =(N) | Fwd., ∥ | accumulate |
||
1↓F+Value | bool |
bP | less |
O(log N) + O(1)↓R; O(N) | B/S | |||
1↓I,∥F | bool |
uP | O(N) | Fwd., ∥ | ||||
1↓F | bool |
bP | less |
O(N) | Fwd., ∥ | |||
1↓R | bool |
bP | less |
O(N) | ∥ | |||
2↓F | bool |
bP | equal_to |
N==M ? O(N2) : O(1)↓R; O(N2) | ||||
2↓I,∥F | bool |
bP | less |
O(N+M), ≤(2×(N+M-1)) | Fwd., ∥ | |||
1↓I+Value / 2↓I+Value | Arg | Value | uT / bT | O(N) | S/C | |||
1↓I / 2↓I | Value | uT / bT, uP / bP | O(N) | S/C | ||||
1↓I,∥F | true |
bool |
uP | O(N) | S/C, ∥ | first_result |
||
1↓I,∥F | false |
bool |
uP | O(N) | S/C, ∥ | first_result |
||
1↓I+Value | false |
bool |
equal_to |
O(N) | S/C | first_result |
||
2↓I | false |
bool |
contains |
O(S×N) | S/C | first_result |
||
2↓I,∥F | bool |
bP | equal_to |
N==M ? ≤(N) : O(1)↓R; ≤(N); O(N)↓∥ | Fwd., ∥ | first_result |
||
2↓I,∥F | bool |
bP | less |
≤(2×min(N, M)) | Fwd., ∥ | first_result |
||
2↓I | ordering | CompareΔ | compare_ |
<(min(N, M)) | Fwd. | first_result |
||
1↓F | Position | uP | O(log N)↓R; O(N) | B/S | ||||
1↓R | Position | bP | less |
O(N) | ∥ | |||
Name | Input ranges* | Accumulator | Returns⌖ | Operations† | Default operations | Complexity | Order‡ | Compare to |
Anamorphisms (unfolds/transforms)
Name | Input ranges* | Accumulator | Output ranges | Operations† | Default operations | Complexity | Order‡ | Compare to |
---|---|---|---|---|---|---|---|---|
1↓I,∥F | First, Arg | 1↓O,∥F | aR, uT | O(N) | ∥ | |||
1↓I,∥F | Arg | 1↓O,∥F | aR, uT | O(N) | ∥ | |||
1↓I | First | 1↓O | A | plus |
O(N), =(N - 1) | Fwd. | ||
1↓I,∥F | First, Arg | 1↓O,∥F | aR | plus |
O(N) | ∥ | ||
1↓I,∥F | Arg | 1↓O,∥F | aR | plus |
O(N) | ∥ | ||
1s↓I,∥F | Quasi | 1↓O,∥F | fD | minus |
O(N), =(N - 1) | Fwd., ∥ | adjacent_ |
|
1s↓I | Quasi | 1↓O | D | O(N), =(N - 1) | Fwd., ∥ | adjacent_ |
||
1s↓I | First | 1↓O | A, D | O(N) | Fwd. | |||
1↓I,∥F / 2↓I,∥F | 1↓O,∥F | uT / bT | =(N) | ∥ | ||||
1↓I,∥F | 1↓O,∥F | =(N) | Fwd., ∥ | transform |
||||
1↓B | 1↓B | =(N) | Rev. | |||||
1↓I,∥F | 1↓O,∥F | =(N) | Unspc., ∥ | transform |
||||
1↓I,∥F+Value | Arg | 1↓O,∥F | equal_to |
=(N) | Fwd., ∥ | transform |
||
1↓I,∥F | Arg | 1↓O,∥F | uP | =(N) | Fwd., ∥ | transform |
||
1↓B | 1↓O,∥F | =(N) | ∥ | transform |
||||
1d↓F | 1↓O,∥F | O(N) | ∥ | |||||
1↓I,∥F | 1↓O,∥F | =(N) | Fwd., ∥ | shift_left |
||||
1↓B | 1↓B | =(N) | Rev. | shift_ |
||||
1d↓F | Self | "≤(N-n)" | Fwd., ∥ | move (algorithm) |
||||
1d↓F(swap),B | Self | "≤(N-n)" | Yes, ∥ | move_ |
||||
1↓F | Arg(URBG) | 1↓O+Size | O(N) | Fwd. | ||||
1↓I | Arg(URBG) | 1↓R+Size | O(N) | Fwd. | ||||
1↓I,∥F | 1↓R | bP | less |
O(N log(min(D,N)) | ∥ | |||
1↓I,∥F | 2↓O,∥F | uP | =(N) | Fwd., ∥ | ||||
1↓I | 1↓O | uP, uT | O(N) | Fwd. | ||||
1↓I,∥F | 1↓O,∥F | uP | O(N) | Fwd., ∥ | transform_if |
|||
1↓I,∥F+Value | 1↓O,∥F | equal_to |
=(N) | Fwd., ∥ | transform_if |
|||
1↓I,∥F | First | 1↓O,∥F | bP | equal_to |
O(N) | Fwd., ∥ | ||
2↓I,∥F | 1↓O,∥F | bP | less |
O(N+M), ≤(2⋅(N+M)−1) | Fwd., ∥ | |||
2↓I,∥F | 1↓O,∥F | bP | less |
O(N+M) | Fwd., ∥ | |||
2↓B | 1↓O | Regex⧫ | Unspc. | See note⧫ | ||||
3↓F | 1↓O | bP | equal_to |
O(N×(S+R)) | regex_ ⧫ |
|||
0 | 1↓F | G | =(N) | Fwd., ∥ | ||||
0 | Arg | 1↓F | ++ |
=(N) | Fwd. | generate |
||
0 | Arg | 1↓F | uT | ++ |
=(N) | Fwd. | generate |
|
0 | Arg | 1↓F | =(N) | Fwd., ∥ | generate , generate_n |
|||
1↓I,∥F | 0∅ | muT | =(N) | Fwd., ∥ | ||||
1↓I / 2↓I | 0∅ | muT / mbT | =(N) | Fwd. | ||||
2↓F | Self | swap |
O(N) | Fwd., ∥ | for_ |
|||
1↓F+Value | Self | equal_to |
O(N) | Fwd., ∥ | ||||
1↓F | Self | uP | O(N) | Fwd., ∥ | ||||
1↓F | Self | bP | equal_to |
O(N) | Fwd., ∥ | |||
1↓F+Value | Self | equal_to |
=(N) | Fwd., ∥ | transform |
|||
1↓F | Self | uP | =(N) | Fwd., ∥ | transform |
|||
1↓B | Self | O(N), =(N/2) | Yes, ∥ | swap_ranges |
||||
1d↓F | Self | O(N) | Yes, ∥ | |||||
1↓R | Acc(URBG) | Self | O(N) | |||||
1↓B | Self | O(N), ≤(N/2) | ||||||
1↓F | Self | uP | O(N); O(N log N)↓∥ | ∥ | ||||
1↓B | Self | uP | O(N) w/ memory, else O(N log N); O(N log N)↓∥ | ∥ | ||||
1↓R | Self | bP | less |
O(N log N) | ∥ | |||
1d↓R | Self | bP | less |
O(N log M) | ∥ | |||
1↓R | Self | bP | less |
O(N log N) w/ memory, else O(N log(N)2) | ∥ | |||
1d↓R | Self | bP | less |
O(N); O(N log N)↓∥ | ∥ | |||
1↓R+positions↓R | Self | bP | less |
O(N log M) | nth_ |
|||
1↓R | Self | bP | less |
O(N), ≤(3N) | ||||
1↓R | Self | bP | less |
O(log N) | ||||
1↓R | Self | bP | less |
O(N log N) | ||||
1d↓B | Self | bP | less |
=(N-1) w/ memory, O(N log N); O(N log N)↓∥ | ∥ | |||
Name | Input ranges* | Accumulator | Output ranges | Operations† | Default operations | Complexity | Order‡ | Compare to |
Named Operators and Function Objects
Name | Arity | Class (<T>) | Signature (<T>) | Class (<void>) | Definition |
---|---|---|---|---|---|
| 2 | (ac)R | (T, T) → T |
bT | x + y |
minus | 2 | R | (T, T) → T |
bT | x - y |
multiplies | 2 | (ac)R | (T, T) → T |
bT | x * y |
divides | 2 | R | (T, T) → T |
bT | x / y |
modulus | 2 | R | (T, T) → T |
bT | x % y |
negate | 1 | uT | (T) → T |
uT | -x |
equal_to | 2 | (c)bP | (T, T) → bool |
bT | x == y |
not_equal_to | 2 | (c)bP | (T, T) → bool |
bT | x != y |
less | 2 | bP | (T, T) → bool |
bT | x < y |
greater | 2 | bP | (T, T) → bool |
bT | x > y |
less_equal | 2 | bP | (T, T) → bool |
bT | x <= y |
greater_equal | 2 | bP | (T, T) → bool |
bT | x >= y |
logical_and | 2 | (c)bP | (T, T) → bool |
bT | x and y |
logical_or | 2 | (c)bP | (T, T) → bool |
bT | x or y |
logical_not | 1 | uP | (T) → bool |
uT | not x |
bit_and | 2 | (ac)R | (T, T) → T |
bT | x & y |
bit_or | 2 | (ac)R | (T, T) → T |
bT | x | y |
bit_xor | 2 | (ac)R | (T, T) → T |
bT | x ^ y |
bit_not | 1 | uT | (T) → T |
uT | ~x |
Name | Arity | Class (<T>) | Signature (<T>) | Class (<void>) | Definition |
Footnotes
Operation types
- A
- Accumulation
(A, T)
→A
- R
- Reduction
(T, T)
→T
- D
- Adjacent Op
(T, T)
→U
- T
- Transform
(Ts...)
→U
- P
- Predicate
(Ts...)
→bool
- G
- Generator
()
→U
Operation semantics
- a
- Associative
op(a, op(b, c))
↔op(op(a, b), c)
- c
- Commutative
op(a, b)
↔op(b, a)
- f
- Flipped
op(b, a)
- u
- Unary
op(a)
- b
- Binary
op(a, b)
- m
- Mutable
op(a)
may modifya
lexicographical_compare_three_way
is unique in that the Compare predicate it requires must return one of the standard three-way ordering types (strong_ordering
, weak_ordering
, or partial_ordering
), rather than bool
.
⌖For the purposes of this analysis, returning an iterator, an index, or a pair of corresponding iterators in different ranges (i.e. mismatch
) are all considered to be a single "position". "Range" as a return value of a catamorphism indicates that two iterators identifying a subrange of the input are returned, not that a new range is produced. "2 Positions" means two unassociated iterators are returned, which do not form a range.
∅I'm not certain if for_each
is, strictly speaking, an anamorphism, because it does not directly produce any output. However, I include it in the anamorphisms table because, as a void
function it doesn't neatly fit into this analysis, and because swap_ranges
, which clearly is an anamorphism, can be implemented in terms of (the binary version of) it.
⧫regex_replace
is a strange case for this list because it's not really meant to be an algorithm. However, the regular expression can be considered a kind of predicated in exactly the same way as the Searcher of search (C++17)
can be, and the actual operation performed is a kind of conditional two-input copy, in a similar way as set_intersection
et al. are, but with the conditionality being somewhat more like unique_copy
in that it relates to the relationship between nearby elements instead of their individual values. Another unusual thing is that it can return its output as a string by value, in addition to writing to an output iterator. Despite that, I'm considering it an anamorphism because its output is a range of the same kind as its input, even if it is represented in the language as a value type, unlike those catamorphisms which can be used to return ranges, but which, for analytical purposes, return values. I wrote a more generic search_replace_copy
, which is a much more normal entry for this list.
Ⓢsample
is listed twice because the requirements on its input and output ranges are complementary, thus effectively providing two overloads.
Acknowledgements
This page would not exist without Conor Hoekstra's two-part talk "Algorithm Intuition" (CppCon 2019, parts one and two) to inspire me and provide the basic framework for me to expand upon. Jonathan Boccara's talk "105 STL Algorithms in Less Than an Hour" (CppCon 2018) was also very helpful for me in framing this work.
These presentations themselves reference excellent presentations by Sean Parent ("C++ Seasoning", GoingNative 2013), Marshall Clow ("STL Algorithms - why you should use them, and how to write your own", CppCon 2016), and Kate Gregory ("It's Complicated", Meeting C++ 2017 Keynote, "Simplicity: not just for beginners", ACCU 2018, and her appearance on CppCast, episode 30, "Stop Teaching C (When Teaching C++)").
Top