Checking if all elements of a vector are equal in C++
Asked Answered
C

16

53

If I have a vector of values and want to check that they are all the same, what is the best way to do this in C++ efficiently? If I were programming in some other language like R one way my minds jumps to is to return only the unique elements of the container and then if the length of the unique elements is more than 1, I know all the elements cannot be the same. In C++ this can be done like this:

//build an int vector
std::sort(myvector.begin(), myvector.end());
std::vector<int>::iterator it;
//Use unique algorithm to get the unique values.
it = std::unique(myvector.begin(), myvector.end());
positions.resize(std::distance(myvector.begin(),it));
if (myvector.size() > 1) {
    std::cout << "All elements are not the same!" << std::endl;
}

However reading on the internet and SO, I see other answers such using a set or the find_if algorithm. So what is the most efficient way of doing this and why? I imagine mine is not the best way since it involves sorting every element and then a resizing of the vector - but maybe I'm wrong.

Clove answered 29/11, 2013 at 13:37 Comment(3)
This has been asked before: #15531758 The answers there point out, importantly, that efficiency in the O(n) compare-all-to-the-first method can be gained by making sure you break off the loop once you find the first element that is not equal.Phyllida
Array version: #14120846 is a subset via data().Choanocyte
What is the preferred behavior on empty vector? The std::equal and std::adjacent_find answers return false, std::find_if and std::all_of return true.Lagas
C
92

You don't need to use std::sort. It can be done in a simpler way with std::adjacent_find:

if ( std::adjacent_find( myvector.begin(), myvector.end(), std::not_equal_to<>() ) == myvector.end() )
{
    std::cout << "All elements are equal each other" << std::endl;
}

Or, since C++20 with std::ranges::adjacent_find:

if ( std::ranges::adjacent_find( myvector, std::ranges::not_equal_to() ) == myvector.end() )
{ /* ... */ }
Cleary answered 29/11, 2013 at 13:46 Comment(3)
I was close to posting a solution using adjacent_find, but mine would have featured a lambda predicate, which is why I did not post it in the end. not_equal_to was the missing piece that makes it a beautiful solution.Longitudinal
It is strange that nobody mentioned std::all_of.:)Cleary
Your comment suggests there's a better solution using all_of. If so, could you (or somebody) edit your answer to show it?Inca
R
46

you can use std::equal

version 1:

//assuming v has at least 1 element
if ( std::equal(v.begin() + 1, v.end(), v.begin()) )
{
    //all equal
}

This will compare each element with the previous one.

version 2:

//assuming v has at least 1 element
int e = v[0]; //preferably "const auto& e" instead
bool all_equal = true;
for(std::size_t i = 1,s = v.size();i<s && all_equal;i++)
    all_equal = e == v[i];

Edit:

Regarding performance, after testing with 100m elements i found out that in Visual Studio 2015 version 1 is about twice as fast as version 2. This is because the latest compiler for vs2015 uses sse instructions in c++ std implementations when you use ints, float , etc..

if you use _mm_testc_si128 you will get a similar performance to std::equal

Raised answered 29/11, 2013 at 13:40 Comment(5)
Less efficient than just iterating through the array as you're incrementing 2 iterators internally.Berneta
@Luchian Grigore yes , but it's shorter to write :)Raised
True, but "So what is the most efficient way of doing this and why?"Berneta
@Luchian Grigore ok , i'll edit to add something that's efficient.Raised
note that while down the bottom he asks for "most efficen"t, up the top he asks for the "best way", subject to being c++ and efficient. The "best way" allows one to consider style, readability and such, while while balancing against a possible factor of 2 slow down.Sobranje
M
22

Using std::all_of and a C++11 lambda:

if (std::all_of(values.begin(), values.end(), [&](int i) { return i == values[0]; })) {
    // all are the same
}

Or, since C++20 using std::ranges::all_of:

if (std::ranges::all_of(values, [&](int i) { return i == values[0]; })) {
    // all are the same
}
Meshach answered 2/10, 2019 at 11:6 Comment(2)
maybe also do begin()+1?Olimpiaolin
@Mikhail, if you can guarantee that values is non-empty, begin()+1 will indeed skip one unnecessary evaluation. But if emptiness is a possibility, the above answer provides safety as it simply returns true in this case.Cooperstein
B
15

Given no constraints on the vector, you have to iterate through the vector at least once, no matter the approach. So just pick the first element and check that all others are equal to it.

Berneta answered 29/11, 2013 at 13:39 Comment(1)
Remember to short-circuit after you find a value unequal to the first!Indo
A
5

While the asymptotic complexity of std::unique is linear, the actual cost of the operation is probably much larger than you need, and it is an inplace algorithm (it will modify the data as it goes).

The fastest approach is to assume that if the vector contains a single element, it is unique by definition. If the vector contains more elements, then you just need to check whether all of them are exactly equal to the first. For that you only need to find the first element that differs from the first, starting the search from the second. If there is such an element, the elements are not unique.

if (v.size() < 2) return true;
auto different = std::find_if(v.begin()+1, v.end(), 
                              [&v](auto const &x) { x != v[0]; });
return different == v.end();

That is using C++14 syntax, in an C++11 toolchain you can use the correct type in the lambda. In C++03 you could use a combination of std::not, std::bind1st/std::bind2nd and std::equal in place of the lambda.

The cost of this approach is distance(start,different element) comparisons and no copies. Expected and worst case linear cost in the number of comparisons (and no copies!)

Autocatalysis answered 29/11, 2013 at 14:16 Comment(2)
Opening statement is misleading. Yes Unique is linear, but it followed on a sort, which is definitely not linear.Sobranje
@RichardPlunkett: If your only expectation is to detect whether there is a unique value or not, you don't need to sort. Note that this is not attempting to solve the generic problem of removing duplicates or finding how many unique values there are, but rather finding whether there is at least one non-duplicate value. Maybe I should make that more explicit in the answer... although it is just a comment on the question's approach, rather than my own approach.Ulphi
S
4

Sorting is an O(NlogN) task.

This is easily solvable in O(N), so your current method is poor.

A simple O(N) would be as Luchian Grigore suggests, iterate over the vector, just once, comparing every element to the first element.

Sobranje answered 29/11, 2013 at 13:44 Comment(0)
O
3
if(std::all_of(myvector.begin()+1, myvector.end(), std::bind(std::equal_to<int>(),
                                      std::placeholders::_1, myvector.front())) {
  // all members are equal
}
Objectify answered 10/7, 2015 at 17:42 Comment(0)
N
2

You can use FunctionalPlus(https://github.com/Dobiasd/FunctionalPlus):

std::vector<std::string> things = {"same old", "same old"};
if (fplus::all_the_same(things))
    std::cout << "All things being equal." << std::endl;
Nausea answered 4/12, 2016 at 7:42 Comment(0)
L
2

Maybe something like this. It traverses vector just once and does not mess with the vector content.

std::vector<int> values { 5, 5, 5, 4 };
bool equal = std::count_if(values.begin(), values.end(), [ &values ] (auto size) { return size == values[0]; }) == values.size();

If the values in the vector are something different than basic type you have to implement equality operator.

After taking into account underscore_d remarks, I'm changing possible solution

std::vector<int> values { 5, 5, 5, 4 };
bool equal = std::all_of(values.begin(),values.end(),[ &values ] (auto item) { return item == values[0]; });
Lehman answered 14/2, 2017 at 14:50 Comment(1)
It's also a waste of time because count has to keep going even after it finds the 1st differing element, but you only need to know if there was one, so those are wasted cycles. I don't see why we needed an inefficient alternative 4 years later.Wong
I
1

In your specific case, iterating over vector element and finding a different element from the first one would be enough. You may even be lucky enough to stop before evaluating all the elements in your vector. (A while loop could be used but I sticked with a for loop for readability reasons)

bool uniqueElt = true;
int firstItem = *myvector.begin();
for (std::vector<int>::const_iterator it = myvector.begin()+1; it != myvector.end() ; ++it) {
    if(*it != firstItem) {
        uniqueElt = false;
        break;
    }
}

In case you want to know how many different values your vector contains, you could build a set and check its size to see how many different values are inside:

std::set mySet;
std::copy(mySet.begin(), myvector.begin(), myvector.end());
Inessive answered 29/11, 2013 at 13:40 Comment(3)
Why is that more efficient than just iterating through the vector?Berneta
it isnt, and in general is as bad as sorting.Sobranje
It is not, I did not have the time to elaborate my answer yet. However, I think it is usefull for Ward9250 to know that building a set would be a possibility if he was looking for more than an unique value.Inessive
C
1

LLVM provides some independently usable headers+libraries:

#include <llvm/ADT/STLExtras.h>
if (llvm::is_splat(myvector))
  std::cout << "All elements are the same!" << std::endl;

https://godbolt.org/z/fQX-jc

Clari answered 14/1, 2020 at 16:14 Comment(0)
B
0

for the sake of completeness, because it still isn't the most efficient, you can use std::unique in a more efficient way to decide whether all members are the same, but beware that after using std::unique this way the container is useless:

#include <algorithm>
#include <iterator>

if (std::distance(cntnr.begin(), std::unique(cntnr.begin(), cntnr.end()) == 1)
{
  // all members were the same, but
}
Bract answered 29/11, 2013 at 14:14 Comment(0)
P
0

You can simply use std::count to count all the elements that match the starting element:

std::vector<int> numbers = { 5, 5, 5, 5, 5, 5, 5 };
if (std::count(std::begin(numbers), std::end(numbers), numbers.front()) == numbers.size())
{
    std::cout << "Elements are all the same" << std::endl;
}
Pietism answered 10/7, 2017 at 2:4 Comment(2)
I don't see how this is any 'simpler' than all the other ways posted 4 years earlier. In fact, it's a waste of time because count has to keep going even after it finds the 1st differing element, but you only need to know if there was one, so those are purely wasted cycles.Wong
You raise a good point, at the time I hadn't considered that it would iterate over all elements; however, many of the other solutions do the same, and I don't claim this is the best method of doing it, but I ran into this issue and found this to be the most readable solution. If a vector is very large then it would definitely be worth using a solution that wouldn't iterate over all elements.Pietism
P
0

Another approach using C++ 14:

bool allEqual = accumulate(v.begin(), v.end(), true, [first = v[0]](bool acc, int b) {
    return acc && (b == first);
  });

which is also order N.

Planula answered 21/6, 2018 at 9:3 Comment(0)
P
0

The C++ function is defined in library in STL. This function operates on whole range of array elements and can save time to run a loop to check each elements one by one. It checks for a given property on every element and returns true when each element in range satisfies specified property, else returns false.

// C++ code to demonstrate working of all_of()
#include <vector>
#include <algorithm>
#include <iostream>
int main()
{
    std::vector<int> v(10, 2);
    
    // illustrate all_of
    if (std::all_of(v.cbegin(), v.cend(), [](int i){ return i % 2 == 0; }))
    {
        std::cout << "All numbers are even\n";
    }

}
Peristyle answered 10/6, 2022 at 5:11 Comment(0)
J
-1

Here is a readable C++17 solution which might remind students of the other constructors of std::vector:

if (v==std::vector(v.size(),v[0])) {
  // you guys are all the same
}

...before C++17, the std::vector rvalue would need its type provided explicitly:

if (v==std::vector<typename decltype(v)::value_type>(v.size(),v[0])) {
  // you guys are all the same
}
Jackqueline answered 16/9, 2021 at 16:34 Comment(1)
O(N) memory overhead, though.Rhaetia

© 2022 - 2024 — McMap. All rights reserved.