Search code examples
c++algorithmsortingcomparator

why is my comparator function not working?


I have an array with elements as [0, 1, 0, 3, 12]. I want to shift all the zeros to the end of it while maintaining the relative order of the non-zero elements. After shifting the array should look like this [1, 3, 12, 0, 0]. So to do this I wrote the following code:

#include <bits/stdc++.h>

using namespace std;

bool cmp(int a, int b){
    if (a == 0) {
        return a > b;
    } else {
        return true;
    }
}

int main(){
    int arr[] = {0, 1, 0, 3, 12};
    sort(arr, arr + 5, cmp);
    for (int i: arr) {
        cout << i;
    }
}

I wrote this code thinking that by returning false whenever zero is placed before a non-zero element i can swap the elements but this code gives the output as [12, 3, 1, 0, 0] i.e it just sorted the elements in descending order. Can anyone explain me why my comparator function does not working? Can I use comparator function to do this?


Solution

  • The comparator you pass to std::sort needs to be a strict weak order. In particular, you need cmp(x, x) = false for the same value, and at most one of cmp(x, y) and cmp(y, x) to be true (which is not true for cmp(1, 2) == cmp(2, 1) == true)

    What you want in your order is for all elements to be equivalent, except 0 which is greater than all other elements. This can be done with a function like this:

    bool cmp(int a, int b) {
        if (a == 0) {
            if (b == 0) return false;  // equal
            return false;  // a is greater
        }
        if (b == 0) return true;  // `a < 0`? True, since 0 is the greatest element
        return false;  // All other elements are equivalent, not less than
    }
    
    // Simplified to
    bool cmp(int a, int b) {
        return b == 0 && a != 0;
    }
    

    And then you want to use std::stable_sort to preserve the order of the equivalent elements.


    However, sorting isn't the best approach here. You can simply move all the non-zero elements forward, then fill in the back space with 0s:

    void move_zeroes_to_back(int* begin, int* end) {
        int* nonzero_begin = begin;
        for (; begin != end; ++begin) {
            if (*begin != 0) *nonzero_begin++ = *begin;
            // else Don't move zero values forward
        }
        // Fill in the rest
        for (; nonzero_begin != end; ++nonzero_begin) {
            *nonzero_begin = 0;
        }
    }
    

    Where the first part of this algorithm is what std::remove does:

    void move_zeroes_to_back(int* begin, int* end) {
        int* nonzero_end = std::remove(begin, end, 0);  // Remove all zero values
        std::fill(nonzero_end, end, 0);  // Replace removed zero values at the end
    }