Minimum and Maximum Algorithms

An introduction to the 7 min/max algorithms in the C++ standard library: clamp, min, min_element, max, max_element, minmax, and minmax_element
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

DreamShaper_v7_hipster_Sidecut_hair_modest_clothes_fully_cloth_1.jpg
Ryan McCombe
Ryan McCombe
Posted

In this lesson, we introduce the minimum and maximum algorithms available within the C++ standard library.

All the algorithms in this section are available within the <algorithm> header:

#include <algorithm>

We will cover clamp, min, min_element, max, max_element, minmax, and minmax_element.

Lets get started with clamp

std::ranges::clamp

The std::ranges::clamp function is used to ensure a value is within a specific range.

The function takes three arguments, the value to clamp, the minimum allowed value. and the maximum allowed value

  • If the value to clamp is below the minimum, clamp returns the minimum
  • If the value to clamp is above the maximum, clamp returns the maximum
  • If the value is between the minimum and maximum, clamp returns the value

The following shows an example of clamping numbers to the 0-255 range:

#include <algorithm>
#include <iostream>

int main() {
  using std::ranges::clamp;

  std::cout
    << "Clamp -30: " << clamp(-30, 0, 255)
    << "\nClamp 100: " << clamp(100, 0, 255)
    << "\nClamp 300: " << clamp(300, 0, 255);
}
Clamp -30: 0
Clamp 100: 100
Clamp 300: 255

The clamp algorithm, and every other algorithm in this lesson, will return a const reference to the chosen element:

#include <algorithm>
#include <iostream>

int main() {
  int Value { 40 };

  const int& ClampedValue {
    std::ranges::clamp(Value, 0, 255)
  };

  Value = 50;

  std::cout << ClampedValue;
}
50

std::ranges::min

The minimum value in a range can be retrieved using min:

#include <algorithm>
#include <iostream>
#include <vector>

int main() {
  std::vector<int> Numbers { 2, 3, 1, 4, 5 };
  const int& Result { std::ranges::min(Numbers) };

  std::cout << "Smallest number: " << Result;
}
Smallest number: 1

If multiple objects are tied for the minimum, the algorithm will return the first (ie, leftmost) one in the range.

std::ranges::min_element

The min_element function works in much the same way as min. The key difference is that, whereas min will return a reference to the smallest object, min_element will return an iterator pointing to it.

In this example, we use this to insert a 0 before our minimum element:

#include <algorithm>
#include <iostream>
#include <vector>

int main() {
  std::vector<int> Numbers { 2, 3, 1, 4, 5 };
  auto Result { std::ranges::min_element(Numbers) };

  Numbers.emplace(Result, 0);

  for (int& i : Numbers) {
    std::cout << i << " ";
  }
}
2 3 0 1 4 5

std::ranges::max

The maximum value in a range can be retrieved using the max algorithm:

#include <algorithm>
#include <iostream>
#include <vector>

int main() {
  std::vector Numbers { 2, 3, 1, 4, 5 };
  const int& Result { std::ranges::max(Numbers) };

  std::cout << "Smallest number: " << Result;
}
Largest number: 5

If multiple objects are tied for the maximum, the algorithm will return the first (ie, leftmost) one in the range.

std::ranges::max_element

Similar to min and min_element, we can instead access an iterator that points to our maximum element. Here, we use the iterator to insert a 0 before our maximum element:

#include <algorithm>
#include <iostream>
#include <vector>

int main() {
  std::vector Numbers { 2, 3, 1, 4, 5 };
  auto Result { std::ranges::max_element(Numbers) };

  Numbers.emplace(Result, 0);

  for (int& i : Numbers) {
    std::cout << i << " ";
  }
}
2 3 1 4 0 5

std::ranges::minmax

We can retrieve both the minimum and maximum values in a range at once using the minmax algorithm. This algorithm returns a struct with two properties: min and max. We can use it like this:

#include <algorithm>
#include <iostream>
#include <vector>

int main() {
  std::vector Numbers { 2, 3, 1, 4, 5 };
  const auto& Result { std::ranges::minmax(Numbers) };

  std::cout << "Smallest: " << Result.min;
  std::cout << "\nLargest: " << Result.max;
}
Smallest: 1
Largest: 5

Typically, we’d use structured binding when retrieving the results from minmax:

#include <algorithm>
#include <iostream>
#include <vector>

int main() {
  std::vector Numbers { 2, 3, 1, 4, 5 };
  const auto& [min, max] {
    std::ranges::minmax(Numbers)
  };

  std::cout << "Smallest number: " << min;
  std::cout << "\nLargest number: " << max;
}
Smallest number: 1
Largest number: 5

If multiple objects are tied for the minimum, the min property will contain the first (ie, the leftmost) one in the range

If multiple objects are tied for the maximum, the max property will contain the last (ie, the rightmost) one in the range

std::ranges::minmax_element

Finally, we have minmax_element, which will return iterators that point to the minimum and maximum objects of our range. Here, we use the iterators to insert a 0 before both our minimum and maximum elements:

#include <algorithm>
#include <iostream>
#include <list>

int main() {
  std::list Numbers { 2, 3, 1, 4, 5 };
  auto [min, max] {
    std::ranges::minmax_element(Numbers)
  };

  Numbers.insert(min, 0);
  Numbers.insert(max, 0);

  for (int& i : Numbers) {
    std::cout << i << " ";
  }
}
2 3 0 1 4 0 5

Minimum and Maximum Algorithms with Custom Types

For our custom types to be directly supported by minimum and maximum algorithms, they generally need to implement all six of the comparison operators: >, <, >=, <=, ==, and !=

Here is an example:

#include <algorithm>
#include <iostream>
#include <vector>

struct MyStruct {
  int Value;
  bool operator<(const MyStruct& Other) const {
    return Value < Other.Value;
  }
  bool operator<=(const MyStruct& Other) const {
    return Value <= Other.Value;
  }
  bool operator>(const MyStruct& Other) const {
    return Value > Other.Value;
  }
  bool operator>=(const MyStruct& Other) const {
    return Value >= Other.Value;
  }
  bool operator==(const MyStruct& Other) const {
    return Value == Other.Value;
  }
  bool operator!=(const MyStruct& Other) const {
    return Value != Other.Value;
  }
};

int main() {
  std::vector Numbers {
    MyStruct { 2 },
    MyStruct { 3 },
    MyStruct { 1 },
    MyStruct { 4 },
    MyStruct { 5 }
  };

  const MyStruct& min { std::ranges::min(Numbers) };

  std::cout << "Smallest number: " << min.Value;
}
Smallest number: 1

Minimum and Maximum Algorithms with Custom Comparison Functions

An optional second argument to our min and max algorithms (aside from clamp) allows us to specify a comparison function. This function will accept two objects of the appropriate type and should return true if the first object is “smaller” than the second.

In this example, we use a custom comparison function to find out which character has the lowest health:

#include <algorithm>
#include <iostream>
#include <vector>

class Character {
 public:
  Character(std::string Name, int Health)
      : Name(std::move(Name)), Health(Health){};

  std::string GetName() const { return Name; }
  int GetHealth() const { return Health; }

 private:
  std::string Name;
  int Health;
};

int main() {
  std::vector Party{
    Character{"Legolas", 100},
    Character{"Gandalf", 200},
    Character{"Gimli", 500}
  };

  auto Comparer {
    [](Character& A, Character& B) {
      return A.GetHealth() < B.GetHealth();
    }
  };

  const Character& LowestHealth {
    std::ranges::min(Party, Comparer)
  };

  std::cout << "Lowest Health: "
            << LowestHealth.GetName();
}
Lowest Health: Legolas

Minimum and Maximum Algorithms with Projection

All the algorithms in this lesson (aside from clamp) accept an additional optional third argument, which can be a projection function. We cover projection in detail here:

In this example, we find the Character with the lowest health, by passing a projection function to the min algorithm:

#include <algorithm>
#include <iostream>
#include <vector>

class Character {
 public:
  Character(std::string Name, int Health)
      : Name(std::move(Name)), Health(Health){};

  std::string GetName() const { return Name; }
  int GetHealth() const { return Health; }

 private:
  std::string Name;
  int Health;
};

int main() {
  std::vector Party {
    Character { "Legolas", 100 },
    Character { "Gandalf", 200 },
    Character { "Gimli", 500 }
  };

  const Character& LowestHealth {
    std::ranges::min(Party, {}, &Character::GetHealth)
  };

  std::cout << "Lowest Health: "
            << LowestHealth.GetName();
}
Lowest Health: Legolas

Was this lesson useful?

Ryan McCombe
Ryan McCombe
Posted
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Standard Library Algorithms
7a.jpg
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access!

This course includes:

  • 106 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

C++ Movement Algorithms

An introduction to the 7 movement algorithms in the C++ standard library: move, move_backward, rotate, reverse, shuffle, shift_left, and shift_right.
DreamShaper_v7_artist_Sidecut_hair_modest_clothes_fully_clothe_3.jpg
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved